【问题标题】:Extract iPod Library raw PCM samples and play with sound effects提取 iPod Library 原始 PCM 样本并播放音效
【发布时间】:2011-06-15 08:41:27
【问题描述】:

我正在尝试从 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]; }

【问题讨论】:

    标签: iphone objective-c mp3 ipod pcm


    【解决方案1】:

    我正在自己的代码中做类似的事情。以下方法为 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];
    }
    

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

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

    【讨论】:

    • 优秀。这让我成功了一半。我一直在对音频框架进行一些研究,似乎唯一适合我要求的框架是 OpenAL。由于学习曲线陡峭,这有点令人反感,但我可以将 NSData 与 Finch 之类的东西一起使用吗?
    • 我自己没用过Finch,所以不确定它是否直接接受NSData。
    • 仅供参考:我试过了,我得到了 EXC_BAD_ACCESS... 我可以通过在执行for 之前检查CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer 返回的 OSStatus 来删除它。
    【解决方案2】:

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

           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] 包装在自动释放池中,如 in this answer 演示的一个不相关但类似的问题。

    【讨论】:

    • 太棒了——当我把它放在一起时,我只是想让它工作,还没有开始优化。我最大的烦恼是,在将整首歌读入内存之前,我似乎无法找到一种方法来获取歌曲的完整大小(以字节为单位)。
    • 显然我可以读一次,无需复制任何内容,只需获取每个样本的长度并将它们相加,但它似乎太乱了。
    【解决方案3】:

    我想你想把它放在那里以确保它的 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];
    

    【讨论】:

      【解决方案4】:

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

      float songTimeInSeconds = CMTimeGetSeconds(songAsset.duration);
          int songMinutes = (int)(songTimeInSeconds/60.);
          int songSeconds = (int)(songTimeInSeconds - 60.0*songMinutes);
      

      【讨论】:

        猜你喜欢
        • 2014-10-11
        • 1970-01-01
        • 1970-01-01
        • 2018-09-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-12-28
        • 2011-04-21
        相关资源
        最近更新 更多