preferredTimescale in CMTimeMakeWithSeconds - ios

I am implementing custom camera and for that I want to set exposure duration.
My code for setting slider's properties is-
slider.maximumValue = Float(CMTimeGetSeconds(camera.activeFormat.maxExposureDuration))
slider.minimumValue = Float(CMTimeGetSeconds(camera.activeFormat.minExposureDuration))
Now the problem comes while setting the exposure time whenever the slider is changed.
My code for that looks like this -
change(duration: CMTimeMakeWithSeconds(Double(slider.value), 600), iso: AVCaptureISOCurrent)
But in
func CMTimeMakeWithSeconds(_ seconds: Float64, _ preferredTimescale: Int32) -> CMTime
I am confused with preferredTimescale, and what should be its value, it's working fine with 600, but what is ideal value.

You should view the discussion here
Preferred time scale will add a denominator to your seconds, meaning if you set 5 seconds and preferredTimeScale of 60 it will be 1/12 of a second

Related

Trying to append CVPixelBuffers to AVAssetWriterInputPixelBufferAdaptor at the intended framerate

I'm trying to append CVPixelBuffers to AVAssetWriterInputPixelBufferAdaptor at the intended framerate, but it seems to be too fast, and my math is off. This isn't capturing from the camera, but capturing changing images. The actual video is much to fast than the elapsed time it was captured.
I have a function that appends the CVPixelBuffer every 1/24 of a second. So I'm trying to add an offset of 1/24 of a second to the last time.
I've tried:
let sampleTimeOffset = CMTimeMake(value: 100, timescale: 2400)
and:
let sampleTimeOffset = CMTimeMake(value: 24, timescale: 600)
and:
let sampleTimeOffset = CMTimeMakeWithSeconds(0.0416666666, preferredTimescale: 1000000000)
I'm adding onto the currentSampleTime and appending like so:
self.currentSampleTime = CMTimeAdd(currentSampleTime, sampleTimeOffset)
let success = self.assetWriterPixelBufferInput?.append(cv, withPresentationTime: currentSampleTime)
One other solution I thought of is get the difference between the last time and the current time, and add that onto the currentSampleTime for accuracy, but unsure how to do it.
I found a way to accurately capture the time delay by comparing the last time in milliseconds compared to the current time in milliseconds.
First, I have a general current milliseconds time function:
func currentTimeInMilliSeconds()-> Int
{
let currentDate = Date()
let since1970 = currentDate.timeIntervalSince1970
return Int(since1970 * 1000)
}
When I create a writer, (when I start recording video) I set a variable in my class to the current time in milliseconds:
currentCaptureMillisecondsTime = currentTimeInMilliSeconds()
Then in my function that's supposed to be called 1/24 of a second is not always accurate, so I need to get the difference in milliseconds between when I started writing, or my last function call.
Do a conversion of milliseconds to seconds, and set that to CMTimeMakeWithSeconds.
let lastTimeMilliseconds = self.currentCaptureMillisecondsTime
let nowTimeMilliseconds = currentTimeInMilliSeconds()
let millisecondsDifference = nowTimeMilliseconds - lastTimeMilliseconds
// set new current time
self.currentCaptureMillisecondsTime = nowTimeMilliseconds
let millisecondsToSeconds:Float64 = Double(millisecondsDifference) * 0.001
let sampleTimeOffset = CMTimeMakeWithSeconds(millisecondsToSeconds, preferredTimescale: 1000000000)
I can now append my frame with the accurate delay that actually occurred.
self.currentSampleTime = CMTimeAdd(currentSampleTime, sampleTimeOffset)
let success = self.assetWriterPixelBufferInput?.append(cv, withPresentationTime: currentSampleTime)
When I finish writing the video and I save it to my camera roll, it is the exact duration from when I was recording.

Getting Duration of AVPlayer in minutes

I am trying to get the duration of an AVPlayer asset in Hours, Minutes, Seconds. I am able to get the time but it seems to be in seconds and milliseconds.
This is how I get the time:
let duration : CMTime = (player.currentItem!.asset.duration)!
let seconds : Float64 = CMTimeGetSeconds(duration)
I am then applying that to a slider using
slider.maximumValue = Float(seconds)
The outcome of this obviously gives me the duration in seconds however I want to be able to use the duration to set the maximumValue of my slider for video clips which may be under a minute.
For Example: My code above returns 30.865 for a 30 second clip. I need it to return 0.30
This ended up working for me:
let duration : CMTime = (player.currentItem!.asset.duration)!
let timeInMinutes = Float(duration.value)

AVAsset video adjust duration

