【问题标题】:Capture stereo audio data捕获立体声音频数据
【发布时间】:2021-01-15 11:52:57
【问题描述】:

我有一个 MacOS Swift 应用程序,可以处理从麦克风录制的音频数据。麦克风具有立体声功能,但我只能录制单声道数据。

在下面的代码中,如果我 let alwaysMono = true,func setup() 报告活动格式是立体声,但将其覆盖为单声道。一切都适用于单声道输入流。

如果我let alwaysMono = false,setup() 将 nChannels 设置为 2。但 captureOutput 没有得到任何数据。从 UnsafeMutableAudioBufferListPointer 返回的 AudioBuffer 始终具有 nil mData。如果我不检查 nil mData,程序就会崩溃。

如何获得完整的立体声输入?

编辑:在 captureOutput 中,CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer 返回错误代码 -12737,对应于 kCMSampleBufferError_ArrayTooSmall。我检查了传入 captureOutput 的 sampleBuffer arg,我看不出它有任何明显的错误。但我不知道该寻找什么。

另一个编辑:我使用内置单声道麦克风的代码测试了我的代码,令我惊讶的是它认为它也是立体声的,这表明我获取和使用 AVCaptureDevice.activeFormat 的方式显然有问题。我不知道从这里去哪里。

class Recorder: NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
    let alwaysMono = false
    var nChannels:UInt32 = 1
    let session : AVCaptureSession!
    static let realTimeQueue = DispatchQueue(label: "com.myapp.realtime",
                                             qos: DispatchQoS( qosClass:DispatchQoS.QoSClass.userInitiated, relativePriority: 0 ))
    override init() {
        session = AVCaptureSession()
        super.init()
    }
    static var recorder:Recorder?
    static func record() ->Bool {
        if recorder == nil {
            recorder = Recorder()
            if !recorder!.setup(callback:record) {
                recorder = nil
                return false
            }
        }
        realTimeQueue.async {
            if !recorder!.session.isRunning {
                recorder!.session.startRunning()
            }
        }
        return true
    }
    static func pause() {
        recorder!.session.stopRunning()
    }
    func setup( callback:@escaping (()->Bool)) -> Bool {
        let device = AVCaptureDevice.default( for: AVMediaType.audio )
        if device == nil { return false }
        if let format = Recorder.getActiveFormat() {
            nChannels = format.mChannelLayoutTag == kAudioChannelLayoutTag_Stereo ? 2 : 1
            print("active format is \((nChannels==2) ? "Stereo" : "Mono")")
            if alwaysMono {
                print( "Overriding to mono" )
                nChannels = 1
            }
        }
        if #available(OSX 10.14, *) {
            let status = AVCaptureDevice.authorizationStatus( for:AVMediaType.audio )
            if status == .notDetermined {
                AVCaptureDevice.requestAccess(for: AVMediaType.audio ){ granted in
                    _ = callback()
                }
                return false
            } else if status != .authorized {
                return false
            }
        }
        var input : AVCaptureDeviceInput
        do {
            try device!.lockForConfiguration()
            try input = AVCaptureDeviceInput( device: device! )
            device!.unlockForConfiguration()
        } catch {
            device!.unlockForConfiguration()
            return false
        }
        let output = AVCaptureAudioDataOutput()
        output.setSampleBufferDelegate(self, queue: Intonia.realTimeQueue)
        let settings = [
            AVFormatIDKey: kAudioFormatLinearPCM,
            AVNumberOfChannelsKey : nChannels,
            AVSampleRateKey : 44100,
            AVLinearPCMBitDepthKey : 16,
            AVLinearPCMIsFloatKey : false
            ] as [String : Any]
        output.audioSettings = settings
        session.beginConfiguration()
        if !session.canAddInput( input ) {
            return false
        }
        session.addInput( input )
        if !session.canAddOutput( output ) {
            return false
        }
        session.addOutput( output )
        session.commitConfiguration()
        return true
    }
    func getActiveFormat() -> AudioFormatListItem? {
        if #available(OSX 10.15, *) {
            let device = AVCaptureDevice.default( for: AVMediaType.audio )
            if device == nil { return nil }
            let list = device!.activeFormat.formatDescription.audioFormatList
            if list.count < 1 { return nil }
            return list[0]
        }
        return nil
    }
    func captureOutput(_ captureOutput: AVCaptureOutput,
                       didOutput sampleBuffer: CMSampleBuffer,
                       from connection: AVCaptureConnection){
        var buffer: CMBlockBuffer? = nil
        var audioBufferList = AudioBufferList(
            mNumberBuffers: 1,
            mBuffers: AudioBuffer(mNumberChannels: nChannels, mDataByteSize: 0, mData: nil)
        )
        CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(
            sampleBuffer,
            bufferListSizeNeededOut: nil,
            bufferListOut: &audioBufferList,
            bufferListSize: MemoryLayout<AudioBufferList>.size,
            blockBufferAllocator: nil,
            blockBufferMemoryAllocator: nil,
            flags: UInt32(kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment),
            blockBufferOut: &buffer
        )
        let abl = UnsafeMutableAudioBufferListPointer(&audioBufferList)
        for buff in abl {
            if buff.mData != nil {
                let count = Int(buff.mDataByteSize)/MemoryLayout<Int16>.size
                let samples = UnsafeMutablePointer<Int16>(OpaquePointer(buff.mData))
                process(samples:samples!, count:count)
            } else {
                print("No data!")
            }
        }
    }
    func process( samples: UnsafeMutablePointer<Int16>, count: Int ) {
        let firstValue = samples[0]
        print( "\(count) values received, first is \(firstValue)" )
    }
}

