Могу ли я использовать AVCaptureSession для кодирования потока AAC в память?

Я пишу приложение для iOS, которое передает видео и аудио по сети.

Я использую AVCaptureSession для захвата необработанных видеокадров с помощью AVCaptureVideoDataOutput и кодирую их в программном обеспечении используя x264. Это прекрасно работает.

Я хотел сделать то же самое для звука, только мне не нужно было так много контроля над звуком, поэтому я хотел использовать встроенный аппаратный кодировщик для создания потока AAC. Это означало использование Audio Converter из слоя Audio Toolbox. Для этого я добавил обработчик аудиокадров AVCaptudeAudioDataOutput:

- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection 
{
    // get the audio samples into a common buffer _pcmBuffer
    CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
    CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &_pcmBufferSize, &_pcmBuffer);

    // use AudioConverter to
    UInt32 ouputPacketsCount = 1;
    AudioBufferList bufferList;
    bufferList.mNumberBuffers = 1;
    bufferList.mBuffers[0].mNumberChannels = 1;
    bufferList.mBuffers[0].mDataByteSize = sizeof(_aacBuffer);
    bufferList.mBuffers[0].mData = _aacBuffer;
    OSStatus st = AudioConverterFillComplexBuffer(_converter, converter_callback, (__bridge void *) self, &ouputPacketsCount, &bufferList, NULL);
    if (0 == st) {
        // ... send bufferList.mBuffers[0].mDataByteSize bytes from _aacBuffer...
    }
}

В этом случае функция обратного вызова для аудио конвертера довольно проста (при условии, что размеры и количество пакетов настроены правильно):

- (void) putPcmSamplesInBufferList:(AudioBufferList *)bufferList withCount:(UInt32 *)count
{
    bufferList->mBuffers[0].mData = _pcmBuffer;         
    bufferList->mBuffers[0].mDataByteSize = _pcmBufferSize;
}

И настройка аудио конвертера выглядит так:

{
    // ...
    AudioStreamBasicDescription pcmASBD = {0};
    pcmASBD.mSampleRate = ((AVAudioSession *) [AVAudioSession sharedInstance]).currentHardwareSampleRate;
    pcmASBD.mFormatID = kAudioFormatLinearPCM;
    pcmASBD.mFormatFlags = kAudioFormatFlagsCanonical;
    pcmASBD.mChannelsPerFrame = 1;
    pcmASBD.mBytesPerFrame = sizeof(AudioSampleType);
    pcmASBD.mFramesPerPacket = 1;
    pcmASBD.mBytesPerPacket = pcmASBD.mBytesPerFrame * pcmASBD.mFramesPerPacket;
    pcmASBD.mBitsPerChannel = 8 * pcmASBD.mBytesPerFrame;

    AudioStreamBasicDescription aacASBD = {0};
    aacASBD.mFormatID = kAudioFormatMPEG4AAC;
    aacASBD.mSampleRate = pcmASBD.mSampleRate;
    aacASBD.mChannelsPerFrame = pcmASBD.mChannelsPerFrame;
    size = sizeof(aacASBD);
    AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &size, &aacASBD);

    AudioConverterNew(&pcmASBD, &aacASBD, &_converter);
    // ...
}

Это кажется довольно простым, только ЭТО НЕ РАБОТАЕТ. После запуска AVCaptureSession аудиопреобразователь (в частности, AudioConverterFillComplexBuffer) возвращает ошибку «hwiu» (используемое оборудование). Преобразование работает нормально, если сеанс остановлен, но тогда я ничего не могу зафиксировать...

Мне было интересно, есть ли способ получить поток AAC из AVCaptureSession. Варианты, которые я рассматриваю, следующие:

  1. Каким-то образом использовать AVAssetWriterInput для кодирования аудиосэмплов в AAC, а затем каким-то образом получать закодированные пакеты (не через AVAssetWriter, который будет записывать только в файл).

  2. Реорганизация моего приложения таким образом, чтобы оно использовало AVCaptureSession только на стороне видео и использовало Очереди аудио на стороне аудио. Это усложнит управление потоком (запуск и остановка записи, реагирование на прерывания) и, боюсь, может вызвать проблемы с синхронизацией аудио и видео. Кроме того, это просто не похоже на хороший дизайн.

