1
votes

insertTimeRange:ofTrack:atTime:error: of class AVMutableCompositionTrack is returning NO and the error "The operation could not be completed" on the iPhone (but not the simulator). The error message is unhelpful and I'm stumped trying to come up with a reason for the error, much less a workaround. Any help would be appreciated.

In the example code below, all I'm doing is attempting to add an audio clip to an AVMutableCompositionTrack. Here's what's going on in the code:

  1. AVMutableComposition and AVMutableCompositionTrack objects are created in init.

  2. The class method addPhrase:atTime: is called to add an audio clip to the composition track.

  3. The function which does the actual insertion is addAudioClip:atTime:.

  4. Helper function audioClipWithName:ofType loads the requested audio file into an AVAssetTrack.

--> NSLOG OUTPUT:

insertTimeRange atTime=0.0 range=(0.0, 2.1) compositionTrack= ----------ERROR! Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSLocalizedDescription=The operation could not be completed, NSUnderlyingError=0x16fc1c90 {Error Domain=NSOSStatusErrorDomain Code=-12780 "(null)"}, NSLocalizedFailureReason=An unknown error occurred (-12780)}

--> SAMPLE CODE:

@interface MyAudioPlayer()

@property (strong, nonatomic) AVMutableComposition *composition;
@property (strong, nonatomic) AVMutableCompositionTrack *compositionTrack;

@end


@implementation MyAudioPlayer

- (id) init
{
    self = [super init];
    if (self != nil) {
        self.composition = [AVMutableComposition composition];
        self.compositionTrack = [self.composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
    }
    return self;
}

// Add the phrase into compositionAudioTrack at the given time.
- (void) addPhrase:(NSString *)phrase atTime:(CMTime)atTime
{
    // Add the phrase to the audio.
    AVAssetTrack *audioClip = [self audioClipWithName:phrase ofType:@"mp3"];
    // Add the phrase to the audio track.
    [self addAudioClip:audioClip atTime];
}

// Add the given audio clip to the composed audio at the given time.
- (void) addAudioClip:(AVAssetTrack *)audioClip atTime:(CMTime)atTime
{
    NSError *error = [[NSError alloc] init];
    CMTimeRange audioClipRange = CMTimeRangeMake(kCMTimeZero, audioClip.timeRange.duration);
    BOOL success = [self.compositionTrack insertTimeRange:audioClipRange ofTrack:audioClip atTime:atTime error:&error];
    NSLog(@"insertTimeRange atTime=%.1f range=(%.1f, %.1f) compositionTrack=%@\n", CMTimeGetSeconds(atTime), CMTimeGetSeconds(audioClipRange.start), CMTimeGetSeconds(audioClipRange.duration), self.compositionTrack);
    if (!success) {
        NSLog(@"----------ERROR! %@", error);
    }
}

// Return an audio clip with the given name and type.
- (AVAssetTrack *) audioClipWithName:(NSString *)name ofType:(NSString *)type
{
    NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:name ofType:type];
    NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:soundFilePath];
    AVAsset *avAsset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
    AVAssetTrack *audioClip = [[avAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
    return audioClip;
}

@end
1

1 Answers

5
votes

Your problem lies in

- (AVAssetTrack *) audioClipWithName:(NSString *)name ofType:(NSString *)type;

It lets the track's owning AVAsset go out of scope and be deallocated, leaving the track in a bad state.

Make sure the AVAsset is referenced (at least) up until you call insertTimeRange. In practical terms, this means you should pass the AVAsset and its AVAssetTrack around together.