Audio file concatenation on iPhone (m4a) fails

I am trying to make one long sound file by concatenating several smaller sound files - they are all in m4a format and the result should also be in m4a format.

Here is the code (the audFiles array contains the names of the audio files to attach). Note. I only print file sizes for peace of mind ...

CMTime nextClipStartTime = kCMTimeZero;
AVMutableComposition *combinedSounds = [AVMutableComposition composition];
NSString *tempDir = NSTemporaryDirectory();
NSArray *audFiles;

for (int i = 0; i < [audFiles count]; i++)
{
    NSString *addSound = [tempDir stringByAppendingString:audFiles[i]];
    if ([[NSFileManager defaultManager] fileExistsAtPath:addSound] == YES)
    {
        NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:addSound error:nil];
        if (fileAttributes != nil)
        {
            NSString *fileSize = [fileAttributes objectForKey:NSFileSize];
            NSLog(@"file %@ %@", addSound, fileSize);
            NSURL *assetURL = [[NSURL alloc] initFileURLWithPath:addSound];

            AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:assetURL options:nil];
            if (asset != nil)
            {
                CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);
                NSLog(@"asset %d length %lld", i, asset.duration.value);
                if (asset.duration.value > 0)
                {
                    AVAssetTrack *audTrack = [asset tracksWithMediaType:AVMediaTypeAudio][0];
                    AVMutableCompositionTrack *audioTrack = [combinedSounds addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
                    if ([audioTrack insertTimeRange:timeRange ofTrack:audTrack atTime:nextClipStartTime error:nil] == NO)
                    {
                        NSLog(@"insertTimeRange %d FAILED", i);
                    }
                    nextClipStartTime = CMTimeAdd(nextClipStartTime, asset.duration);
                    nextClipStartTime = CMTimeAdd(nextClipStartTime, CMTimeMake(0.1, 1));
                    NSLog(@"nextClipStartTime %lld", nextClipStartTime.value);
                }
            }
        }
    }
}

NSString *finalSound = [tempDir stringByAppendingString:@"result.m4a"];
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:combinedSounds presetName:AVAssetExportPresetPassthrough];

 NSString *exported = [tempDir stringByAppendingString:finalSound];
[[NSFileManager defaultManager] removeItemAtPath:exported error:nil];
NSURL *exportedURL = [[NSURL alloc] initFileURLWithPath:exported];
exportSession.outputURL = exportedURL;
exportSession.shouldOptimizeForNetworkUse = YES;
exportSession.outputFileType = AVFileTypeAppleM4A;

[exportSession exportAsynchronouslyWithCompletionHandler:^{
    switch (exportSession.status)
    {
        case AVAssetExportSessionStatusFailed:
        {
            NSLog(@"exportSession FAIL");
            break;
        }
        case AVAssetExportSessionStatusCompleted:
        {
            NSLog(@"exportSession SUCCESS");
        }
    }
}];

      

"exportSession SUCCESS" is reported and the exported file exists and can be played, but it contains only the first of the concatenated files.

Any ideas what I am doing wrong?

Thank.

+3


source to share


1 answer


Step 1 :

Add AVFoundation.Framework

and CoreMedia.Framework

to your project

Step 2 :

#import <AVFoundation/AVFoundation.h>

in your .h

file

Step 3 :

Write following method in your .m file



Step 4 :

Call this function from ViewDidAppear to create Merged Audio file.

// Get Asset Detail
- (AVAsset *)getAvAssetForFile:(NSString *)fileName andType:(NSString *)fileType
{
    NSString *URLPath = [[NSBundle mainBundle] pathForResource:fileName ofType:fileType];
    NSURL *assetURL = [NSURL fileURLWithPath:URLPath];
    AVAsset *asset = [AVAsset assetWithURL:assetURL];
    return asset;
}

- (void)mergeAudioFiles
{
    // `An Array with List of Audio Clips to be Merged, they could be of different type also`
    NSMutableArray *listOfAudio = [[NSMutableArray alloc] initWithCapacity:0];

    // `Create AVAsset Object from your audio files and add to array`

    [listOfAudio addObject:[self getAvAssetForFile:@"audioFile1" andType:@"caf"]];
    [listOfAudio addObject:[self getAvAssetForFile:@"audioFile2" andType:@"caf"]];
    [listOfAudio addObject:[self getAvAssetForFile:@"audioFile3" andType:@"aiff"]];

    AVMutableComposition *composition = [AVMutableComposition composition];
    CMTime current = kCMTimeZero;
    NSError *compositionError = nil;
    for(AVAsset *asset in listOfAudio) {
        BOOL result = [composition insertTimeRange:CMTimeRangeMake(kCMTimeZero, [asset duration])
                                           ofAsset:asset
                                            atTime:current
                                             error:&compositionError];
        if(!result) {
            if(compositionError) {
                // manage the composition error case                
                NSLog(@"Error in Merging : %@",[compositionError debugDescription]);
            }
        } else {
            current = CMTimeAdd(current, [asset duration]);
        }
    }

    AVAssetExportSession *exporter = [[AVAssetExportSession alloc]
                                      initWithAsset: composition
                                      presetName: AVAssetExportPresetAppleM4A];

    exporter.outputFileType = @"com.apple.m4a-audio";

    // Set Output FileName
    NSString *fileName = @"myAudioTrack";
    NSString *exportFile = [DOC_DIR_PATH stringByAppendingFormat: @"/%@.m4a", fileName];

    // set up export
    NSURL *exportURL = [NSURL fileURLWithPath:exportFile];
    exporter.outputURL = exportURL;

    // do the export
    [exporter exportAsynchronouslyWithCompletionHandler:^{
        int exportStatus = exporter.status;
        switch (exportStatus) {
            case AVAssetExportSessionStatusFailed: NSLog (@"AVAssetExportSessionStatusFailed: %@", exporter.debugDescription); break;
            case AVAssetExportSessionStatusCompleted: NSLog (@"AVAssetExportSessionStatusCompleted"); break;
            case AVAssetExportSessionStatusUnknown: NSLog (@"AVAssetExportSessionStatusUnknown"); break;
            case AVAssetExportSessionStatusExporting: NSLog (@"AVAssetExportSessionStatusExporting"); break;
            case AVAssetExportSessionStatusCancelled: NSLog (@"AVAssetExportSessionStatusCancelled"); break;
            case AVAssetExportSessionStatusWaiting: NSLog (@"AVAssetExportSessionStatusWaiting"); break;
            default:  NSLog (@"didn't get export status"); break;
        }
    }];    
}

      

+1


source







All Articles