Кодирование PCM (CMSampleBufferRef) в AAC на iOS - Как установить частоту и битрейт?

Хочу кодировать PCM (CMSampleBufferRef(ы) в прямом эфире из AVCaptureAudioDataOutputSampleBufferDelegate) в AAC.

Когда первый CMSampleBufferRef прибывает, я устанавливаю оба (вход / выход) AudioStreamBasicDescription(s), "вне" согласно документации

AudioStreamBasicDescription inAudioStreamBasicDescription = *CMAudioFormatDescriptionGetStreamBasicDescription((CMAudioFormatDescriptionRef)CMSampleBufferGetFormatDescription(sampleBuffer));

AudioStreamBasicDescription outAudioStreamBasicDescription = {0}; // Always initialize the fields of a new audio stream basic description structure to zero, as shown here: ...
outAudioStreamBasicDescription.mSampleRate = 44100; // The number of frames per second of the data in the stream, when the stream is played at normal speed. For compressed formats, this field indicates the number of frames per second of equivalent decompressed data. The mSampleRate field must be nonzero, except when this structure is used in a listing of supported formats (see “kAudioStreamAnyRate”).
outAudioStreamBasicDescription.mFormatID = kAudioFormatMPEG4AAC; // kAudioFormatMPEG4AAC_HE does not work. Can't find `AudioClassDescription`. `mFormatFlags` is set to 0.
outAudioStreamBasicDescription.mFormatFlags = kMPEG4Object_AAC_SSR; // Format-specific flags to specify details of the format. Set to 0 to indicate no format flags. See “Audio Data Format Identifiers” for the flags that apply to each format.
outAudioStreamBasicDescription.mBytesPerPacket = 0; // The number of bytes in a packet of audio data. To indicate variable packet size, set this field to 0. For a format that uses variable packet size, specify the size of each packet using an AudioStreamPacketDescription structure.
outAudioStreamBasicDescription.mFramesPerPacket = 1024; // The number of frames in a packet of audio data. For uncompressed audio, the value is 1. For variable bit-rate formats, the value is a larger fixed number, such as 1024 for AAC. For formats with a variable number of frames per packet, such as Ogg Vorbis, set this field to 0.
outAudioStreamBasicDescription.mBytesPerFrame = 0; // The number of bytes from the start of one frame to the start of the next frame in an audio buffer. Set this field to 0 for compressed formats. ...
outAudioStreamBasicDescription.mChannelsPerFrame = 1; // The number of channels in each frame of audio data. This value must be nonzero.
outAudioStreamBasicDescription.mBitsPerChannel = 0; // ... Set this field to 0 for compressed formats.
outAudioStreamBasicDescription.mReserved = 0; // Pads the structure out to force an even 8-byte alignment. Must be set to 0.

а также AudioConverterRef,

AudioClassDescription audioClassDescription;
memset(&audioClassDescription, 0, sizeof(audioClassDescription));
UInt32 size;
NSAssert(AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders, sizeof(outAudioStreamBasicDescription.mFormatID), &outAudioStreamBasicDescription.mFormatID, &size) == noErr, nil);
uint32_t count = size / sizeof(AudioClassDescription);
AudioClassDescription descriptions[count];
NSAssert(AudioFormatGetProperty(kAudioFormatProperty_Encoders, sizeof(outAudioStreamBasicDescription.mFormatID), &outAudioStreamBasicDescription.mFormatID, &size, descriptions) == noErr, nil);
for (uint32_t i = 0; i < count; i++) {

    if ((outAudioStreamBasicDescription.mFormatID == descriptions[i].mSubType) && (kAppleSoftwareAudioCodecManufacturer == descriptions[i].mManufacturer)) {

        memcpy(&audioClassDescription, &descriptions[i], sizeof(audioClassDescription));

    }
}
NSAssert(audioClassDescription.mSubType == outAudioStreamBasicDescription.mFormatID && audioClassDescription.mManufacturer == kAppleSoftwareAudioCodecManufacturer, nil);
AudioConverterRef audioConverter;
memset(&audioConverter, 0, sizeof(audioConverter));
NSAssert(AudioConverterNewSpecific(&inAudioStreamBasicDescription, &outAudioStreamBasicDescription, 1, &audioClassDescription, &audioConverter) == 0, nil);

А потом я конвертирую каждый CMSampleBufferRef в необработанные данные AAC.

