如何让AVCaptureSession和AVPlayer尊重AVAudioSessionCategoryAmbient?

时间:2015-11-19 01:51:53

标签: ios swift avfoundation avplayer avaudiosession

我正在创建一个记录(AVCaptureSession)和播放(AVPlayerLayer)视频的应用。我希望能够在不暂停其他应用程序的背景音频的情况下执行此操作,并且我希望播放时尊重静音开关。

在AppDelegate中我设置了AVAudioSessionCategoryAmbient,根据文档应该:

  

声音播放非主要应用的类别 - 也就是说,您的应用可以在关闭声音的情况下成功使用。

     

此类别也适用于“播放”风格的应用,例如用户在播放音乐应用时播放的虚拟钢琴。使用此类别时,来自其他应用程序的音频会与您的音频混合。通过屏幕锁定和静音开关(在iPhone上称为响铃/静音开关)使您的音频静音。

这完美地描述了我正在寻找的行为。但它不起作用。

我知道它已设置,因为如果我在任何视图控制器中尝试print(AVAudioSession.sharedInstance().category),它将返回AVAudioSessionCategoryAmbient

有什么想法吗?我正在使用Swift,但即使是一个模糊的方向也不胜感激。

2 个答案:

答案 0 :(得分:6)

如何将背景音频与AVCapture会话混合:

如果您有麦克风输入,默认情况下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 :(得分:1)

编辑:这可能仅与那些使用AVAssetWriter使用样本缓冲区录制视频的人有关-在直接操作和渲染摄像机的逐帧输出时,要更好地管理几个数量级。

我为此苦了一段时间,因为我正在构建一个复杂的应用程序,该应用程序必须在(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).")
    }

使用AVCaptureSession设置音频设备输入/输出

        // 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

准备好所有这些之后,您想要设置和配置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的视频和音频输入标记为完成。