如何使用CoreAudio的AudioConverter实时编码AAC?
所有的示例代码,我可以find使用AudioConverterRef
重点是使用情况下,我有所有的数据AudioConverterRef
(如转换文件在磁盘上)。 他们通常使用PCM调用AudioConverterFillComplexBuffer
作为inInputDataProcUserData
进行转换,并将inInputDataProcUserData
填入callback中。 (这真的是应该如何使用?为什么它需要callback,然后呢?)对于我的使用情况,我试图从麦克风streamaacaudio,所以我没有文件,我的PCM缓冲区正在实时填写。
由于我没有*ioNumberDataPackets = 0
提供所有的数据,所以我一直试图在callback*ioNumberDataPackets = 0
中调用*ioNumberDataPackets = 0
,但是这只是把AudioConverter置于死状态,在那里它需要AudioConverterReset()
特德,我没有得到任何数据。
我在网上看到的一个build议是,如果我存储的数据太小,就会从callback中返回一个错误,只要我有更多的数据,就再试一次,但是这看起来像是浪费资源,我不能让自己尝试一下。
我真的需要做“重试,直到我的input缓冲区足够大”,还是有更好的方法吗?
AudioConverterFillComplexBuffer
实际上并不意味着“用我的input缓冲区填充编码器”。 这意味着“用来自编码器的编码数据填充这个输出缓冲器 ”。 有了这个观点,callback突然变得有意义 – 它被用来获取源数据来满足“为我填充这个输出缓冲区”的请求。 也许这对其他人来说是显而易见的,但是我花了很长时间才明白这一点(并且从所有的AudioConverter示例代码中我看到人们通过inInputDataProcUserData
发送input数据,我猜并不是唯一的)。
AudioConverterFillComplexBuffer
调用是阻塞的,并期望您从callback中同步传递数据。 如果您实时编码,则需要在您自己设置的单独线程上调用FillComplexBuffer
。 在callback中,可以检查可用的input数据,如果不可用,则需要阻塞信号量。 使用NSCondition,编码器线程会看起来像这样:
- (void)startEncoder { OSStatus creationStatus = AudioConverterNew(&_fromFormat, &_toFormat, &_converter); _running = YES; _condition = [[NSCondition alloc] init]; [self performSelectorInBackground:@selector(_encoderThread) withObject:nil]; } - (void)_encoderThread { while(_running) { // Make quarter-second buffers. size_t bufferSize = (_outputBitrate/8) * 0.25; NSMutableData *outAudioBuffer = [NSMutableData dataWithLength:bufferSize]; AudioBufferList outAudioBufferList; outAudioBufferList.mNumberBuffers = 1; outAudioBufferList.mBuffers[0].mNumberChannels = _toFormat.mChannelsPerFrame; outAudioBufferList.mBuffers[0].mDataByteSize = (UInt32)bufferSize; outAudioBufferList.mBuffers[0].mData = [outAudioBuffer mutableBytes]; UInt32 ioOutputDataPacketSize = 1; _currentPresentationTime = kCMTimeInvalid; // you need to fill this in during FillComplexBuffer const OSStatus conversionResult = AudioConverterFillComplexBuffer(_converter, FillBufferTrampoline, (__bridge void*)self, &ioOutputDataPacketSize, &outAudioBufferList, NULL); // here I convert the AudioBufferList into a CMSampleBuffer, which I've omitted for brevity. // Ping me if you need it. [self.delegate encoder:self encodedSampleBuffer:outSampleBuffer]; } }
callback看起来像这样:(注意,我通常使用这个蹦床来立即转发到我的实例上的一个方法(通过在inUserData
转发我的实例;为简洁起见,这一步被省略)):
static OSStatus FillBufferTrampoline(AudioConverterRef inAudioConverter, UInt32* ioNumberDataPackets, AudioBufferList* ioData, AudioStreamPacketDescription** outDataPacketDescription, void* inUserData) { [_condition lock]; UInt32 countOfPacketsWritten = 0; while (true) { // If the condition fires and we have shut down the encoder, just pretend like we have written 0 bytes and are done. if(!_running) break; // Out of input data? Wait on the condition. if(_inputBuffer.length == 0) { [_condition wait]; continue; } // We have data! Fill ioData from your _inputBuffer here. // Also save the input buffer's start presentationTime here. // Exit out of the loop, since we're done waiting for data break; } [_condition unlock]; // 2. Set ioNumberDataPackets to the amount of data remaining // if running is false, this will be 0, indicating EndOfStream *ioNumberDataPackets = countOfPacketsWritten; return noErr; }
为了完整起见,下面介绍如何为编码器提供数据,以及如何正确closures它:
- (void)appendSampleBuffer:(CMSampleBufferRef)sampleBuffer { [_condition lock]; // Convert sampleBuffer and put it into _inputBuffer here [_condition broadcast]; [_condition unlock]; } - (void)stopEncoding { [_condition lock]; _running = NO; [_condition broadcast]; [_condition unlock]; }
为了将来的参考,有一个方法更容易的select。
CoreAudio标题的状态:
如果callback返回错误,则必须返回零包数据。 AudioConverterFillComplexBuffer将停止产生输出,并返回已经产生的输出给调用者以及错误代码。 当input过程暂时没有数据,但尚未到达stream结尾时,可以使用此机制。
那么,就这么做吧。 而不是用* ioNumberDataPackets = 0来返回noErr,而是返回任何错误(只做一个,我用-1),并且已经转换的数据将被返回,而audio转换器保持活动状态,不需要重置。