【问题标题】:How do I make AVCaptureSession and AVPlayer respect AVAudioSessionCategoryAmbient?如何让 AVCaptureSession 和 AVPlayer 尊重 AVAudioSessionCategoryAmbient?
【发布时间】:2015-11-19 01:51:53
【问题描述】:

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

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

声音播放为非主要应用的类别,即您的应用可以在关闭声音的情况下成功使用。

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

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

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

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

【问题讨论】:

    标签: ios swift avfoundation avplayer avaudiosession


    【解决方案1】:

    如何将背景音频与 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 时会暂停背景音频。

    【讨论】:

    • 简而言之,听起来好像没有办法在麦克风处于活动状态时保持AVAudioSessionCategoryPlayAndRecord 模式。
    • 要更正我上面的评论,当麦克风处于活动状态时,您不能使用AVAudioSessionCategoryAmbient 模式
    • 您在使用 5s 时遇到过任何问题吗?我们的代码与您在此处发布的代码非常相似,它在 6s 和 4s 上可以正常工作,但是当我们在 5s 上运行时,我们的捕获屏幕会冻结并且无法正常工作。仅当我们在具有 avplayer 的屏幕之一后导航到捕获屏幕时才会出现此问题
    • 当我使用这段代码时,它会冻结 AVCaptureSession 大约 1-2 秒。有什么解决方法吗?
    【解决方案2】:

    编辑:这可能只与那些使用 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 的视频和音频输入标记为已完成。

    【讨论】:

    • 您能否告诉我如何处理这种情况,例如,如果在其他应用程序中播放音乐而不是我的录音机应该接受该音乐,并且如果当前没有播放音乐,那么我的录音机应该启用麦克风以进行录音. TIA
    猜你喜欢
    • 1970-01-01
    • 2015-09-26
    • 1970-01-01
    • 2017-07-13
    • 2018-11-18
    • 2013-06-30
    • 2012-09-04
    • 1970-01-01
    • 2015-07-08
    相关资源
    最近更新 更多