I'm using an AVComposition to merge two .m4a audio files together. One has to be inserted in the other one at a specific time. I then use an AVAssetExportSession to save the merge audio.
I'm getting this error during the Export Session Completion Handler:
Error Domain=AVFoundationErrorDomain Code=-11820 "Cannot Complete Export" UserInfo=0x1c3760 {NSLocalizedRecoverySuggestion=Try exporting again., NSLocalizedDescription=Cannot Complete Export}
Code - Merging the two assets:
AVAsset *originalAsset = [AVAsset assetWithURL:url];
NSURL *recordingAssetUrl = [[NSURL fileURLWithPath: NSTemporaryDirectory()] URLByAppendingPathComponent:kRecordTemp];
AVAsset *recordingAsset = [AVAsset assetWithURL:recordingAssetUrl];
AVMutableComposition *composition = [AVMutableComposition new];
AVMutableCompositionTrack *audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio
preferredTrackID:kCMPersistentTrackID_Invalid];
//Add the original audio asset to the composition
AVAssetTrack *sourceAudioTrack = [[originalAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
NSError *error = nil;
CMTimeRange sourceRange = CMTimeRangeMake(CMTimeMakeWithSeconds(0, 1), originalAsset.duration);
[audioTrack insertTimeRange:sourceRange ofTrack:sourceAudioTrack atTime:CMTimeMakeWithSeconds(0, 1) error:&error];
if (error) [self showError:error];
//Remove the portion of the original audio asset that will be replaced by the recording
CMTimeRange overwrittenRange = CMTimeRangeMake(self.currentTime, recordingAsset.duration);
[audioTrack removeTimeRange:overwrittenRange];
//Add the recording asset to the composition
AVAssetTrack *recordAudioTrack = [[recordingAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
error = nil;
CMTimeRange recordRange = CMTimeRangeMake(CMTimeMakeWithSeconds(0, 1), recordingAsset.duration);
[audioTrack insertTimeRange:recordRange ofTrack:recordAudioTrack atTime:self.currentTime error:&error];
if (error) [self showError:error];
//Delete the original asset
if([[NSFileManager defaultManager] fileExistsAtPath:url.path]){
error = nil;
[[NSFileManager defaultManager] removeItemAtURL:url error:&error];
if(error) [self showError:error];
}
//Write the composition
AVAssetExportSession * exportSession = [AVAssetExportSession exportSessionWithAsset:composition
presetName:AVAssetExportPresetAppleM4A];
exportSession.outputURL = url;
exportSession.outputFileType = AVFileTypeAppleM4A;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
if(exportSession.status != AVAssetExportSessionStatusCompleted){
[self showError:exportSession.error];
}
}];
//Delete the temporary asset
if([[NSFileManager defaultManager] fileExistsAtPath:recordingAssetUrl.path]){
error = nil;
[[NSFileManager defaultManager] removeItemAtURL:recordingAssetUrl error:&error];
if(error) [self showError:error]; //BOOM it blows up here
}
I'm not sure what other piece of code I should paste since it's such a generic error.
I'm using an AVAssetWriter to write the sample buffer to create the recording asset I receive from the capture session. The capture session successfully records and saves into files into the urls used in the above listing.
Related
I have implemented the scenario of converting audio from video but it is taking about 10 to 15 second, I want it to b much quicker than this as it is not user friendly in my app.
Please suggest me a solution
-(void)extractAudioFromVideo{
//Create a audia composition and add audio track
AVMutableComposition *newAudioAsset = [AVMutableComposition composition];
AVMutableCompositionTrack *dstCompositionTrack = [newAudioAsset addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
//Get video asset from which the audio should be extracted
NSURL *url = outputMainUrl;
AVAsset *srcAsset = [AVAsset assetWithURL:url];
NSArray *trackArray = [srcAsset tracksWithMediaType:AVMediaTypeAudio];
if(!trackArray.count){
NSLog(#"Track returns empty array for mediatype AVMediaTypeAudio");
return;
}
AVAssetTrack *srcAssetTrack = [trackArray objectAtIndex:0];
//Extract time range
CMTimeRange timeRange = srcAssetTrack.timeRange;
//Insert audio from the video to mutable avcomposition track
NSError *err = nil;
if(NO == [dstCompositionTrack insertTimeRange:timeRange ofTrack:srcAssetTrack atTime:kCMTimeZero error:&err]){
NSLog(#"Failed to insert audio from the video to mutable avcomposition track");
return;
}
//Export the avcompostion track to destination path
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex: 0];
NSString *dstPath = [documentsDirectory stringByAppendingString:#"/sample_audio.mp4"];
NSURL *dstURL = [NSURL fileURLWithPath:dstPath];
urlOrigionalAudio = dstURL;
//Remove if any file already exists
[[NSFileManager defaultManager] removeItemAtURL:dstURL error:nil];
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]initWithAsset:newAudioAsset presetName:AVAssetExportPresetPassthrough];
NSLog(#"support file types= %#", [exportSession supportedFileTypes]);
exportSession.outputFileType = #"public.mpeg-4";
exportSession.outputURL = dstURL;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
AVAssetExportSessionStatus status = exportSession.status;
if(status == AVAssetExportSessionStatusCompleted){
urlOrigionalAudio = exportSession.outputURL;
[SVProgressHUD dismiss];
}
else if (status == AVAssetExportSessionStatusFailed){
[SVProgressHUD showErrorWithStatus:#"Failled!"];
NSLog(#"Export status not yet completed");
}
}];
}
I want to convert a MP4-file from the apps Document folder into an Audio-file (mp3 or m4a).
I already tried, but i can not play the converted MP3-file with AVPlayer.
Here is my code:
-(void)convertMP4toMP3withFile:(NSString*)dstPath
{
NSURL *dstURL = [NSURL fileURLWithPath:dstPath];
AVMutableComposition* newAudioAsset = [AVMutableComposition composition];
AVMutableCompositionTrack* dstCompositionTrack;
dstCompositionTrack = [newAudioAsset addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
AVAsset* srcAsset = [AVURLAsset URLAssetWithURL:dstURL options:nil];
AVAssetTrack* srcTrack = [[srcAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
CMTimeRange timeRange = srcTrack.timeRange;
NSError* error;
if(NO == [dstCompositionTrack insertTimeRange:timeRange ofTrack:srcTrack atTime:kCMTimeZero error:&error]) {
NSLog(#"track insert failed: %#\n", error);
return;
}
AVAssetExportSession* exportSesh = [[AVAssetExportSession alloc] initWithAsset:newAudioAsset presetName:AVAssetExportPresetPassthrough];
exportSesh.outputFileType = AVFileTypeAppleM4A;
exportSesh.outputURL = dstURL;
[[NSFileManager defaultManager] removeItemAtURL:dstURL error:nil];
[exportSesh exportAsynchronouslyWithCompletionHandler:^{
AVAssetExportSessionStatus status = exportSesh.status;
NSLog(#"exportAsynchronouslyWithCompletionHandler: %i\n", status);
if(AVAssetExportSessionStatusFailed == status) {
NSLog(#"FAILURE: %#\n", exportSesh.error);
} else if(AVAssetExportSessionStatusCompleted == status) {
NSLog(#"SUCCESS!\n");
NSError *error;
//append the name of the file in jpg form
//check if the file exists (completely unnecessary).
NSString *onlyPath = [dstPath stringByDeletingLastPathComponent];
NSString *toPathString = [NSString stringWithFormat:#"%#/testfile.m4a", onlyPath];
[[NSFileManager defaultManager] moveItemAtPath:dstPath toPath:toPathString error:&error];
[self loadFiles];
}
}];
}
Has anyone a solution for my problem or can improve my code?
replace this line:
exportSesh.outputFileType = AVFileTypeAppleM4A;
with:
exportSesh.outputFileType = AVFileTypeCoreAudioFormat;
& this:
NSString *toPathString = [NSString stringWithFormat:#"%#/testfile.m4a", onlyPath];
with:
NSString *toPathString = [NSString stringWithFormat:#"%#/testfile.mp3", onlyPath];
Worked for me :)
The code does work on converting mp4 to m4a. Maybe your play audio file code is wrong. Following questions may be helpful to you.
No sound coming from AVPlayer
iOS Extracting Audio from .mov file
I am trying to extract the audio file from .m4v video. It shows the error like,
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo=0x15e350d0 {NSLocalizedDescription=The operation could not be completed, NSUnderlyingError=0x15d1c6b0 "The operation couldn’t be completed. (OSStatus error -12124.)", NSLocalizedFailureReason=An unknown error occurred (-12124)}
This is my code:
-(void)extractAudioFromVideo{
//Create a audia composition and add audio track
AVMutableComposition *newAudioAsset = [AVMutableComposition composition];
AVMutableCompositionTrack *dstCompositionTrack = [newAudioAsset addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
//Get video asset from which the audio should be extracted
NSURL *url = [[NSBundle mainBundle] URLForResource:#"sample_iPod" withExtension:#"m4v"];
AVAsset *srcAsset = [AVAsset assetWithURL:url];
NSArray *trackArray = [srcAsset tracksWithMediaType:AVMediaTypeAudio];
if(!trackArray.count){
NSLog(#"Track returns empty array for mediatype AVMediaTypeAudio");
return;
}
AVAssetTrack *srcAssetTrack = [trackArray objectAtIndex:0];
//Extract time range
CMTimeRange timeRange = srcAssetTrack.timeRange;
//Insert audio from the video to mutable avcomposition track
NSError *err = nil;
if(NO == [dstCompositionTrack insertTimeRange:timeRange ofTrack:srcAssetTrack atTime:kCMTimeZero error:&err]){
NSLog(#"Failed to insert audio from the video to mutable avcomposition track");
return;
}
//Export the avcompostion track to destination path
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex: 0];
NSString *dstPath = [documentsDirectory stringByAppendingString:#"sample_audio.mp4"];
NSURL *dstURL = [NSURL fileURLWithPath:dstPath];
//Remove if any file already exists
[[NSFileManager defaultManager] removeItemAtURL:dstURL error:nil];
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]initWithAsset:newAudioAsset presetName:AVAssetExportPresetPassthrough];
NSLog(#"support file types= %#", [exportSession supportedFileTypes]);
exportSession.outputFileType = #"public.mpeg-4";
exportSession.outputURL = dstURL;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
AVAssetExportSessionStatus status = exportSession.status;
if(AVAssetExportSessionStatusCompleted != status){
NSLog(#"Export status not yet completed. Error: %#", exportSession.error.description);
}
}];
}
How can I resolve this error?
I've tested your code in simulator. It's working fine in simulator(IOS7). But When I run on iPodTouch-5, it shows error as like what you've mention. Spend more than 15 mins, found and silly mistake.
Getting path as below(Documentssample_audio.mp4) when I run in Device..
#"file:///var/mobile/Applications/A1E1D85F-0198-4A0C-80F8-222F0DA1C31A/Documentssample_audio.mp4"
so I've modified path as..
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex: 0];
NSString *dstPath = [documentsDirectory stringByAppendingString:#"/sample_audio.mp4"];
Now I'm getting path as below(/Documents/sample_audio.mp4) and working fine. But I don't know, How it is happened
#"file:///var/mobile/Applications/A1E1D85F-0198-4A0C-80F8-222F0DA1C31A/Documents/sample_audio.mp4"
I am trying to join 2 preexisting mpeg4 video's together on an ipad2 with the following code.
-(void)mergeTestVideos
{
//setup asset
NSString *firstassetpath = [NSString stringWithFormat:#"%#mpeg4-1.mp4", NSTemporaryDirectory()];
NSString *secondassetpath = [NSString stringWithFormat:#"%#mpeg4-2.mp4", NSTemporaryDirectory()];
NSFileManager *fileManager = [NSFileManager defaultManager];
AVAsset *firstAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:firstassetpath]];
AVAsset *secondAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:secondassetpath]];
NSLog(#"FirstAsset Is Readable = %d", firstAsset.isReadable);
NSLog(#"FirstAsset Is playable = %d", firstAsset.isPlayable);
NSLog(#"FirstAsset Is exportable = %d", firstAsset.exportable);
NSLog(#"SecondAsset Is Readable = %d", secondAsset.isReadable);
NSLog(#"SecondAsset Is playable = %d", secondAsset.isPlayable);
NSLog(#"SecondAsset Is exportable = %d", secondAsset.exportable);
//setup composition and track
AVMutableComposition *composition = [[AVMutableComposition alloc]init];
AVMutableCompositionTrack *track = [composition addMutableTrackWithMediaType:AVAssetExportPresetPassthrough preferredTrackID:kCMPersistentTrackID_Invalid];
//add assets to track
[track insertTimeRange:CMTimeRangeMake(kCMTimeZero, firstAsset.duration) ofTrack:[[firstAsset tracksWithMediaType:AVMediaTypeVideo]objectAtIndex:0] atTime:kCMTimeZero error:nil];
[track insertTimeRange:CMTimeRangeMake(kCMTimeZero, secondAsset.duration) ofTrack:[[secondAsset tracksWithMediaType:AVMediaTypeVideo]objectAtIndex:0] atTime:firstAsset.duration error:nil];
// 5 - Create exporter
AVAssetExportSession *exporter = [[AVAssetExportSession alloc]initWithAsset:composition presetName:AVAssetExportPresetPassthrough];
NSString *outputURL = [NSString stringWithFormat:#"%#mergedvid.mp4", NSTemporaryDirectory()];
NSLog(#"%#", exporter.supportedFileTypes);
exporter.outputURL=[NSURL fileURLWithPath:outputURL];
exporter.outputFileType = AVFileTypeMPEG4;
[exporter exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
[self exportDidFinish:exporter];
});
}];
}
-(void)exportDidFinish:(AVAssetExportSession*)session {
NSLog(#"export method");
NSLog(#"%i", session.status);
NSLog(#"%#", session.error);
}
and output is as follows:
- FirstAsset Is Readable = 1
- FirstAsset Is playable = 1
- FirstAsset Is exportable = 1
- SecondAsset Is Readable = 1
- SecondAsset Is playable = 1
- SecondAsset Is exportable = 1
- (
"com.apple.quicktime-movie",
"com.apple.m4a-audio",
"public.mpeg-4",
"com.apple.m4v-video",
"public.3gpp",
"org.3gpp.adaptive-multi-rate-audio",
"com.microsoft.waveform-audio",
"public.aiff-audio",
"public.aifc-audio",
"com.apple.coreaudio-format"
)
-export method
- 4
- Error Domain=AVFoundationErrorDomain Code=-11838 "Operation Stopped" UserInfo=0x155f76f0 {NSLocalizedDescription=Operation Stopped, NSLocalizedFailureReason=The operation is not supported for this media.}
Ok so according to the output my files are ok and exportable and mp4 is a supported output type.
Does anybody have any idea why it is giving me the error 'The operation is not supported for this media'
I think this statement is your culprit
AVMutableCompositionTrack *track = [composition addMutableTrackWithMediaType:AVAssetExportPresetPassthrough preferredTrackID:kCMPersistentTrackID_Invalid];
Here you are passing AVAssetExportPresetPassthrough where you should have used AVMediaTypeVideo or AVMediaTypeAudio
If you are joining two videos then why you are using kCMTimeZero at both place while inserting time range. I think it should be
[track insertTimeRange:CMTimeRangeMake(kCMTimeZero, firstAsset.duration) ofTrack:[[firstAsset tracksWithMediaType:AVMediaTypeVideo]objectAtIndex:0] atTime:kCMTimeZero error:nil];
[track insertTimeRange:CMTimeRangeMake(kCMTimeZero, secondAsset.duration) ofTrack:[[secondAsset tracksWithMediaType:AVMediaTypeVideo]objectAtIndex:0] atTime:firstAsset.duration error:nil];
See changes in atTime while inserting time range for secondAsset
I have a weird problem. In my app I am combining multiple audio and video files using the code below. The resulted video seems to work fine once I downloaded it from the device to the computer and play with Quick Time, but whenever I am trying to play the newly composed video using either UIWebView or AVPLayer I can only see first part of merged video files.
Furthermore when I tried to use MPMoviePlayerController to play it hangs on "Loading".
I can hear audio for all composition. To make it clear I have two arrays:
1- audioPieces with paths to audio files [song1, song2, song3];
2- moviePieces with paths to video files [movie1,movie2,movie3];
After merging those files I can see only movie1 but I can hear song1 + song2 + song3.
P.S. songs and movies have different lengths (Less than 0.2s difference).
Any help will be appreciated.
Thank you in advance,
Janusz
-(void)putFilesTogether{
AVMutableComposition *mixComposition = [AVMutableComposition composition];
AVMutableCompositionTrack *videoCompositionTrack =[[AVMutableCompositionTrack alloc]init];
AVMutableCompositionTrack *audioCompositionTrack =[[AVMutableCompositionTrack alloc]init];
NSLog(#" movie %# audio %# ", moviePieces, audioPieces);
NSError * error;
for(int i=0;i<moviePieces.count;i++)
{
NSFileManager * fm = [NSFileManager defaultManager];
NSString * movieFilePath;
NSString * audioFilePath;
movieFilePath = [moviePieces objectAtIndex:i];
audioFilePath = [audioPieces objectAtIndex:i];
if(![fm fileExistsAtPath:movieFilePath]){
NSLog(#"Movie doesn't exist %# ",movieFilePath);
}
else{
NSLog(#"Movie exist %# ",movieFilePath);
}
if(![fm fileExistsAtPath:audioFilePath]){
NSLog(#"Audio doesn't exist %# ",audioFilePath);
}
else{
NSLog(#"Audio exists %# ",audioFilePath);
}
NSURL *videoUrl = [NSURL fileURLWithPath:movieFilePath];
NSURL *audioUrl = [NSURL fileURLWithPath:audioFilePath];
AVURLAsset *videoasset = [[AVURLAsset alloc]initWithURL:videoUrl options:nil];
AVAssetTrack *videoAssetTrack= [[videoasset tracksWithMediaType:AVMediaTypeVideo] lastObject];
AVURLAsset *audioasset = [[AVURLAsset alloc]initWithURL:audioUrl options:nil];
AVAssetTrack *audioAssetTrack= [[audioasset tracksWithMediaType:AVMediaTypeAudio] lastObject];
videoCompositionTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
audioCompositionTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
CMTime tempTime = mixComposition.duration;
[audioCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, audioasset.duration) ofTrack:audioAssetTrack atTime:tempTime error:&error];
[videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoasset.duration) ofTrack:videoAssetTrack atTime:tempTime error:&error];
if(error)
{
NSLog(#"Ups. Something went wrong! %#", [error debugDescription]);
}
}
NSDate *now = [NSDate dateWithTimeIntervalSinceNow:0];
NSString *caldate = [now description];
float ran = arc4random()%1000;
NSString * pathToSave = [NSString stringWithFormat:#"Output%#%f.mp4",caldate,ran];
pathToSave =[DOCUMENTS_FOLDER stringByAppendingPathComponent:pathToSave];
NSURL *movieUrl = [NSURL fileURLWithPath:pathToSave];
AVAssetExportSession *exporter =[[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetPassthrough];
exporter.outputFileType=AVFileTypeQuickTimeMovie;
exporter.outputURL=movieUrl;
exporter.shouldOptimizeForNetworkUse=YES;
CMTimeValue val = mixComposition.duration.value;
CMTime start=CMTimeMake(0, 600);
CMTime duration=CMTimeMake(val, 600);
CMTimeRange range=CMTimeRangeMake(start, duration);
exporter.timeRange=range;
[exporter exportAsynchronouslyWithCompletionHandler:^{
switch ([exporter status]) {
case AVAssetExportSessionStatusFailed:{
NSLog(#"Export failed: %# %#", [[exporter error] localizedDescription],[[exporter error]debugDescription]);
NSString * message = #"Movie wasn't created. Try again later.";
[self performSelectorOnMainThread:#selector(dismissMe:) withObject:message waitUntilDone:NO];
break;}
case AVAssetExportSessionStatusCancelled:{ NSLog(#"Export canceled");
NSString * message1 = #"Movie wasn't created. Try again later.";
[self performSelectorOnMainThread:#selector(dismissMe:) withObject:message1 waitUntilDone:NO];
break;}
case AVAssetExportSessionStatusCompleted:
{
NSString * message = #"Movie was successfully created.";
CMTime duration = mixComposition.duration;
[self saveData:duration ofPath:pathToSave];
[self cleanFiles];
[self performSelectorOnMainThread:#selector(dismissMe:) withObject:message waitUntilDone:NO];
}
}}];
}
The problem lays in:
videoCompositionTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
audioCompositionTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
They need to be moved outside the for loop body.