Change camera resolution if I have width and height in iOS - ios

I need to change live video resolution with the width and height inputted by user. Sorry for my question but I have never done it before.
Please help.

You can change video resolution by using AVMutableVideoComposition and AVAssetExportSession.
First create object of AVMutableVideoComposition shown below.
AVMutableVideoComposition* videoComposition = [AVMutableVideoComposition videoComposition];
videoComposition.frameDuration = CMTimeMake(1, 30);
videoComposition.renderSize = CGSizeMake(YOUR_WIDTH, YOUR_HEIGHT);
Then, create object of AVAssetExportSession,
exporter = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetHighestQuality];
exporter.videoComposition = videoComposition;
And write completionBlock for exporter.
Hope this helps.

If you are using OpenTok, you can use a custom video capturer that is mostly identical to the one found in this sample. The only difference is that you would need to additionally write code to scale the image from the CVPixelBuffer (called imageBuffer) to the size which your user is setting.
One way technique to scale the image would be to use the CoreImage APIs as shown here: https://stackoverflow.com/a/8494304/305340

Related

AVPlayer plays video composition result incorrectly

I need a simple thing: play a video while rotating and applying CIFilter on it.
First, I create the player item:
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:videoURL];
// DEBUG LOGGING
AVAssetTrack *track = [[item.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
NSLog(#"Natural size is: %#", NSStringFromCGSize(track.naturalSize));
NSLog(#"Preffered track transform is: %#", NSStringFromCGAffineTransform(track.preferredTransform));
NSLog(#"Preffered asset transform is: %#", NSStringFromCGAffineTransform(item.asset.preferredTransform));
Then I need to apply the video composition. Originally, I was thinking to create an AVVideoComposition with 2 instructions - one will be the AVVideoCompositionLayerInstruction for rotation and the other one will be CIFilter application. However, I got an exception thrown saying "Expecting video composition to contain only AVCoreImageFilterVideoCompositionInstruction" which means Apple doesn't allow to combine those 2 instructions. As a result, I combined both under the filtering, here is the code:
AVAsset *asset = playerItem.asset;
CGAffineTransform rotation = [self transformForItem:playerItem];
AVVideoComposition *composition = [AVVideoComposition videoCompositionWithAsset:asset applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest * _Nonnull request) {
// Step 1: get the input frame image (screenshot 1)
CIImage *sourceImage = request.sourceImage;
// Step 2: rotate the frame
CIFilter *transformFilter = [CIFilter filterWithName:#"CIAffineTransform"];
[transformFilter setValue:sourceImage forKey: kCIInputImageKey];
[transformFilter setValue: [NSValue valueWithCGAffineTransform: rotation] forKey: kCIInputTransformKey];
sourceImage = transformFilter.outputImage;
CGRect extent = sourceImage.extent;
CGAffineTransform translation = CGAffineTransformMakeTranslation(-extent.origin.x, -extent.origin.y);
[transformFilter setValue:sourceImage forKey: kCIInputImageKey];
[transformFilter setValue: [NSValue valueWithCGAffineTransform: translation] forKey: kCIInputTransformKey];
sourceImage = transformFilter.outputImage;
// Step 3: apply the custom filter chosen by the user
extent = sourceImage.extent;
sourceImage = [sourceImage imageByClampingToExtent];
[filter setValue:sourceImage forKey:kCIInputImageKey];
sourceImage = filter.outputImage;
sourceImage = [sourceImage imageByCroppingToRect:extent];
// Step 4: finish processing the frame (screenshot 2)
[request finishWithImage:sourceImage context:nil];
}];
playerItem.videoComposition = composition;
The screenshots I made during debugging show that the image is successfully rotated and the filter is applied (in this example it was an identity filter which doesn't change the image). Here are the screenshot 1 and screenshot 2 which were taken at the points marked in the comments above:
As you can see, the rotation is successful, the extent of the resulting frame was also correct.
The problem starts when I try to play this video in a player. Here is what I get:
So seems like all the frames are scaled and shifted down. The green area is the empty frame info, when I clamp to extent to make frame infinite size it shows border pixels instead of green. I have a feeling that the player still takes some old size info before rotation from the AVPlayerItem, that's why in the first code snippet above I was logging the sizes and transforms, there are the logs:
Natural size is: {1920, 1080}
Preffered track transform is: [0, 1, -1, 0, 1080, 0]
Preffered asset transform is: [1, 0, 0, 1, 0, 0]
The player is set up like this:
layer.videoGravity = AVLayerVideoGravityResizeAspectFill;
layer.needsDisplayOnBoundsChange = YES;
PLEASE NOTE the most important thing: this only happens to videos which were recorded by the app itself using camera in landscape iPhone[6s] orientation and saved on the device storage previously. The videos that the app records in portrait mode are totally fine (by the way, the portrait videos got exactly the same size and transform log like landscape videos! strange...maybe iphone puts the rotation info in the video and fixes it). So zooming and shifting the video seems like a combination of "aspect fill" and old resolution info before rotation. By the way, the portrait video frames are shown partially because of scaling to fill the player area which has a different aspect ratio, but this is expected behavior.
Let me know your thoughts on this and, if you know a better way how to accomplish what I need, then it would be great to know.
UPDATE: There comes out to be an easier way to "change" the AVPlayerItem video dimensions during playback - set the renderSize property of video composition (can be done using AVMutableVideoComposition class).
MY OLD ANSWER BELOW:
After a lot of debugging I understood the problem and found a solution. My initial guess that AVPlayer still considers the video being of the original size was correct. In the image below it is explained what was happening:
As for the solution, I couldn't find a way to change the video size inside AVAsset or AVPlayerItem. So I just manipulated the video to fit the size and scale that AVPlayer was expecting, and then when playing in a player with correct aspect ratio and flag to scale and fill the player area - everything looks good. Here is the graphical explanation:
And here goes the additional code that needs to be inserted in the applyingCIFiltersWithHandler block mentioned in the question:
... after Step 3 in the question codes above
// make the frame the same aspect ratio as the original input frame
// by adding empty spaces at the top and the bottom of the extent rectangle
CGFloat newHeight = originalExtent.size.height * originalExtent.size.height / extent.size.height;
CGFloat inset = (extent.size.height - newHeight) / 2;
extent = CGRectInset(extent, 0, inset);
sourceImage = [sourceImage imageByCroppingToRect:extent];
// scale down to the original frame size
CGFloat scale = originalExtent.size.height / newHeight;
CGAffineTransform scaleTransform = CGAffineTransformMakeScale(scale, scale);
[transformFilter setValue:sourceImage forKey: kCIInputImageKey];
[transformFilter setValue: [NSValue valueWithCGAffineTransform: scaleTransform] forKey: kCIInputTransformKey];
sourceImage = transformFilter.outputImage;
// translate the frame to make it's origin start at (0, 0)
CGAffineTransform translation = CGAffineTransformMakeTranslation(0, -inset * scale);
[transformFilter setValue:sourceImage forKey: kCIInputImageKey];
[transformFilter setValue: [NSValue valueWithCGAffineTransform: translation] forKey: kCIInputTransformKey];
sourceImage = transformFilter.outputImage;

Video-capture vertical output iOS

I am trying to capture video with PBJVision.
I set up camera like this
vision.cameraMode = PBJCameraModeVideo;
vision.cameraOrientation = PBJCameraOrientationPortrait;
vision.outputFormat = PBJOutputFormatWidescreen;
And this produces output 1280x720 where 1280 is width.
Setting orientation to Landscape ROTATES the stream.
I have been trying to record video with GPUImage, and there I can
videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset1280x720
cameraPosition:AVCaptureDevicePositionBack];
videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
_movieWriter = [[GPUImageMovieWriter alloc] initWithMovieURL:_movieURL size:CGSizeMake(720.0, 1280.0)];
So that i get vertical output.
I would like to achieve vertical output for PBJVision, because I experience problems with GPUImage writing video to disk. (I will make another question for that).
What method/property of AVFoundation is responsible for giving the vertical output instead of horizontal?
Sorry for the question, I have been googling 2 days - can't find the answer.
I was having the same issue. I changed the output format to vision.outputFormat = PBJOutputFormatPreset; and I'm getting that portrait/vertical output.

insertEmptyTimeRange to AVMutableCompositionTrack not working

I'm stitching videos together in an AVMutableCompositionTrack, using this:
AVMutableVideoCompositionLayerInstruction *passThroughLayer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
I'm also adding a CALayer with text and images to the composition, using an animationLayer.
At the beginning, I add 5 seconds of nothing to insert a title using insertEmptyTimeRange.
Up to here, everything's working fine.
Now I want to add some «nothing» to the end of the video, using insertEmptyTimeRange again - but that fails miserably.
CMTime creditsDuration = CMTimeMakeWithSeconds(5, 600);
CMTimeRange creditsRange = CMTimeRangeMake([[compositionVideoTrack asset] duration], creditsDuration);
[compositionVideoTrack insertEmptyTimeRange:creditsRange];
[compositionAudioTrack insertEmptyTimeRange:creditsRange];
NSLog(#"credit-range %f from %f", CMTimeGetSeconds(creditsRange.duration), CMTimeGetSeconds(creditsRange.start));
NSLog(#"Total duration %f", CMTimeGetSeconds([[compositionVideoTrack asset] duration]));
The insert-points are correct (first NSLog), but the total duration won't get extended...
Any ideas what I could be doing wrong?
Turns out, it seems to be impossible to add an empty timerange to the end of an AVMutableComposition.
This answer saved my life: AVMutableComposition of a Solid Color with No AVAsset

Scale and crop CMSampleBufferRef

I am using AvFoundation & AVCaptureVideoDataOutputSampleBufferDelegate to record a video.
I need to implement Zoom functionality in the video being recorded. I am using the following delegate method.
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
I am using this for getting video frames because i need to add text and images later on it before the appending it to the AVAssetWriterInput, using
[assetWriterVideoIn appendSampleBuffer:sampleBuffer]
The only way i can think to perform zoom is to scale and crop the "(CMSampleBufferRef)sampleBuffer" that i get from the delegate method.
Please help me out on this. I need to know the possible ways to scale and crop "CMSampleBufferRef".
One solution is to convert the CMSampleBuffer ref to a CIImage, then scale that and write it back to CVPixelBufferRef and append that.
You can see how to do that here which contains the code structure.
Adding filters to video with AVFoundation (OSX) - how do I write the resulting image back to AVWriter?
Another alternative is to scale the video using Layer Instructions like:
AVMutableVideoCompositionLayerInstruction *layerInstruction =
[AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstruction];
layerInstruction.trackID = mutableCompositionTrack.trackID;
[layerInstruction setTransform:CGAffineTransformMakeScale(2.0f,2.0f) atTime:kCMTimeZero];
This tells the composition to scale the mutableCompositionTrack (or whatever variable name you use for the track) by a factor of 2.0 starting at the beginning of video.
Now when you composite the video, add the array of layer intructions and you'll get your scaling without needing to worry about manipulating CMSampleBuffer (it will also be a lot faster).
AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
videoComposition.renderSize = CGSizeMake(1280, 720);
videoComposition.frameDuration = CMTimeMake(1, 30);
videoComposition.instructions = #[_instructions];

Can we know the naturalSize of a video directly from the file?

I need the information about naturalSize of the video without playing it in the moviePlayer as my ViewController loads.
I have the file in the device and I know its path I just need to know its naturalSize.
You can, you have to create an AVAsset first and then check its natural size property, here is a reference...and here is an example
AVAsset *asset = [AVAsset assetWithUrl:..];
CGSize s= asset.naturalSize
hope it helps

Resources