Кто-нибудь знает, возможно ли получить AAC из AVCaptureSession? Должен ли я использовать здесь аудиоочереди? Может ли это привести к проблемам с синхронизацией или управлением?


person Avner    schedule 30.05.2012    source источник
comment
Вы уверены, что ваш AudioConverter вообще работает? Вы пытались отключить захват и кодирование нулей, скажем?   -  person Rhythmic Fistman    schedule 31.05.2012
comment
Да, я сделал (кажется, я также упомянул об этом в вопросе). Кодер работает нормально, если AVCaptureSession не находится в рабочем состоянии.   -  person Avner    schedule 31.05.2012
comment
ой, извините. похоже, вы в безвыходном положении. добавление аудиовхода в сеанс захвата, похоже, связывает кодировщик AAC.   -  person Rhythmic Fistman    schedule 31.05.2012
comment
Вот что я понял. Я предполагаю, что сеанс захвата использует аудио-очередь или какой-то API более низкого уровня, который использует кодировщик. Увы, почему бы им все равно не предоставить доступ к данным, которые они обрабатывают...   -  person Avner    schedule 31.05.2012
comment
Итак, позвольте AVAssetWriter кодировать аудиоданные в файл и (осторожно) передавать их в потоковом режиме. Люди используют аналогичную технику для потоковой передачи данных h264 из кодировщика hw. Поэтому вы используете x264 вместо аппаратного кодировщика?   -  person Rhythmic Fistman    schedule 01.06.2012


Ответы (1)


В итоге я обратился за советом к Apple (оказывается, вы можете это сделать, если у вас есть платная учетная запись разработчика).

Кажется, что AVCaptureSession захватывает аппаратный кодировщик AAC, но позволяет использовать его только для записи непосредственно в файл.

Вы можете использовать программный кодировщик, но вы должны запросить его специально вместо использования AudioConverterNew:

AudioClassDescription *description = [self
        getAudioClassDescriptionWithType:kAudioFormatMPEG4AAC
                        fromManufacturer:kAppleSoftwareAudioCodecManufacturer];
if (!description) {
    return false;
}
// see the question as for setting up pcmASBD and arc ASBD
OSStatus st = AudioConverterNewSpecific(&pcmASBD, &aacASBD, 1, description, &_converter);
if (st) {
    NSLog(@"error creating audio converter: %s", OSSTATUS(st));
    return false;
}

с участием

- (AudioClassDescription *)getAudioClassDescriptionWithType:(UInt32)type
                                           fromManufacturer:(UInt32)manufacturer
{
    static AudioClassDescription desc;

    UInt32 encoderSpecifier = type;
    OSStatus st;

    UInt32 size;
    st = AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders,
                                    sizeof(encoderSpecifier),
                                    &encoderSpecifier,
                                    &size);
    if (st) {
        NSLog(@"error getting audio format propery info: %s", OSSTATUS(st));
        return nil;
    }

    unsigned int count = size / sizeof(AudioClassDescription);
    AudioClassDescription descriptions[count];
    st = AudioFormatGetProperty(kAudioFormatProperty_Encoders,
                                sizeof(encoderSpecifier),
                                &encoderSpecifier,
                                &size,
                                descriptions);
    if (st) {
        NSLog(@"error getting audio format propery: %s", OSSTATUS(st));
        return nil;
    }

    for (unsigned int i = 0; i < count; i++) {
        if ((type == descriptions[i].mSubType) &&
            (manufacturer == descriptions[i].mManufacturer)) {
            memcpy(&desc, &(descriptions[i]), sizeof(desc));
            return &desc;
        }
    }

    return nil;
}

Программный кодировщик, конечно, займет ресурсы процессора, но свою работу сделает.

person Avner    schedule 06.07.2012
comment
Не могли бы вы опубликовать сопроводительный код для преобразования? В основном реализация функции обратного вызова и определения _aacBuffer и _pcmBuffer. Большое спасибо. - person Erik Villegas; 09.04.2014
comment
Я ищу эквивалент Mac для того же самого, но mManufacturer не найден для Mac. Любые идеи ? - person Dinesh; 07.08.2015
comment
Ошибка в AudioConverterFillComplexBuffer возникает только в iphone. не в айпадах - person Pablo Martinez; 01.03.2016