I'm trying to merge (append) 3 videos using AVAssetExportSession, but I keep getting this error. Weirdly for 1 or 2 videos it worked.
Error Domain=AVFoundationErrorDomain Code=-11820 "Cannot Complete Export" UserInfo=0x458120 {NSLocalizedRecoverySuggestion=Try exporting again., NSLocalizedDescription=Cannot Complete Export}
I even tried to redo the function in case of error but what I got is only infinite error message. This is the snippet of my code.
AVMutableComposition *mixComposition = [AVMutableComposition composition];
AVMutableCompositionTrack *compositionTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
NSError * error = nil;
NSMutableArray * timeRanges = [NSMutableArray arrayWithCapacity:arrayMovieUrl.count];
NSMutableArray * tracks = [NSMutableArray arrayWithCapacity:arrayMovieUrl.count];
for (int i=0; i<[arrayMovieUrl count]; i++) {
AVURLAsset *assetClip = [arrayMovieUrl objectAtIndex:i];
AVAssetTrack *clipVideoTrackB = [[assetClip tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
[timeRanges addObject:[NSValue valueWithCMTimeRange:CMTimeRangeMake(kCMTimeZero, assetClip.duration)]];
[tracks addObject:clipVideoTrackB];
}
[compositionTrack insertTimeRanges:timeRanges ofTracks:tracks atTime:kCMTimeZero error:&error];
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPreset1280x720];
NSParameterAssert(exporter != nil);
exporter.outputFileType = AVFileTypeQuickTimeMovie;
exporter.outputURL = outputUrl;
[exporter exportAsynchronouslyWithCompletionHandler:^{
switch ([exporter status]) {
case AVAssetExportSessionStatusFailed:
NSLog(#"Export failed: %#", [exporter error]);
break;
case AVAssetExportSessionStatusCancelled:
NSLog(#"Export canceled");
break;
case AVAssetExportSessionStatusCompleted:
NSLog(#"Export successfully");
break;
default:
break;
}
if (exporter.status != AVAssetExportSessionStatusCompleted){
NSLog(#"Retry export");
[self renderMovie];
}
}];
Is there something wrong with my code or iOS 5 has some bug?
I've found the problem. So the problem was actually because I use AVPlayerLayer to display each video in preview mode simultaneously. Referring to this question AVPlayerItem fails with AVStatusFailed and error code "Cannot Decode" , there's undocumented limit of maximum 4 simultaneous AVPlayer to work. And this limit somehow hinders AVAssetExportSession from working when there's 4 AVPlayer instance at that moment.
The solution is to release the AVPlayer before exporting, or not using AVPlayer altogether.
Related
It seems that AVFoundation cannot accept one of my videos. I really don't know why. It works with other videos, but not this one.
I'm not even modifying the video, I'm just doing a composition with the video track, and exporting it with the preset "AVAssetExportPresetHighestQuality".
I get this error :
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSUnderlyingError=0x60000045a8e0 {Error Domain=NSOSStatusErrorDomain Code=-12769 "(null)"}, NSLocalizedFailureReason=An unknown error occurred (-12769), NSLocalizedDescription=The operation could not be completed}
Do you know if there is something wrong in my code, or if the video is just not supported by AVFoundation ?
Here's the project on Github (it just exports the video to the camera roll) :
https://github.com/moonshaped/ExportSessionCrash
Or if you don't want to use Github :
Here's the video :
Dropbox link : https://www.dropbox.com/s/twgah26gqgsv9y9/localStoreTempVideoPath.mp4?dl=0
Or WeTransfer link : https://wetransfer.com/downloads/8f8ab257068461a2c9a051542610725220170606122640/8d934c
And here's the code :
- (void)exportVideo:(AVAsset *)videoAsset
videoDuration:(Float64)videoAssetDuration
to:(NSString *)resultPath{
[Utilities deleteFileIfExists:resultPath];
AVMutableComposition *mainComposition = [[AVMutableComposition alloc] init];
AVMutableCompositionTrack *compositionVideoTrack = [mainComposition addMutableTrackWithMediaType:AVMediaTypeVideo
preferredTrackID:kCMPersistentTrackID_Invalid];
int timeScale = 100000;
int videoDurationI = (int) (videoAssetDuration * (float) timeScale);
CMTime videoDuration = CMTimeMake(videoDurationI, timeScale);
CMTimeRange videoTimeRange = CMTimeRangeMake(kCMTimeZero, videoDuration);
NSArray<AVAssetTrack *> *videoTracks = [videoAsset tracksWithMediaType:AVMediaTypeVideo];
AVAssetTrack *videoTrack = [videoTracks objectAtIndex:0];
[compositionVideoTrack insertTimeRange:videoTimeRange
ofTrack:videoTrack
atTime:kCMTimeZero
error:nil];
NSURL *outptVideoUrl = [NSURL fileURLWithPath:resultPath];
self.exporter = [[AVAssetExportSession alloc] initWithAsset:mainComposition
presetName:AVAssetExportPresetHighestQuality];
self.exporter.outputURL = outptVideoUrl;
self.exporter.outputFileType = AVFileTypeMPEG4;
self.exporter.shouldOptimizeForNetworkUse = YES;
[self.exporter exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
switch (self.exporter.status) {
case AVAssetExportSessionStatusFailed:{
#throw [NSException exceptionWithName:#"failed export"
reason:[self.exporter.error description]
userInfo:nil];
}
case AVAssetExportSessionStatusCancelled:
#throw [NSException exceptionWithName:#"cancelled export"
reason:#"Export cancelled"
userInfo:nil];
case AVAssetExportSessionStatusCompleted: {
NSLog(#"Export finished");
}
break;
default:
break;
}
});
}];
}
I did an experiment and come to this. If you reduce 1 or more millisecond from the videoTimeRange then it will work. Try replacing the below code block:
int timeScale = 100000;
Float64 seconds = CMTimeGetSeconds([videoAsset duration]) - 0.001;
NSUInteger videoDurationI = (NSUInteger) (seconds * (float) timeScale);
CMTime videoDuration = CMTimeMake(videoDurationI, timeScale);
CMTimeRange videoTimeRange = CMTimeRangeMake(kCMTimeZero, videoDuration);
The device you are trying to test is not able to decode it. Please try it on some newer devices e.g. iPhone 6. I tested your media on iPad simulator iOS10.3 and it worked fine there so it must be something to do with encoding.
I'm creating an iOS app with the functionality to record audio for several minutes. I need to have some kind of auto save function every 10/20/30 seconds to have a fallback for when the app crashes / incoming phone calls / closing app completely.
What I've tried already:
I tried to work with the file after a crash, I could see the file in iTunes & play it on my MacBook but I couldn't play it in the app anymore (probably not saved in the right way?)
I tried duplicating the file while recording but the duplicate was also not workable (probably the same reason?)
Saving the file every .. seconds was not possible at all
I've tried to detect the moment the app was crashing/closed and then save the file quickly and store it locally. This was working but wasn't reliable enough that all the code was executed before the app went in to the background.
How can I make some sort of auto-save function so I always have a fallback for the recorded audio file??
I'm recording the audio with the AVAudioRecorder and AVAudioSession. Exporting the audio file (when done or when crashing/closing) is done with an AVMutableComposition and AVAssetExportSession.
Here is some of my code for initialising the AVAudioRecorder:
self.recorder = [[AVAudioRecorder alloc] initWithURL:filePath settings:_recordSetting error:NULL];
self.recorder.delegate = self;
self.recorder.meteringEnabled = YES;
[self.recorder prepareToRecord];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
And here is how I export the audio:
AVMutableComposition* composition = [[AVMutableComposition alloc] init];
AVMutableCompositionTrack *audioCombinedTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
[audioCombinedTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, [audio1 duration]) ofTrack:[audio1.tracks objectAtIndex:0] atTime:kCMTimeZero error:&error];
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetPassthrough];
NSString *latestFileName = [NSString stringWithFormat:#"%ld", (long)[[NSDate date] timeIntervalSince1970]];
NSString *exportPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.wav", latestFileName]];
_exportURL = [NSURL fileURLWithPath:exportPath];
exportSession.outputURL = _exportURL;
exportSession.outputFileType = AVFileTypeWAVE;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
NSLog (#"Exporting. status is %ld", (long)exportSession.status);
switch (exportSession.status) {
case AVAssetExportSessionStatusFailed:
NSLog(#"export failed");
break;
case AVAssetExportSessionStatusExporting: {
NSLog(#"export exporting");
break;
}
case AVAssetExportSessionStatusCancelled: {
NSLog(#"export cancelled");
break;
}
case AVAssetExportSessionStatusWaiting: {
NSLog(#"export waiting");
break;
}
case AVAssetExportSessionStatusUnknown: {
NSLog(#"export unknown");
break;
}
case AVAssetExportSessionStatusCompleted: {
NSLog(#"export done");
break;
}
};
}];
Note: when using the app (so just recording and stopping the recorder) the audio files are created correctly and everything works fine.
My app captures a video clip for 3 seconds, and programmatically I want to create a 15 sec clip from recorded 3 sec clip by looping it 5 times. And finally have to save 15 sec clip in CameraRoll.
I have got my 3 sec video clip via AVCaptureMovieFileOutput and I have its NSURL from delegate which is currently in NSTemporaryDirectory().
I am using AVAssetWriterInput for looping it. But it ask for CMSampleBufferRef like :
[writerInput appendSampleBuffer:sampleBuffer];
How will I gee this CMSampleBufferRef from video in NSTemporaryDirectory() ?
I have seen code for converting UIImage to CMSampleBufferRef, but I can find any for video file.
Any suggestion will be helpful. :)
Finally, I fixed my problem using AVMutableComposition. Here is my code :
AVMutableComposition *mixComposition = [AVMutableComposition new];
AVMutableCompositionTrack *mutableCompVideoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVURLAsset *videoAsset = [[AVURLAsset alloc]initWithURL:3SecFileURL options:nil];
CMTimeRange video_timeRange = CMTimeRangeMake(kCMTimeZero, [videoAsset duration]);
CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(M_PI_2);
[mutableCompVideoTrack setPreferredTransform:rotationTransform];
CMTime currentCMTime = kCMTimeZero;
for (NSInteger count = 0 ; count < 5 ; count++)
{
[mutableCompVideoTrack insertTimeRange:video_timeRange ofTrack:[[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] atTime:currentCMTime error:nil];
currentCMTime = CMTimeAdd(currentCMTime, [videoAsset duration]);
}
NSString *fullMoviePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[#"moviefull" stringByAppendingPathExtension:#"mov"]];
NSURL *finalVideoFileURL = [NSURL fileURLWithPath:fullMoviePath];
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetPassthrough];
[exportSession setOutputFileType:AVFileTypeQuickTimeMovie];
[exportSession setOutputURL:finalVideoFileURL];
CMTimeValue val = [mixComposition duration].value;
CMTime start = CMTimeMake(0, 1);
CMTime duration = CMTimeMake(val, 1);
CMTimeRange range = CMTimeRangeMake(start, duration);
[exportSession setTimeRange:range];
[exportSession exportAsynchronouslyWithCompletionHandler:^{
switch ([exportSession status])
{
case AVAssetExportSessionStatusFailed:
{
NSLog(#"Export failed: %# %#", [[exportSession error] localizedDescription], [[exportSession error]debugDescription]);
}
case AVAssetExportSessionStatusCancelled:
{
NSLog(#"Export canceled");
break;
}
case AVAssetExportSessionStatusCompleted:
{
NSLog(#"Export complete!");
}
default: NSLog(#"default");
}
}];
Take a look at AVAssetReader, this can return an CMSampleBufferRef. Keep in mind, you'll need to manipulate the timestamp for your approach to work.
So I've been trying to use AVAssetExportSession to trim a square video. But for some reason I just keep getting this error:
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo=0x1a03be70 {NSLocalizedDescription=The operation could not be completed, NSUnderlyingError=0x1a04c5e0 "The operation couldn’t be completed. (OSStatus error -12769.)", NSLocalizedFailureReason=An unknown error occurred (-12769)}
From Apple's website I found out that -11800 is an unknown error, but what about OSStatus error -12769? I searched everywhere on the internet and I had not seen any question/problem related to this error code. Please help. Thanks!
My code here:
AVAsset *asset = [[AVURLAsset alloc]initWithURL:self.originalVidUrl options:nil];
AVAssetTrack *clipVideoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
if (clipVideoTrack.naturalSize.width==clipVideoTrack.naturalSize.height) {
NSLog(#"Square video");
NSArray *presets = [AVAssetExportSession exportPresetsCompatibleWithAsset:asset];
if ([presets containsObject:AVAssetExportPresetHighestQuality]) {
self.exportSession = [[AVAssetExportSession alloc]initWithAsset:asset presetName:AVAssetExportPresetHighestQuality];
self.exportSession.outputURL = [NSURL fileURLWithPath: self.tmpVidPath];
self.exportSession.outputFileType = AVFileTypeMPEG4;
CMTime start = ...
CMTime duration = ...
CMTimeRange range = CMTimeRangeMake(start, duration);
self.exportSession.timeRange = range;
[self.exportSession exportAsynchronouslyWithCompletionHandler:^{
switch ([self.exportSession status]) {
case AVAssetExportSessionStatusFailed:
NSLog(#"%#",self.exportSession.error);
break;
case AVAssetExportSessionStatusCancelled:
NSLog(#"Export canceled");
break;
default:
NSLog(#"Export Success, File Saved.");
break;
}
}];
}
}
P.S. this code works for videos taken with the native camera app (i.e. non-square/non-processed videos).
I think I found the solution, but I have no idea why or how it worked.
Simply change the preset name from AVAssetExportPresetHighestQuality to AVAssetExportPreset1280x720, and you're good to go!
I have a 22kHz wave file and want a 22kHz m4a file. AVAssetExportSession with the preset AVAssetExportPresetAppleM4A automatically converts my wav to 44kHZ. I've tried different presets and nil to create the ExportSession, but without success.
Is there a way to set custom export properties of an AVAssetExportSession or do I need a completely different approach like the one stated in How to convert WAV file to M4A??
here is the code I have so far, which works great if you want a 44kHz file:
AVURLAsset *wavAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:wavPath] options:optionsDict];
AVMutableComposition *mutableComposition = [AVMutableComposition composition];
[mutableComposition insertTimeRange:CMTimeRangeMake(kCMTimeZero, wavAsset.duration)
ofAsset:wavAsset atTime:kCMTimeZero error:&error];
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]
initWithAsset:[mutableComposition copy] presetName:AVAssetExportPresetAppleM4A];
exportSession.outputURL = [NSURL fileURLWithPath:m4aPath];
exportSession.outputFileType = AVFileTypeAppleM4A;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
switch (exportSession.status) {
case AVAssetExportSessionStatusCompleted: {
[exportSession release];
completionHandler(nil);
break;
}
case AVAssetExportSessionStatusFailed: {
NSLog(#"There was an error while exporting the file: %#", exportSession.error);
completionHandler(exportSession.error);
break;
}
// ... handle some other cases...
default: {
break;
}
}
}];
Would be great if I just missed something.
Thanks in advance,
Dom
I've had some luck with the TPAACAudioConverter available here. It uses the ExtendedAudioFile API in the AudioToolbox Framework.