【发布时间】: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