AVMutableVideoComposition some inserted video crash composition - iphone

Some videos play fine alone as simple AVAssets but when inserted in an AVMutableVideoComposition they make the whole composition fail even if those videos are inserted alone in an empty composition. Anyone has similar issues? Sometime re-encoding the videos before inserting them makes it work sometime not. I can't see any mistake in my timing instruction and using some other video don't cause any problem at all no matter their length. Can there be issues with the number of frame or the duration of the asset or their format? (all are h264 single track)

Well indeed it looks like some video may cause problem when being inserted. example here duration of the track is the issue.
AVAsset * video = ...;
NSArray * videoTracks = [video tracksWithMediaType:AVMediaTypeVideo];
AVAssetTrack * videoTrack = videoTracks.firstObject;
CMTime duration = videoTrack.timeRange.duration // this time cause error
AVMutableCompositionTrack * track = ...;
CMTime insertCMTime = kCMTimeZero;
CMTimeRange trackRange = CMTimeRangeMake(kCMTimeZero, duration);
[track insertTimeRange:trackRange
ofTrack:videoTrack
atTime:insertCMTime
error:nil];
Trimming the range to insert to a round second solved the problem for all video tested so far
NSTimeInterval interval = (int)CMTimeGetSeconds(videoTrack.timeRange.duration);
CMTime roundedDuration = CMTimeMakeWithSeconds(interval, 60000);

Related

Exporting video from MPMediaPickerController - no audio

I am using an MPMediaPickerController to allow the user to select videos and songs from the library on the device. I allow this with the: initWithMediaTypes:MPMediaTypeAny initialization for the picker. The user can then play the song or video in-app after the export takes place. Here is my movie-exporting code after stripping it to its core functionality:
- (void)mediaPicker:(MPMediaPickerController*)mediaPicker didPickMediaItems:(MPMediaItemCollection*)mediaItemCollection {
AVAssetExportSession *exportSession;
NSString *filePath;
NSURL *fileUrl;
for (MPMediaItem *item in mediaItemCollection.items) {
NSURL *assetUrl = [item valueForProperty:MPMediaItemPropertyAssetURL];
AVAsset *currAsset = [AVAsset assetWithURL:assetUrl];
exportSession = [[AVAssetExportSession alloc] initWithAsset:[AVAsset assetWithURL:assetUrl] presetName:AVAssetExportPresetHighestQuality];
exportSession.shouldOptimizeForNetworkUse = YES;
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
filePath = [title stringByAppendingString:#".mov"];
fileUrl = [NSURL fileURLWithPath:[[NSFileManager documentDirectory] stringByAppendingPathComponent:filePath]];
exportSession.outputURL = fileUrl;
dispatch_group_enter(dispatchGroup);
[exportSession exportAsynchronouslyWithCompletionHandler:^{
// success
}
dispatch_group_leave(dispatchGroup);
}];
}
This similar code works fine for doing audio, but for video, the video's audio does not play. Most content from iTunes is protected and non-exportable, so I wanted to test with a homemade quick video I shot with my iPhone. I shot the video, dragged it into iTunes (and I made it a "music video" so that it shows up properly and can be exported to my phone's library). Then I sync'd and sent it to my device for testing.
In the app, the video shows up fine in the Media Picker, and I can export it with no errors that I can see. However, when I play it in-app, it only plays the video and not the audio. Other videos that I import from other sources work fine for playing the video's audio, so I don't 'think' it's the player itself.
Is there something I may be missing here on why the audio would not be coming across from this kind of export from the media picker? Thanks in advance for any assistance on this issue!
Not sure if this is the ideal solution, but the only way we found around this issue was to change it to force m4v format with PresetPassthrough set. I.e:
exportSession = [[AVAssetExportSession alloc] initWithAsset:[AVAsset assetWithURL:assetUrl] presetName:AVAssetExportPresetPassthrough];
exportSession.shouldOptimizeForNetworkUse = YES;
exportSession.outputFileType = AVFileTypeAppleM4V;
filePath = [title stringByAppendingString:#".m4v"];
Audio and video seems to work fine for videos imported locally this way, after making that change.

AVAssetExportSession estimatedOutputFileLength always returns 0

Having a problem where the estimatedOutputFileLength property of AVAssetExportSession always returns 0 (and returns -9223372036854775808 on the simulator).
I've tried everything to get this to work, trying different outputFileTypes, toggling shouldOptimizeForNetworkUse on and off, specifying (or not specifying) the outputURL... despite all this, nothing seems to work and I'm beginning to think this may be a bug in the SDK.
This is my code:
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetMediumQuality]; // doesn't matter which preset is used
//exportSession.shouldOptimizeForNetworkUse = YES;
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
NSLog(#"bytes = %lld", exportSession.estimatedOutputFileLength);
I just can't figure out why this isn't working! (iOS 6, iPhone 5)
You can workaround this problem by setting proper timeRange on the exportSession:
exportSession.timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);
It seems that in iOS, the AVAssetExportSessionInternal.timeRange is not getting sensible result when estimating file length.
You need to include the timerange.
How much of the file you intend to export. Without that it will return 0,
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset: songAsset presetName: AVAssetExportPresetAppleM4A];
exporter.outputFileType = AVFileTypeAppleM4A;
CMTime full = CMTimeMultiplyByFloat64(exporter.asset.duration, 1);
exporter.timeRange = CMTimeRangeMake(kCMTimeZero, full);
long long size = exporter.estimatedOutputFileLength;
fileInfo.fileSize = size;

