3

我一直在使用 AVAssetReader 读取音频资产,以便以后可以使用带有 AudioUnit 回调的 AUGraph 播放音频。我有 AUGraph 和 AudioUnit 回调工作,但它从磁盘读取文件,如果文件太大,它会占用太多内存并使应用程序崩溃。所以我转而直接阅读资产,并且只有有限的大小。然后,我会将其作为双缓冲区进行管理,并在需要时获取 AUGraph 所需的内容。

(注意:我很想知道我是否可以使用音频队列服务并且仍然使用带有 AudioUnit 回调的 AUGraph,以便 iOS 框架为我管理内存。)

我的问题是我对 C 中的数组、结构和指针没有很好的理解。我需要帮助的部分是将保存在单个 AudioBuffer 上的单个 AudioBufferList 并将该数据添加到另一个保存所有 AudioBufferList 的 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?
}

然后我以这种方式分配bufferList,主要是从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。

4

1 回答 1

3

我想出了我自己的答案。我决定使用一个 NSMutableData 对象,它允许我在调用 CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer 后从 CMSampleBufferRef 中追加字节以获取 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,我不应该在任何地方保留它。我确实调用“自由”来释放结构内的子元素,最后是结构本身在我使用 malloc 的任何地方。

于 2011-04-21T07:41:21.860 回答