Given a list of CMSampleBuffers that have been read in from an asset, I want to adjust the duration of the asset so that it's half length (twice the speed) of the original.
Currently my function for generating new time stamps looks like:
func adjustTimeStampsForBuffers(buffers: [CMSampleBuffer]) -> [CMTime] {
let frameCount = buffers.count
// self.duration is CMTimeGetSeconds(asset.duration)
let increment = Float(self.duration / 2) / Float(frameCount)
return Array(0.stride(to: frameCount, by: 1)).enumerate().map {
let seconds: Float64 = Float64(increment) * Float64($0.index)
return CMTimeMakeWithSeconds(seconds, self.asset.duration.timescale)
}
}
however this doesn't seem to work and the outputted assets are in fact twice the length, not half. Can anybody point out where I'm going wrong?
Edit:
Thanks to #sschale, here's my final answer:
func adjustTimeStampsForBuffers(buffers: [CMSampleBuffer]) -> [CMTime] {
return buffers.map {
let time = CMSampleBufferGetPresentationTimeStamp($0)
return CMTimeMake(time.value, time.timescale * 2)
}
}
Instead of calculating new values, the timestamp is adjusted instead.
Based on my reading of the docs, it looks like that self.asset.duration.timescale may be the key here, as changing it will influence the whole file (if I'm understanding the reference you're making that that timescale is for the whole file, or maybe you need to adjust it in each of the buffers).
See here for more info as well.
Relevant section:
A CMTime is represented as a rational number, with a numerator (an
int64_t value), and a denominator (an int32_t timescale).
Conceptually, the timescale specifies the fraction of a second each
unit in the numerator occupies. Thus if the timescale is 4, each unit
represents a quarter of a second; if the timescale is 10, each unit
represents a tenth of a second, and so on. In addition to a simple
time value, a CMTime can represent non-numeric values: +infinity,
-infinity, and indefinite. Using a flag CMTime indicates whether the time been rounded at some point.
CMTimes contain an epoch number, which is usually set to 0, but can be
used to distinguish unrelated timelines: for example, it could be
incremented each time through a presentation loop, to differentiate
between time N in loop 0 from time N in loop 1

iOS Movement of UISlider thumb to track the progress of the movie

I'm implementing a video player in which I've taken UISlider to track the progress of the movie. This would mean that the thumb of the slider should be at the max value when the movie finishes.
I'm not able to achieve this. Movie finishes before thumb reaches at the end. Could anybody please help me understand what could be the issue?
This is the code that I've written:
//I'm setting the min, max values of the UISlider and calculating the current value
progressBar.minimumValue = 0.0;
progressBar.maximumValue = (float)movie.duration;
progressBar.value = ((float)movie.currentPlaybackTime/(float)movie.duration) * progressBar.frame.size.width;
Just use progressBar.value = (float)movie.currentPlaybackTime; instead of progressBar.value = ((float)movie.currentPlaybackTime/(float)movie.duration) * progressBar.frame.size.width;
Max value should be equal to the frame width:
progressBar.minimumValue = 0.0;
progressBar.maximumValue = progressBar.frame.size.width;
progressBar.value = ((float)movie.currentPlaybackTime/(float)movie.duration) * progressBar.frame.size.width;
Shouldn't it just be: progressBar.value = (float)movie.currentPlaybackTime ?
I mean, lets say movie duration is 60 sec, and the movie is at 15 sec (this is currentplaybacktime, I guess).
so:
progressbar min value is 0
progressbar max is 60
progressbar value should be between 0 and 60, and it is the value where the film is at right now: its 15.
Edit: You should also use: - setValue:animated:
Another idea: maybe you are not on the main thread. Make sure you are on the main thread when making ui changes.

Trying to understand CMTime

I have seen some examples of CMTime (Three separate links), but I still don't get it. I'm using an AVCaptureSession with AVCaptureVideoDataOutput and I want to set the max and min frame rate of the the output. My problem is I just don't understand the CMTime struct.
Apparently CMTimeMake(value, timeScale) should give me value frames every 1/timeScale seconds for a total of value/timeScale seconds, or am I getting that wrong?
Why isn't this documented anywhere in order to explain what this does?
If it does truly work like that, how would I get it to have an indefinite number of frames?
If its really simple, I'm sorry, but nothing has clicked just yet.
A CMTime struct represents a length of time that is stored as rational number (see CMTime Reference). CMTime has a value and a timescale field, and represents the time value/timescale seconds .
CMTimeMake is a function that returns a CMTime structure, for example:
CMTime t1 = CMTimeMake(1, 10); // 1/10 second = 0.1 second
CMTime t2 = CMTimeMake(2, 1); // 2 seconds
CMTime t3 = CMTimeMake(3, 4); // 3/4 second = 0.75 second
CMTime t4 = CMTimeMake(6, 8); // 6/8 second = 0.75 second
The last two time values t3 and t4 represent the same time value, therefore
CMTimeCompare(t3, t4) == 0
If you set the videoMinFrameDuration of a AVCaptureSession is does not make a difference if you set
connection.videoMinFrameDuration = CMTimeMake(1, 20); // or
connection.videoMinFrameDuration = CMTimeMake(2, 40);
In both cases the minimum time interval between frames is set to 1/20 = 0.05 seconds.
My experience differs.
For let testTime = CMTime(seconds: 3.83, preferredTimescale: 100)
If you set a breakpoint and look in the debugger side window it says:
"383 100ths of a second"
Testing by seeking to a fixed offset in a video in AVPlayer has confirmed this.
So put the actual number of seconds in the seconds field, and the precision in the preferredTimescale field. So 100 means precision of hundredths of a second.
Doing
let testTime = CMTime(seconds: 3.83, preferredTimescale: 100)
Still seeks to the same place in the video, but it displays in the debugger side window as "3833 1000ths of a second"
Doing
let testTime = CMTime(seconds: 3.83, preferredTimescale: 1)
Does not seek to the same place in the video, because it's been truncated, and it displays in the debugger side window as "3 seconds". Notice that the .833 part has been lost due to the preferredTimescale.
CMTime(seconds: value, timescale: scale)
means value/scale in a just one second

Resources