Как добавить в AudioBufferList с помощью AVAssetReader?

Я работаю над чтением аудиоактива с помощью AVAssetReader, чтобы впоследствии воспроизвести аудио с помощью AUGraph с обратным вызовом AudioUnit. У меня работает обратный вызов AUGraph и AudioUnit, но он читает файлы с диска, и если файл слишком большой, он займет слишком много памяти и приведет к сбою приложения. Поэтому я вместо этого читаю актив напрямую и только ограниченного размера. Затем я буду управлять им как двойным буфером и получать от AUGraph то, что ему нужно, когда ему это нужно.

(Примечание: я хотел бы знать, могу ли я использовать службы Audio Queue Services и по-прежнему использовать AUGraph с обратным вызовом AudioUnit, чтобы память управлялась для меня платформами iOS.)

Моя проблема в том, что я плохо разбираюсь в массивах, структурах и указателях в C. Часть, где мне нужна помощь, - это взять отдельный AudioBufferList, который содержит один AudioBuffer, и добавить эти данные в другой AudioBufferList, который содержит все данные, которые будут использованы позже. Я считаю, что мне нужно использовать memcpy, но не ясно, как его использовать или даже инициализировать AudioBufferList для моих целей. Я использую MixerHost для справки, который является образцом проекта от Apple, который читается в файл с диска.

Я загрузил свою незавершенную работу, если вы хотите загрузить ее в Xcode. Я понял большую часть того, что мне нужно для этого, и как только я соберу все данные в одном месте, я буду готов к работе.

Пример проекта: MyAssetReader.zip

В заголовке вы можете видеть, что я объявляю bufferList как указатель на структуру.

@interface MyAssetReader : NSObject {
    BOOL reading;
    signed long sampleTotal;
    Float64 totalDuration;

    AudioBufferList *bufferList; // How should this be handled?
}

Затем я выделяю буферList таким образом, в значительной степени заимствуя из MixerHost...

UInt32 channelCount = [asset.tracks count];

if (channelCount > 1) {
    NSLog(@"We have more than 1 channel!");
}

bufferList = (AudioBufferList *) malloc (
                                         sizeof (AudioBufferList) + sizeof (AudioBuffer) * (channelCount - 1)
                                         );

if (NULL == bufferList) {NSLog (@"*** malloc failure for allocating bufferList memory"); return;}

// initialize the mNumberBuffers member
bufferList->mNumberBuffers = channelCount;

// initialize the mBuffers member to 0
AudioBuffer emptyBuffer = {0};
size_t arrayIndex;
for (arrayIndex = 0; arrayIndex < channelCount; arrayIndex++) {
    // set up the AudioBuffer structs in the buffer list
    bufferList->mBuffers[arrayIndex] = emptyBuffer;
    bufferList->mBuffers[arrayIndex].mNumberChannels  = 1;
    // How should mData be initialized???
    bufferList->mBuffers[arrayIndex].mData            = malloc(sizeof(AudioUnitSampleType)); 
}

Наконец, я перебираю чтение.

int frameCount = 0;

CMSampleBufferRef nextBuffer;
while (assetReader.status == AVAssetReaderStatusReading) {
    nextBuffer = [assetReaderOutput copyNextSampleBuffer];

    AudioBufferList  localBufferList;
    CMBlockBufferRef blockBuffer;    
    CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(nextBuffer, NULL, &localBufferList, sizeof(localBufferList), NULL, NULL, 
                                                            kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &blockBuffer);

    // increase the number of total bites
    bufferList->mBuffers[0].mDataByteSize += localBufferList.mBuffers[0].mDataByteSize;

    // carefully copy the data into the buffer list
    memcpy(bufferList->mBuffers[0].mData + frameCount, localBufferList.mBuffers[0].mData, sizeof(AudioUnitSampleType));

    // get information about duration and position
    //CMSampleBufferGet
    CMItemCount sampleCount = CMSampleBufferGetNumSamples(nextBuffer);
    Float64 duration = CMTimeGetSeconds(CMSampleBufferGetDuration(nextBuffer));
    Float64 presTime = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(nextBuffer));

    if (isnan(duration)) duration = 0.0;
    if (isnan(presTime)) presTime = 0.0;

    //NSLog(@"sampleCount: %ld", sampleCount);
    //NSLog(@"duration: %f", duration);
    //NSLog(@"presTime: %f", presTime);

    self.sampleTotal += sampleCount;
    self.totalDuration += duration;
    frameCount++;

    free(nextBuffer);
}

