我找到的所有使用AudioConverterRef
的示例代码都侧重于我预先拥有所有数据的用例(例如在磁盘上转换文件)。他们通常使用PCM将AudioConverterFillComplexBuffer
调用为inInputDataProcUserData
并将其填入回调中。 (这真的是它应该如何使用?为什么它需要回调呢?)对于我的用例,我试图从麦克风流式传输aac音频,所以我没有文件,我的PCM缓冲区正在实时填写。
由于我没有预先提供所有数据,因此我在输入数据输出后尝试在回调中执行*ioNumberDataPackets = 0
,但这只是将AudioConverter置于需要的死状态是AudioConverterReset()
,我没有得到任何数据。
我在网上看到的一种方法是,如果我存储的数据太小,就会从回调中返回错误,只要我有更多数据就再试一次,但这似乎浪费了我的资源不能让自己尝试。
我真的需要进行“重试,直到我的输入缓冲区足够大”,还是有更好的方法?
答案 0 :(得分:13)
AudioConverterFillComplexBuffer
实际上并不意味着“使用我在这里的输入缓冲区填充编码器”。这意味着“在这里用编码器中的编码数据填充输出缓冲区”。从这个角度来看,回调突然变得有意义 - 它用于获取源数据以满足“为我填充此输出缓冲区”请求。也许这对其他人来说是显而易见的,但我花了很长的时间来理解这一点(从我看到的所有AudioConverter示例代码中,我看到人们通过inInputDataProcUserData
发送输入数据,我'猜测我不是唯一一个。)
AudioConverterFillComplexBuffer
调用正在阻止,并期望您从回调中同步传递数据。如果您是实时编码,则需要在自己设置的单独线程上调用FillComplexBuffer
。在回调中,您可以检查可用的输入数据,如果它不可用,则需要阻塞信号量。使用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];
}
}
并且回调可能如下所示:(请注意,我通常使用此蹦床立即转发到我的实例上的方法(通过在inUserData
中转发我的实例;为简洁省略此步骤)):< / p>
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;
}
为了完整起见,以下是您如何为此编码器提供数据,以及如何正确关闭它:
- (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];
}
答案 1 :(得分:0)
为了将来参考,有一种方法可以更容易选择。
CoreAudio标头的状态:
如果回调返回错误,则必须返回零数据包。 AudioConverterFillComplexBuffer将停止生成输出并返回任何内容 已经为其调用者生成了输出以及错误代码。这个 当输入proc暂时耗尽数据时,可以使用机制,但是 还没有到达目的地。
所以,做到这一点。不使用* ioNumberDataPackets = 0返回noErr,而是返回任何错误(只需将其设置为1,我使用-1),并且将返回已转换的数据,同时音频转换器保持活动状态且不需要重置。