使用AVAssetReader读取音频样本

17

如何通过AVAssetReader读取音频样本?我已经找到了使用AVAssetReader进行复制或混合的示例,但是这些循环始终由AVAssetWriter循环控制。是否可以仅创建一个AVAssetReader并通过它阅读,获取每个样本并将每个音频样本的int32放入数组中?

谢谢。

3个回答

27

为了进一步解释@amrox的答案,你可以从CMBlockBufferRef获取一个AudioBufferList,例如:

CMItemCount numSamplesInBuffer = CMSampleBufferGetNumSamples(buffer);

AudioBufferList audioBufferList;

CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(
      buffer,
      NULL,
      &audioBufferList,
      sizeof(audioBufferList),
      NULL,
      NULL,
      kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment,
      &buffer
    );

for (int bufferCount=0; bufferCount < audioBufferList.mNumberBuffers; bufferCount++) {
  SInt16* samples = (SInt16 *)audioBufferList.mBuffers[bufferCount].mData;
  for (int i=0; i < numSamplesInBuffer; i++) {
    // amplitude for the sample is samples[i], assuming you have linear pcm to start with
  }
}

//Release the buffer when done with the samples 
//(retained by CMSampleBufferGetAudioBufferListWithRetainedblockBuffer)
CFRelease(buffer); 

非常感谢!那正是我缺少的部分。 - Eric Christensen
当我使用这种方法时,声音会变得很卡顿。但是当我使用@amrox的方法,该方法使用char*,在执行samples[i] *= 0.5;之类的音量调整操作时一切正常。有什么原因吗? - Dex
能否获取样本并将其写入另一个音频文件中?我正在尝试反转音频文件。请查看我的问题。任何帮助将不胜感激。 - Chan Jing Hong
那么你如何从扬声器播放该音频呢? - omarojo
1
在Swift 4中,当我在for循环中声明变量“samples”时,会出现错误“Type 'AudioBuffer' has no subscript members”。 - JCutting8

18
AVAssetReader *reader   = [[AVAssetReader alloc] initWithAsset:asset error:&error];
AVAssetTrack  *track    = [[asset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
NSDictionary  *settings = @{ AVFormatIDKey : [NSNumber numberWithInt:kAudioFormatLinearPCM] };

AVAssetReaderTrackOutput *readerOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:track
                                                                                    outputSettings:settings];

[reader addOutput:readerOutput]; 
[reader startReading];

CMSampleBufferRef sample = [readerOutput copyNextSampleBuffer];

while ( sample )
{
   sample = [readerOutput copyNextSampleBuffer];

    if ( ! sample )
    {
       continue;
    }

    CMBlockBufferRef buffer = CMSampleBufferGetDataBuffer(sample);

    size_t  lengthAtOffset;
    size_t  totalLength;
    char   *data;

    if ( CMBlockBufferGetDataPointer( buffer, 0, &lengthAtOffset, &totalLength, &data ) != noErr )
    {
        NSLog(@"error!");
        break;
    }

    // do something with data...

    CFRelease(sample);
}

2
感谢@amrox。这确实有所帮助。但我认为它缺少添加输出和开始读取的部分。这可以通过以下方式添加: [reader addOutput:readerOutput]; [reader startReading];在创建readerOutput之后,对吗?一旦我添加了这个,它就不会读取整个音频文件,而只是一两次。这是它应该工作的方式吗?如果是这样,我不明白这如何让我获得单个样本。音频文件中的每个样本都是一个int,其数量和范围由位深度和采样率定义。我怎样才能获得这些int?再次感谢。 - Eric Christensen
他们在那个char*数据中。如果你想要更多的控制,请在设置字典中设置更多的键:AVNumberOfChannelsKey,AVLinearPCMBitDepthKey。不要忘记把赏金颁给amrox。 - Rhythmic Fistman

7
这里的答案并不是通用的。当需要调整AudioBufferList的大小时,调用CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer可能会失败,例如在使用非交错样本时。
正确的方法是调用CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer两次。第一次调用查询所需的AudioBufferList大小,第二次调用填充实际的AudioBufferList
size_t bufferSize = 0;
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(
    sampleBuffer,
    &bufferSize,
    NULL,
    0,
    NULL,
    NULL,
    0,
    NULL
);

AudioBufferList *bufferList = malloc(bufferSize);
CMBlockBufferRef blockBuffer = NULL;
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(
    sampleBuffer,
    NULL,
    bufferList,
    bufferSize,
    NULL,
    NULL,
    kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment,
    &blockBuffer
);

// handle audio here

free(bufferList);
CFRelease(blockBuffer);

在一个真实的世界示例中,您必须进行错误处理,并且不应该为每个帧分配内存,而是缓存AudioBufferList

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接