Я не уверен в том, что я обрабатываю mDataByteSize и mData, особенно с memcpy. Поскольку mData является пустым указателем, это дополнительная сложная область.

    memcpy(bufferList->mBuffers[0].mData + frameCount, localBufferList.mBuffers[0].mData, sizeof(AudioUnitSampleType));

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

  • Поскольку указатель void равен всего 1, а не размеру указателя для AudioUnitSampleType, мне может потребоваться умножить его также на sizeof(AudioUnitSampleType), чтобы получить memcpy в правильном положении.
  • Возможно, я неправильно использую malloc для подготовки mData, но, поскольку я не уверен, сколько кадров будет, я не уверен, что делать, чтобы его инициализировать.

В настоящее время, когда я запускаю это приложение, оно завершает эту функцию с недопустимым указателем для bufferList.

Я ценю вашу помощь в том, что вы помогли мне лучше понять, как управлять AudioBufferList.


person Brennan    schedule 10.04.2011    source источник
comment
Я узнал больше об указателях void и о том, как использовать malloc и memcpy, которые, кажется, являются ключевой частью понимания структуры AudioBufferList. Мне нужно будет знать способ хранения значений в динамическом массиве в C. Я читаю, что такие функции, возможно, были добавлены с C99. Я продолжаю читать об этом.   -  person Brennan    schedule 11.04.2011
comment
Я думаю, что я буду накапливать значения из AudioBuffer в массив, который увеличивается с помощью ralloc, но я могу сделать это с блоками по 100, поэтому realloc не вызывается при каждой итерации. Затем я могу создать массив динамического размера после цикла, использовать memcpy для заполнения этого массива накопленными значениями, а затем установить mData в буфере с этим массивом.   -  person Brennan    schedule 11.04.2011


Ответы (1)


Я придумал свой собственный ответ. Я решил использовать объект NSMutableData, который позволяет мне добавлять байты из CMSampleBufferRef после вызова CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer для получения AudioBufferList.

        [data appendBytes:localBufferList.mBuffers[0].mData length:localBufferList.mBuffers[0].mDataByteSize];

После завершения цикла чтения у меня есть все данные в моем объекте NSMutableData. Затем я создаю и заполняю AudioBufferList таким образом.

audioBufferList = (AudioBufferList *)malloc(sizeof(AudioBufferList));
if (NULL == audioBufferList) {
    NSLog (@"*** malloc failure for allocating audioBufferList memory");
    [data release];
    return;
}
audioBufferList->mNumberBuffers = 1;
audioBufferList->mBuffers[0].mNumberChannels = channelCount;
audioBufferList->mBuffers[0].mDataByteSize = [data length];
audioBufferList->mBuffers[0].mData = (AudioUnitSampleType *)malloc([data length]);
if (NULL == audioBufferList->mBuffers[0].mData) {
    NSLog (@"*** malloc failure for allocating mData memory");
    [data release];
    return;
}
memcpy(audioBufferList->mBuffers[0].mData, [data mutableBytes], [data length]);
[data release];

Я был бы признателен за небольшой обзор кода о том, как я использую malloc для создания структуры и ее заполнения. Время от времени я получаю сообщение об ошибке EXC_BAD_ACCESS, но пока не могу точно определить, где ошибка. Поскольку я использую malloc в структуре, мне не нужно его нигде сохранять. Я вызываю «free», чтобы освободить дочерние элементы внутри структуры и, наконец, саму структуру везде, где я использую malloc.

person Brennan    schedule 21.04.2011