Cropping videos in iOS

We can crop images. Can we crop videos?
Since video is a collection of pictures you can crop all frames from video and after create new video. AVFoundation guide describe some tasks:
Putting it all Together: Capturing Video Frames as UIImage Objects
After this you crops images and write video
You can use an asset writer to produce a QuickTime movie file or an
MPEG-4 file from media such as sample buffers or still images.
See for more details AV Foundation Framework
[[NSFileManager defaultManager] removeItemAtURL:outputURL error:nil];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:inputURL options:nil];
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetLowQuality];
exportSession.outputURL = outputURL;
exportSession.shouldOptimizeForNetworkUse = YES;
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
CMTime start = CMTimeMakeWithSeconds(1.0, 600);
CMTime duration = CMTimeMakeWithSeconds(120.0, 600);
CMTimeRange range = CMTimeRangeMake(start, duration);
exportSession.timeRange = range;
[exportSession exportAsynchronouslyWithCompletionHandler:^(void){
handler(exportSession);
[exportSession release];}];
Here we get a video of first 2 mins.
You should be able to do this using AVAssetExportSession, AVVideoComposition, and AVVideoCompositionCoreAnimationTool (and just set up a CALayer hierarchy with the positioning you want). I'm not sure if this is the most efficient way, though.
it's not as simple as images
but it could be as easy as the correct specification of the video but there is not enough information.
in the decoding settings, you can manipulate video pixels by geometry, ie, anamorphic, squeezed, stretched and also player/browser settings, the image window or player window, you can specify a small player window and a magnification level. if you allow or disallow zoom/magnification, you'll force an offscreeen draw or black bars.
i would encode to the correct size and platform for best quality, these kinds of fixes are 'kludges' but they work in a pinch. i would grab the quicktime sdk and poke around.

How to find an audio file's length (in seconds)