【问题讨论】:

  • 在 [libraries.io 网站]: (libraries.io/github/fruitsamples/AVCaptureToAudioUnit) 上找到了这个,使用 CoreMedia API CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer,使用 CAAudioBufferList 会派上用场。前面提到的 API(名称较长的 API)需要一个大小正确的客户端分配的 AudioBufferList 连同分配的大小一起传递。如果大小错误,您可能会返回可怕的 kFigSampleBufferError_ArrayTooSmall(嗯,这就是标题所说的,但实际上是 kCMSampleBufferError_ArrayTooSmall)和空缓冲区。
  • Here I found this: AudioBufferList 必须包含相同数量的通道,并且其数据缓冲区的大小必须能够容纳指定数量的帧。 我没有使用 Core 的经验媒体,但似乎通道数不匹配,或者您可能需要将缓冲区大小加倍以适应完整的立体声帧(立体声 PCM 帧中有两倍的样本)祝您好运!
  • 我没有指定缓冲区大小。对于单声道,返回的缓冲区为 1024 字节。在AudioBufferList构造函数中,AudioBuffer原来指定了nMumberChannels:0,改成1或者2好像没有任何效果。

标签: swift macos avcapturesession


【解决方案1】:

这个问题可能有很多地方出错。首先,这取决于您使用的麦克风。它是你 Mac 上的那个吗?如果是这样,那么它通常是单声道的。但是,您可以让自己成为一种解决方法来测试它是否有效并且不依赖于 always: bool。尝试录制两次,用单声道录制第一个通道,然后用第二个通道录制第二个,使用不同的“加倍”代码。

我看到的第二件事可能是在 setup func 中显式键入立体声通道,以确保它正在获取它的 2 个通道并使用带有显式 == true 或 false 语句的 alwaysMono。看看你是否可以在代码期间打印一些频道信息,这样你就知道哪里出了问题。

【讨论】:

  • 我正在使用 Zoom H2 录音机作为外部 USB 麦克风。 Audacity 可以很好地使用它,并记录两个独立的频道。我刚刚使用内置单声道麦克风的代码测试了我的代码,令我惊讶的是它认为它也是立体声的,这表明我获取和使用 AVCaptureDevice.activeFormat 的方式显然有问题。我不知道从这里去哪里。
  • 好的,从 Mac 麦克风开始。你能克隆代码并记录两次吗?
  • 第二次您尝试在 alwaysMono 部分中使用 true - false 吗?第三:我发现了这个:developer.apple.com/documentation/avfoundation/avaudiosession/… 查看输入方向部分,它还需要显式输入。也许您可以将 2 个单声道文件合并为 1 个。我不知道您是否已经能够写入文件?内置的麦克风大多是立体声的,但 Mac 只有一个。据我所知,M4A 文件更容易。
  • 第一个:当从 .WAV 或 .MP3 读取时,我的应用程序可以很好地处理立体声。它可以对其进行分析、回放并将其保存到文件中。我可以通过复制单声道轻松创建立体声文件,但我不知道这将如何解决我的问题。 2nd:我不明白你想让我用 alwaysMono 做什么。当 alwaysMono 为真时,nChannels 为 1,当它为假时,nChannels 可能为 1 或 2。传递给输出音频设置的是 nChannels,而不是 alwaysMono。第三:您提供的链接适用于 AVAudioSession,它在 iOS 中可用,但在 MacOS 中不可用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-08-07
  • 1970-01-01
  • 1970-01-01
  • 2011-03-18
  • 1970-01-01
  • 2013-05-04
  • 1970-01-01
相关资源
最近更新 更多