AudioBufferList inAaudioBufferList;
CMBlockBufferRef blockBuffer;
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBuffer, NULL, &inAaudioBufferList, sizeof(inAaudioBufferList), NULL, NULL, 0, &blockBuffer);
NSAssert(inAaudioBufferList.mNumberBuffers == 1, nil);

uint32_t bufferSize = inAaudioBufferList.mBuffers[0].mDataByteSize;
uint8_t *buffer = (uint8_t *)malloc(bufferSize);
memset(buffer, 0, bufferSize);
AudioBufferList outAudioBufferList;
outAudioBufferList.mNumberBuffers = 1;
outAudioBufferList.mBuffers[0].mNumberChannels = inAaudioBufferList.mBuffers[0].mNumberChannels;
outAudioBufferList.mBuffers[0].mDataByteSize = bufferSize;
outAudioBufferList.mBuffers[0].mData = buffer;

UInt32 ioOutputDataPacketSize = 1;

NSAssert(AudioConverterFillComplexBuffer(audioConverter, inInputDataProc, &inAaudioBufferList, &ioOutputDataPacketSize, &outAudioBufferList, NULL) == 0, nil);

NSData *data = [NSData dataWithBytes:outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize];

free(buffer);
CFRelease(blockBuffer);

inInputDataProc() реализация:

OSStatus inInputDataProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData)
{
    AudioBufferList audioBufferList = *(AudioBufferList *)inUserData;

    ioData->mBuffers[0].mData = audioBufferList.mBuffers[0].mData;
    ioData->mBuffers[0].mDataByteSize = audioBufferList.mBuffers[0].mDataByteSize;

    return  noErr;
}

Теперь data содержит мой необработанный AAC, который я помещаю в кадр ADTS с соответствующим заголовком ADTS, а последовательность этих кадров ADTS является воспроизводимым документом AAC.

Но я не понимаю этот код столько, сколько хочу. Вообще, я не понимаю аудио... Я просто написал его как-то, следуя блогам, форумам и документам, довольно много времени, и теперь оно работает, но я не знаю, почему и как изменить некоторые параметры. Итак, вот мои вопросы:

  1. Мне нужно использовать этот конвертер, когда кодер HW занят (AVAssetWriter). Вот почему я делаю SW конвертер через AudioConverterNewSpecific() и не AudioConverterNew(), Но сейчас настройка outAudioStreamBasicDescription.mFormatID = kAudioFormatMPEG4AAC_HE; не работает. Не могу найти AudioClassDescription, Даже если mFormatFlags установлен на 0. Что я теряю с помощью kAudioFormatMPEG4AAC (kMPEG4Object_AAC_SSR) над kAudioFormatMPEG4AAC_HE? Что я должен использовать для прямой трансляции? kMPEG4Object_AAC_SSR или же kMPEG4Object_AAC_Main?

  2. Как правильно изменить частоту дискретизации? Если я установлю outAudioStreamBasicDescription.mSampleRate например, до 22050 или 8000, воспроизведение звука похоже на замедление. Я установил индекс частоты дискретизации в заголовке ADTS для той же частоты, что и outAudioStreamBasicDescription.mSampleRate является.

  3. Как изменить битрейт? ffmpeg -i показывает эту информацию для произведенного aac:Stream #0:0: Audio: aac, 44100 Hz, mono, fltp, 64 kb/s, Как изменить его до 16 кбит / с например? Битрейт уменьшается, так как я уменьшаю частоту, но я считаю, что это не единственный способ? И воспроизведение повреждается уменьшением частоты, как я уже упоминал в 2 в любом случае.

  4. Как рассчитать размер buffer? Теперь я установил его на uint32_t bufferSize = inAaudioBufferList.mBuffers[0].mDataByteSize; как я считаю, сжатый формат не будет больше, чем несжатый... Но не слишком ли это много?

  5. Как установить ioOutputDataPacketSize должным образом? Если я правильно понимаю документацию, я должен установить ее как UInt32 ioOutputDataPacketSize = bufferSize / outAudioStreamBasicDescription.mBytesPerPacket; но mBytesPerPacket равно 0. Если я установлю его на 0, AudioConverterFillComplexBuffer() возвращает ошибку. Если я установлю его на 1, это работает, но я не знаю почему...

  6. В inInputDataProc() Есть 3 "выходных" параметра. Я установил только ioData, Должен ли я также установить ioNumberDataPackets а также outDataPacketDescription? Почему и как?

1 ответ

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

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