8

我正在尝试从 iPod 库中的 MP3 中提取原始 PCM 样本,以便我可以播放歌曲并操纵音高、速度和应用音效(例如过滤器)。我已经走上了 AVPlayer 和 AVAudioPlayer 的路线,它们都不允许对播放进行太多控制。

下面的代码是据我所知。我现在不知道如何处理我的 while 循环中的 CMSampleBufferRef,因为我不知道使用哪个框架来播放音频并应用此类效果。

知道实现这一目标的最佳方法是什么吗?我已经查看了使用 AVAssetWriter 转换文件的情况,但这对我来说不会削减它,因为该过程太耗时。当然,我可以将 PCM 样本读入内存进行播放,而无需先将它们写入磁盘?

注意:我知道下面的代码引用了项目中的 mp3,但我知道这种方法的工作方式与我从 MPMediaPropertyAssetURL 中提取 NSURL 相同


-(IBAction)loadTrack:(id)sender {

 NSString *songPath = [[NSBundle mainBundle] pathForResource:@"Smooth_Sub Focus_192" ofType:@"mp3"];
 NSURL *assetURL = [[NSURL alloc] initFileURLWithPath:songPath];

 AVURLAsset *songAsset = [AVURLAsset URLAssetWithURL:assetURL options:nil];

 NSError *assetError = nil;
 AVAssetReader *assetReader = [[AVAssetReader assetReaderWithAsset:songAsset
                error:&assetError] retain];
 if (assetError) {
  NSLog (@"Error: %@", assetError);
  return;
 }

 AVAssetReaderOutput *assetReaderOutput = [[AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:songAsset.tracks
                           audioSettings: nil] retain];
 if (![assetReader canAddOutput:assetReaderOutput]) {
  NSLog (@"Incompatible Asser Reader Output");
  return;
 }

 [assetReader addOutput: assetReaderOutput];
 [assetReader startReading];

 CMSampleBufferRef nextBuffer;
 while (nextBuffer = [assetReaderOutput copyNextSampleBuffer]) {
  /* What Do I Do Here? */
 }

 [assetReader release];
 [assetReaderOutput release];

}

4

4 回答 4

9

我在自己的代码中做类似的事情。以下方法为 AVURLAsset 返回一些 NSData:

- (NSData *)extractDataForAsset:(AVURLAsset *)songAsset {

    NSError * error = nil;
    AVAssetReader * reader = [[AVAssetReader alloc] initWithAsset:songAsset error:&error];

    AVAssetTrack * songTrack = [songAsset.tracks objectAtIndex:0];
    AVAssetReaderTrackOutput * output = [[AVAssetReaderTrackOutput alloc] initWithTrack:songTrack outputSettings:nil];
    [reader addOutput:output];
    [output release];

    NSMutableData * fullSongData = [[NSMutableData alloc] init];
    [reader startReading];

    while (reader.status == AVAssetReaderStatusReading){

        AVAssetReaderTrackOutput * trackOutput = (AVAssetReaderTrackOutput *)[reader.outputs objectAtIndex:0];
        CMSampleBufferRef sampleBufferRef = [trackOutput copyNextSampleBuffer];

        if (sampleBufferRef){
            CMBlockBufferRef blockBufferRef = CMSampleBufferGetDataBuffer(sampleBufferRef);

            size_t length = CMBlockBufferGetDataLength(blockBufferRef);
            UInt8 buffer[length];
            CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, buffer);

            NSData * data = [[NSData alloc] initWithBytes:buffer length:length];
            [fullSongData appendData:data];
            [data release];

            CMSampleBufferInvalidate(sampleBufferRef);
            CFRelease(sampleBufferRef);
        }
    }

    if (reader.status == AVAssetReaderStatusFailed || reader.status == AVAssetReaderStatusUnknown){
        // Something went wrong. Handle it.
    }

    if (reader.status == AVAssetReaderStatusCompleted){
        // You're done. It worked.
    }

    [reader release];

    return [fullSongData autorelease];
}

我建议在后台线程上执行此操作,因为它很耗时。

这种方法的一个缺点是整首歌曲都加载到内存中,这当然是有限的。

于 2011-01-25T18:15:39.993 回答
5

除了汤姆欧文的回答,我建议更换

       UInt8 buffer[length];
       CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, buffer);

        NSData * data = [[NSData alloc] initWithBytes:buffer length:length];

        NSMutableData * data = [[NSMutableData alloc] initWithLength:length];
        CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, data.mutableBytes);

这避免了双重处理样本,并减少了内存使用开销。

或者,您可以将 [NSMutableData dataWithLength:length] 包装在自动释放池中,如this answer to an unrelated but similar question所示。

于 2011-09-21T19:40:43.603 回答
2

我认为您希望将其放在那里以确保其 PCM...

NSDictionary* outputSettingsDict = [[NSDictionary alloc] initWithObjectsAndKeys:

                        [NSNumber numberWithInt:kAudioFormatLinearPCM],AVFormatIDKey,
                   //     [NSNumber numberWithInt:44100.0],AVSampleRateKey, /*Not Supported*/
                   //     [NSNumber numberWithInt: 2],AVNumberOfChannelsKey,    /*Not Supported*/

                        [NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,
                        [NSNumber numberWithBool:NO],AVLinearPCMIsBigEndianKey,
                        [NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,
                        [NSNumber numberWithBool:NO],AVLinearPCMIsNonInterleaved,

                        nil];

AVAssetReaderTrackOutput* output = [[AVAssetReaderTrackOutput alloc] initWithTrack:songTrack outputSettings:outputSettingsDict];
于 2011-04-08T23:20:35.547 回答
0

对于歌曲时长,我相信您可以这样简单地查询歌曲资产:

float songTimeInSeconds = CMTimeGetSeconds(songAsset.duration);
    int songMinutes = (int)(songTimeInSeconds/60.);
    int songSeconds = (int)(songTimeInSeconds - 60.0*songMinutes);
于 2012-08-22T00:31:59.467 回答