Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AVAssetWriter How to write down-sampled/compressed m4a/mp3 files

I'm trying to take a local m4a or mp3 file and compress/down-sample this file (for the purposes of making a smaller file).

Originally, I was using the AVAssetExportSession to export an AVAsset to a temp directory, but I didn't have any control over compression/down-sampling (you can only use presets, which of them, only .wav file formats support quality degradation).

Then, following several examples here on SO, I tried using AVAssetReader/AVAssetWriter to preform this 'export'.

I create my reader/writer as such:

NSString *exportPath = [NSHomeDirectory() stringByAppendingPathComponent:@"out.m4a"];

NSURL *exportURL = [NSURL fileURLWithPath:outPath];

// reader
NSError *readerError = nil;
AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:asset
                                                       error:&readerError];

AVAssetTrack *track = [[asset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];    
AVAssetReaderTrackOutput *readerOutput = [[AVAssetReaderTrackOutput alloc] initWithTrack:track
                                                                          outputSettings:nil];
[reader addOutput:readerOutput];

// writer
NSError *writerError = nil;
AVAssetWriter *writer = [[AVAssetWriter alloc] initWithURL:exportURL
                                                  fileType:AVFileTypeAppleM4A
                                                     error:&writerError];

AudioChannelLayout channelLayout;
memset(&channelLayout, 0, sizeof(AudioChannelLayout));
channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;

// use different values to affect the downsampling/compression
NSDictionary *outputSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                [NSNumber numberWithInt: kAudioFormatMPEG4AAC], AVFormatIDKey,
                                [NSNumber numberWithFloat:44100.0], AVSampleRateKey,
                                [NSNumber numberWithInt:2], AVNumberOfChannelsKey,
                                [NSNumber numberWithInt:128000], AVEncoderBitRateKey,
                                [NSData dataWithBytes:&channelLayout length:sizeof(AudioChannelLayout)], AVChannelLayoutKey,
                                nil];

AVAssetWriterInput *writerInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio
                                                                 outputSettings:outputSettings];
[writerInput setExpectsMediaDataInRealTime:NO];
[writer addInput:writerInput];

And then I start writing...

[writer startWriting];
[writer startSessionAtSourceTime:kCMTimeZero];

[reader startReading];
dispatch_queue_t mediaInputQueue = dispatch_queue_create("mediaInputQueue", NULL);
[writerInput requestMediaDataWhenReadyOnQueue:mediaInputQueue usingBlock:^{

    NSLog(@"Asset Writer ready : %d", writerInput.readyForMoreMediaData);
    while (writerInput.readyForMoreMediaData) {
        CMSampleBufferRef nextBuffer;
        if ([reader status] == AVAssetReaderStatusReading && (nextBuffer = [readerOutput copyNextSampleBuffer])) {
            if (nextBuffer) {
                NSLog(@"Adding buffer");
                [writerInput appendSampleBuffer:nextBuffer];
            }
        } else {
            [writerInput markAsFinished];

            switch ([reader status]) {
                case AVAssetReaderStatusReading:
                    break;
                case AVAssetReaderStatusFailed:
                    [writer cancelWriting];
                    break;
                case AVAssetReaderStatusCompleted:
                    NSLog(@"Writer completed");
                    [writer endSessionAtSourceTime:asset.duration];
                    [writer finishWriting];

                    NSData *data = [NSData dataWithContentsOfFile:exportPath];
                    NSLog(@"Data: %@", data);
                    break;
            }
            break;
        }
    }
}];

When I'm done writing, the data i've supposedly written to the exportURL is null, and the writer reports a successful completion. Any ideas what might be going wrong?

Update The writer status after calling appendSampleBuffer: is AVAssetWriterStatusFailed, though the readers status is successful, and seems to read through the entire file.

like image 386
Dfowj Avatar asked Oct 24 '12 20:10

Dfowj


1 Answers

I came across the solution:

Using NSHomeDirectory() in NSString *exportPath = [NSHomeDirectory() stringByAppendingPathComponent:@"out.m4a"] was causing the writer to not be able to create the file. Not exactly sure why, or what I would need to do to allow this to work, but changing NSHomeDirectiory() to NSTemporaryDirectory() has solved my problems in the meantime.

like image 106
Dfowj Avatar answered Oct 07 '22 17:10

Dfowj