Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extract iPod Library raw PCM samples and play with sound effects

I am trying to extract raw PCM samples from an MP3 in the iPod Library so that I can play the song and manipulate the pitch, tempo, and apply sound effects (such as filters). I have already gone down the route of AVPlayer and AVAudioPlayer which both do not allow very much control over the playback at all.

The code below is as far as I have gotten with this. I am at a point now where I do not know what to do with the CMSampleBufferRef's in my while loop because I do not know which framework to use in order to playback the audio and apply such effects.

Any idea what would be the best approach to achieve this? I have looked at cases where the file is converted using an AVAssetWriter but this is not going to cut it for me because the process is too time consuming. Surely I can just read the PCM samples into memory for playback without having to write them to disk first?

NB: I know the code below references an mp3 within the project but I am aware that this approach will work the same as if I were pulling an NSURL from the MPMediaPropertyAssetURL


-(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];

}

like image 888
Dino Avatar asked Jan 25 '11 17:01

Dino


2 Answers

I'm doing something similar in my own code. The following method returns some NSData for a AVURLAsset:

- (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];
}

I would recommend doing this on a background thread because it's time consuming.

A drawback to this method is that the whole song is loaded into memory, which is of course limited.

like image 151
Tom Irving Avatar answered Sep 28 '22 12:09

Tom Irving


Additionally to Tom Irving's Answer, I suggest replacing

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

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

with

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

which avoids double handling the samples, and reduces memory usage overhead.

alternatively, you can wrap [NSMutableData dataWithLength:length] in an auto release pool as demonstrated in this answer to an unrelated but similar question.

like image 37
unsynchronized Avatar answered Sep 28 '22 10:09

unsynchronized