7

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

因为我没有预先准备好所有数据,所以*ioNumberDataPackets = 0一旦我的输入数据出来,我就尝试在回调中进行操作,但这只会使 AudioConverter 处于需要测试的死状态AudioConverterReset(),而我没有从中获取任何数据。

我在网上看到的一种方法是,如果我存储的数据太小,则从回调中返回错误,一旦我有更多数据再试一次,但这似乎是一种资源浪费,我不能让自己尝试一下。

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

4

2 回答 2

14

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;为简洁起见,省略了此步骤)):

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];
}
于 2015-05-16T02:21:39.410 回答
0

为了将来参考,有一种更简单的选择。

CoreAudio 标头的状态:

如果回调返回错误,它必须返回零数据包。AudioConverterFillComplexBuffer 将停止产生输出并将已经产生的任何输出连同错误代码一起返回给它的调用者。当输入过程暂时用完数据但尚未到达流的末尾时,可以使用此机制。

所以,就这样做吧。不是用*ioNumberDataPackets = 0返回noErr,而是返回任何错误(只是弥补一个,我用-1),并且已经转换的数据将被返回,而Audio Converter保持活动状态并且不需要重置。

于 2017-01-05T05:01:20.917 回答