我正在制作一个用于录制(AVCaptureSession
)和播放(AVPlayerLayer
)视频的应用。我希望能够在不暂停其他应用程序的背景音频的情况下执行此操作,并且希望播放时遵守静音开关。
在AppDelegate中,我根据文档设置了AVAudioSessionCategoryAmbient
:
非主要声音播放的应用程序的类别,也就是说,关闭声音后可以成功使用您的应用程序。
此类别也适用于“伴奏”风格的应用程序,例如用户在播放“音乐”应用程序时演奏的虚拟钢琴。使用此类别时,其他应用程序的音频会与您的音频混合在一起。屏幕锁定和静音开关(在iPhone上称为“铃声/静音”开关)使音频静音。
这完美地描述了我正在寻找的行为。但这不起作用。
我知道它已设置,因为如果我在任何视图控制器中尝试print(AVAudioSession.sharedInstance().category)
,它将返回AVAudioSessionCategoryAmbient
。
有什么想法吗?我使用的是Swift,但即使是模糊的指导方向也可以使用。
[如果您有麦克风输入,则默认情况下,AVCapture会话会将您的应用程序AVAudioSession设置为AVAudioSessionCategoryPlayAndRecord
。您必须告诉它不要:
AVCaptureSession.automaticallyConfiguresApplicationAudioSession = false
但是,只需冻结应用程序。因为不幸的是,AVAudioSessionCategoryAmbient
不适用于AVCaptureSession
。
解决方案是将您的应用程序AVAudioSession
设置为AVAudioSessionCategoryPlayAndRecord
带有选项:
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, withOptions: [.MixWithOthers, .AllowBluetooth, .DefaultToSpeaker])
try AVAudioSession.sharedInstance().setActive(true)
} catch let error as NSError {
print(error)
}
.MixWithOthers
是最重要的一种。这样就可以播放其他应用程序的音频。但是它把它转换成从听筒里出来的声音,这真是太奇怪了(我以为它一开始会被掩盖)。因此,.DefaultToSpeaker
将其移至底部扬声器,.AllowBluetooth
使您可以使蓝牙音频从耳机中发出,同时还可以启用蓝牙麦克风。不知道是否可以进一步完善,但是它们似乎是所有相关选项。
在录制过程中,您将AVAudioSession
设置为AVAudioSessionCategoryPlayAndRecord
,但这不考虑静音开关。
因为有麦克风输入时无法设置AVAudioSessionCategoryAmbient
。诀窍是从AVCaptureSession
上移除麦克风,然后将AVAudioSession
设置为AVAudioSessionCategoryAmbient
:
do {
captureSession.removeInput(micInput)
try AVAudioSession.sharedInstance().setActive(false)
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
try AVAudioSession.sharedInstance().setActive(true)
} catch let error as NSError { print(error) }
一旦完成播放并需要返回录音,则需要再次设置AVAudioSessionCategoryPlayAndRecord
(再次设置选项,以便背景音频继续播放:]
do {
try AVAudioSession.sharedInstance().setActive(false)
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, withOptions: [.MixWithOthers, .AllowBluetooth, .DefaultToSpeaker])
try AVAudioSession.sharedInstance().setActive(true)
} catch let error as NSError { print(error) }
captureSession.automaticallyConfiguresApplicationAudioSession = false
captureSession.addInput(micInput!)
do
块中的第一行是让我困扰很长时间的事情。我不需要将音频会话设置为非活动状态即可切换到AVAudioSessionCategoryAmbient
,但是回到AVAudioSessionCategoryPlayAndRecord
时它正在暂停背景音频。
我一直在为此苦苦挣扎,因为我正在构建一个复杂的应用程序,该应用程序必须在(1)播放视频(带有其他应用程序的音频)和(2)播放和录制视频(带有其他应用程序的音频)之间进行委派。在我的上下文中,如果您有一个AVPlayer对象播放视频,以及一个AVCaptureSession记录来自输入设备的视频,则必须添加以下播放视频之前:
do {
// MARK: - AVAudioSession
try AVAudioSession.sharedInstance().setActive(false, options: [])
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers])
try AVAudioSession.sharedInstance().setActive(true, options: [])
} catch let error {
print("\(#file)/\(#line) - Error setting the AVAudioSession for mixing audio play back: \(error.localizedDescription as Any).")
}
接下来,要录制有/没有其他应用程序的音频和/或AVPlayer对象播放带有音频的视频的视频,您必须执行以下操作:
// Prepare the audio session to allow simultaneous audio-playback while recording video
do {
// MARK: - AVAudioSession
try AVAudioSession.sharedInstance().setActive(false, options: [.notifyOthersOnDeactivation])
} catch let error {
print("\(#file)/\(#line) - Error deactivating AVAudioSession: \(error.localizedDescription as Any).")
}
do {
// MARK: - AVAudioSession
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [.mixWithOthers, .defaultToSpeaker, .allowBluetoothA2DP])
} catch let error {
print("\(#file)/\(#line) - Error setting the AVAudioSession category: \(error.localizedDescription as Any).")
}
do {
// MARK: - AVAudioSession
try AVAudioSession.sharedInstance().setActive(true, options: [])
} catch let error {
print("\(#file)/\(#line) - Error activating the AVAudioSession: \(error.localizedDescription as Any).")
}
// Setup the audio device input
setupAudioDeviceInput { (error: NGError?) in
if error == nil {
print("\(#file)/\(#line) - Successfully added audio-device input.")
} else {
print("\(#file)/\(#line) - Error: \(error?.localizedDescription as Any)")
}
}
// Setup the audio data output
setupAudioDataOutput { (error: NGError?) in
if error == nil {
print("\(#file)/\(#line) - Successfully added audio-data output.")
} else {
print("\(#file)/\(#line) - Error: \(error?.localizedDescription as Any)")
}
}
/// (1) Initializes the AVCaptureDevice for audio, (2) creates the associated AVCaptureDeviceInput for audio, and (3) adds the audio device input to the AVCaptureSession.
/// - Parameter error: An optional NGError object returned if the setup for the audio device input fails.
func setupAudioDeviceInput(completionHandler: @escaping (_ error: NGError?) -> ()) {
// Setup the AVCaptureDevice for audio input
self.audioCaptureDevice = AVCaptureDevice.default(for: .audio)
// Unwrap the AVCaptureDevice for audio input
guard audioCaptureDevice != nil else {
print("\(#file)/\(#line) - Couldn't unwrap the AVCaptureDevice for audio.")
return
}
do {
/// Create the AVCaptureDeviceInput for the AVCaptureDevice for audio
self.audioCaptureDeviceInput = try AVCaptureDeviceInput(device: audioCaptureDevice)
// Add the AVCaptureDeviceInput for the audio
if self.captureSession.canAddInput(self.audioCaptureDeviceInput) {
self.captureSession.addInput(self.audioCaptureDeviceInput)
// Pass the values in the completion handler
completionHandler(nil)
} else {
// MARK: - NGError
let error = NGError(message: "\(#file)/\(#line) - Couldn't add the AVCaptureDeviceInput to the capture session.")
// Pass the values in the completion handler
completionHandler(error)
}
} catch let error {
// MARK: - NGError
let error = NGError(message: "\(#file)/\(#line) - Error setting up audio input for the capture session \(error.localizedDescription as Any)")
// Pass the values in the completion handler
completionHandler(error)
}
}
/// (1) Initializes the AVCaptureAudioDataOutput, (2) sets its AVCaptureAudioDataOutputSampleBufferDelegate and adds it to the AVCaptureSession.
/// - Parameter error: An optional NGError object returned if the setup fails.
func setupAudioDataOutput(completionHandler: @escaping (_ error: NGError?) -> ()) {
// Setup the AVCaptureAudioDataOutput
self.audioDataOutput = AVCaptureAudioDataOutput()
// Determine if the AVCaptureSession can add the audio data output
if self.captureSession.canAddOutput(self.audioDataOutput) {
// Setup the AVCaptureAudioDataOutput's AVCaptureAudioDataOutputSampleBufferDelegate and add it to the AVCaptureSession
self.audioDataOutput.setSampleBufferDelegate(self, queue: self.outputQueue)
self.captureSession.addOutput(self.audioDataOutput)
// Pass the values to the completion handler
completionHandler(nil)
} else {
// MARK: - NGError
let error = NGError(message: "\(#file)/\(#line) - Couldn't add the AVCaptureAudioDataOutput to the AVCaptureSession.")
// Pass the values to the completion handler
completionHandler(error)
}
}
准备好所有这些之后,您想要设置和配置AVAssetWriter以开始将视频数据写入文件。
// Remove the audio device input and its audio data output.
if let audioInput = audioCaptureDeviceInput, let audioOutput = audioDataOutput {
captureSession.removeInput(audioInput)
captureSession.removeOutput(audioOutput)
} else {
print("\(#file)/\(#line) - Couldn't remove the AVCaptureDeviceInput for audio and the AVCaptureAudioDataOutput.")
}
完成视频/音频数据处理后,请确保将AVAssetWriter的视频和音频输入标记为已完成。