Как использовать AudioConverter от CoreAudio для кодирования AAC в режиме реального времени?

Весь пример кода, который я могу найти, который использует AudioConverterRef фокусируется на случаях использования, когда у меня есть все данные заранее (например, преобразование файла на диск). Они обычно называют AudioConverterFillComplexBuffer с PCM для преобразования в качестве inInputDataProcUserData и просто заполните его в обратном вызове. (Это действительно то, как это должно использоваться? Тогда зачем нужен обратный вызов?) Для моего случая использования я пытаюсь транслировать звук aac с микрофона, поэтому у меня нет файла, и мой буфер PCM работает заполнено в режиме реального времени.

Поскольку у меня нет всех данных заранее, я попытался сделать *ioNumberDataPackets = 0 в обратном вызове, как только мои входные данные выходят, но это просто переводит AudioConverter в мертвое состояние, где он должен быть AudioConverterReset()Тед, и я не получаю никаких данных из этого.

Один из подходов, которые я видел в Интернете, - это возвращать ошибку из обратного вызова, если данные, которые я сохранил, слишком малы, и попробуйте еще раз, когда у меня будет больше данных, но это кажется такой пустой тратой ресурсов, что я не могу заставить себя даже попробовать это.

Мне действительно нужно сделать "повторить, пока мой входной буфер не станет достаточно большим", или есть лучший способ?

2 ответа

Решение

AudioConverterFillComplexBuffer на самом деле не означает "заполнить кодировщик моим входным буфером, который у меня есть". Это означает "заполнить этот выходной буфер здесь закодированными данными из кодера". С этой точки зрения обратный вызов внезапно имеет смысл - он используется для выборки исходных данных для удовлетворения запроса "заполнить этот буфер вывода для меня". Возможно, это очевидно для других, но мне потребовалось много времени, чтобы понять это (и из всего примера кода AudioConverter, который я вижу, всплывают, где люди отправляют входные данные через inInputDataProcUserDataПолагаю, я не единственный)

AudioConverterFillComplexBuffer вызов блокируется и ожидает, что вы доставите ему данные синхронно от обратного вызова. Если вы кодируете в режиме реального времени, вам нужно будет позвонить FillComplexBuffer на отдельной ветке, которую вы настраиваете сами. В обратном вызове вы можете проверить наличие доступных входных данных, а если они недоступны, вам нужно заблокировать семафор. Используя условие NSC, поток кодера будет выглядеть примерно так:

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

Для дальнейшего использования есть способ проще.

Состояние заголовка CoreAudio:

Если обратный вызов возвращает ошибку, он должен вернуть ноль пакетов данных. AudioConverterFillComplexBuffer прекратит производить вывод и вернет все, что уже было сделано, его вызывающей стороне вместе с кодом ошибки. Этот механизм может использоваться, когда во входной процедуре временно заканчиваются данные, но еще не достигнут конец потока.

Так что, делай именно это. Вместо того, чтобы возвращать noErr с *ioNumberDataPackets = 0, возвращайте любую ошибку (просто сделайте одну, я использовал -1), и уже преобразованные данные будут возвращены, в то время как Audio Converter остается в живых и не требует сброса.

Другие вопросы по тегам