Ios 如何使用CoreAudio';s音频转换器对AAC进行实时编码?

Ios 如何使用CoreAudio';s音频转换器对AAC进行实时编码?,ios,audio,core-audio,aac,audio-converter,Ios,Audio,Core Audio,Aac,Audio Converter,我能找到的所有使用AudioConverterRef的示例代码都集中在我预先拥有所有数据的用例上(例如转换磁盘上的文件)。他们通常使用PCM调用AudioConverterFillComplexBuffer,将PCM转换为inInputDataProcUserData中的,然后在回调中填充它。(这就是它应该被使用的方式吗?为什么它需要回调呢?)在我的用例中,我试图从麦克风传输aac音频,所以我没有文件,我的PCM缓冲区正在被实时填充 因为我没有预先准备好所有的数据,所以我尝试在输入数据输出后在回

我能找到的所有使用
AudioConverterRef
的示例代码都集中在我预先拥有所有数据的用例上(例如转换磁盘上的文件)。他们通常使用PCM调用
AudioConverterFillComplexBuffer
,将PCM转换为inInputDataProcUserData中的
,然后在回调中填充它。(这就是它应该被使用的方式吗?为什么它需要回调呢?)在我的用例中,我试图从麦克风传输aac音频,所以我没有文件,我的PCM缓冲区正在被实时填充

因为我没有预先准备好所有的数据,所以我尝试在输入数据输出后在回调中执行
*ioNumberDataPackets=0
,但这只会使音频转换器处于一种死区状态,它需要是
AudioConverterReset()
ted,而我没有从中获取任何数据

我在网上看到的一种方法是,如果我存储的数据太小,就从回调中返回一个错误,然后在我有更多数据时再试一次,但这似乎是对资源的浪费,我甚至无法尝试


我真的需要“重试直到我的输入缓冲区足够大”,还是有更好的方法?

AudioConverterFillComplexBuffer
实际上并不意味着“用我这里的输入缓冲区填充编码器”。这意味着“用编码器的编码数据填充此输出缓冲区”。从这个角度来看,回调突然变得有意义了——它用于获取源数据以满足“为我填充此输出缓冲区”请求。也许这对其他人来说是显而易见的,但我花了很长时间才理解这一点(从我看到的所有音频转换器示例代码中,人们通过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
中转发我的实例;为了简洁起见,省略了这个步骤):

为了完整起见,下面介绍如何向编码器提供数据,以及如何正确关闭编码器:

- (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];
}

作为将来的参考,有一种方法更容易选择

CoreAudio标头的状态:

如果回调返回错误,它必须返回零个数据包。 AudioConverterFillComplexBuffer将停止输出并返回任何内容 已向其调用者生成输出以及错误代码。这 当输入进程暂时耗尽数据时,可以使用该机制,但 尚未到达流的末尾


那么,就这么做吧。不要返回*ioNumberDataPackets=0的noErr,而是返回任何错误(只需补错一次,我使用了-1),已经转换的数据将被返回,而音频转换器保持活动状态,不需要重置。

我尝试过;当我尝试这种方法时,AudioConverter会给我一个只有一个mpeg头的12字节缓冲区,然后拒绝接收更多数据。我假设这意味着AC需要足够的数据来发出完整的aac帧才能工作。啊。可能是。我只使用PCM输出,这对我来说非常好。AudioConverter确实保持了它自己的内部缓冲,所以奇怪的是,这对AAC也不起作用。但是API确实声明它必须输出一些东西,所以可能会把它们放在一个奇怪的地方。我在用_inputBuffer填充iodate和设置ioNumberDataPackets时遇到了一些问题,请您填充代码好吗?一些问题:是否需要将ioData.mNumberBuffers设置为1?我们是否需要填充从_inputBuffer到ioData.mBuffers[0]的所有数据?我们如何计算数据包的数量?还是把它设为1?“将ioNumberDataPackets设置为剩余的数据量”是什么意思?文档中说“退出时,实际提供的音频数据包的数量用于输入”?@nevyn我目前正在努力将AudioBufferList转换为CMSampleBuffer,您在这里省略了它。你能告诉我你是怎么做到的吗?谢谢非常感谢。我最初的方法是在
FillComplexBuffer
(输出)循环上放置一个信号量来等待传入的数据。但那地方放错了。在
FillBuffer
(输入)回调中将wait移动到一个循环,将“背压”放在正确的位置。你的问答很好地解释了原因。
- (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];
}