(Objective C)
Just using simple AudioServicesPlaySystemSoundID and its counterparts, but I can't find in the documentation if there is already a way to find the length of an audio file.
I know there is AudioServicesGetPropertyInfo, but that seems to return a byte-buffer - do audio files embed their length in themselves and I can just extract it with this?
Or is there perhaps a formula based on bit-rate * fileSize to convert to length-of-time?
mIL3S
www.milkdrinkingcow.com
According to a quick Google search, there is a formula:
length-of-time (duration in seconds) = fileSize (in bytes) / bit-rate (bits/secs)*8
Is there any particular reason you're using System Sound Services to play a sound?
If you use AVAudioPlayer to handle your sounds you could do something like:
AVAudioPlayer * sound = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:nil];
sound.delegate = self;
sound.volume = 1;
NSLog([NSString stringWithFormat:#"%f", sound.duration]);
sample code from answer How to get the duration of an audio file in iOS?. This is the best answer.
AVURLAsset* audioAsset = [AVURLAsset URLAssetWithURL:audioFileURL options:nil];
CMTime audioDuration = audioAsset.duration;
float audioDurationSeconds = CMTimeGetSeconds(audioDuration);
See at:
Float64 fileDuration = PrepareFileAU (fileAU, fileFormat, audioFile);
see ADC sample at:
http://developer.apple.com/library/mac/#samplecode/PlayFile/Introduction/Intro.html
without using AVPlayer...

iPhone: get duration of an audio file

What is the easiest way to get a duration of an audio file?
I could create an object of AVAudioPlayer, initialize it with URL and than get the duration, but this way is too long. Is there an easier way?
Thanks.
Correct code is
NSURL *afUrl = [NSURL fileURLWithPath:soundPath];
AudioFileID fileID;
OSStatus result = AudioFileOpenURL((CFURLRef)afUrl, kAudioFileReadPermission, 0, &fileID);
Float64 outDataSize = 0;
UInt32 thePropSize = sizeof(Float64);
result = AudioFileGetProperty(fileID, kAudioFilePropertyEstimatedDuration, &thePropSize, &outDataSize);
AudioFileClose(fileID);
outDataSize should be Float64 not UInt64.
You can use the Audio File Services functions. There's one property to get that should give you the estimated duration. Code:
NSURL *afUrl = [NSURL fileURLWithPath:soundPath];
AudioFileID fileID;
OSStatus result = AudioFileOpenURL((CFURLRef)afUrl, kAudioFileReadPermission, 0, &fileID);
Float64 outDataSize = 0;
UInt32 thePropSize = sizeof(Float64);
result = AudioFileGetProperty(fileID, kAudioFilePropertyEstimatedDuration, &thePropSize, &outDataSize);
AudioFileClose(fileID);
You can check the docs here
If you know anything about the audio file in question (samplerate, bitdepth, channel count), and it's an uncompressed format (WAV/AIFF), then you can calculate the /approximate/ duration from the filesize:
length_in_seconds = (file_length-guess_100_bytes_for_header) / (samplerate*(bitdepth*channel_count/8))
It depends on the file type. If it's a WAV file you can locate the file's header and determine the playback duration that way. If it's a compressed format (*.mp3 etc.) you're better off sticking to the method you mentioned.
Combining AVAudioPlayer with Swift becomes as easy as (I am migrating a Realm table below, but you get the idea):
import AVFoundation
let resource = old!["filename"] as? String
let afUrl = NSBundle.mainBundle().URLForResource(resource, withExtension: nil)
let player = try! AVAudioPlayer(contentsOfURL: afUrl!)
new!["duration"] = Double(player.duration)
In case anyone lands here looking for a way to get the duration for both audio and video files, have a look at this answer to another post, which instead uses AVAsset instead:
https://stackoverflow.com/a/7052147/381233
Using AudioFileGetProperty (like the other two main answers here) to get the duration of A/V files didn't work for a few .mov files on my device (result was always 0), while the solution using AVAsset got the duration for all audio and video files on my device.
(Interestingly enough, however, the duration from both solutions was sometimes 1 second more than that displayed in the UI of an actual AVAudioPlayer. Most likely the AVAudioPlayer uses a non-standard rounding routine for the displayed duration.)
AVURLAsset* audioAsset = [AVURLAsset URLAssetWithURL:mp3_url options:nil];
[audioAsset loadValuesAsynchronouslyForKeys:#[#"duration"] completionHandler:^{
CMTime audioDuration = audioAsset.duration;
float audioDurationSeconds = CMTimeGetSeconds(audioDuration);
NSLog(#"duration:%f",audioDurationSeconds);
}];
sample code from answer How to get the duration of an audio file in iOS?. This is the best answer.
AVURLAsset* audioAsset = [AVURLAsset URLAssetWithURL:audioFileURL options:nil];
CMTime audioDuration = audioAsset.duration;
float audioDurationSeconds = CMTimeGetSeconds(audioDuration);