最近在做視頻開發(fā),避不開就是會(huì)用到CMTime。根據(jù)網(wǎng)上之前的教程,CMTime的用法其實(shí)挺簡單的,例如:
Float64 seconds = 5;
int32_t preferredTimeScale = 600;
CMTime inTime = CMTimeMakeWithSeconds(seconds, preferredTimeScale);
CMTimeShow(inTime);
然后告訴你seconds是時(shí)長,preferredTimeScale是幀率。
int64_t value = 10000;
int32_t preferredTimeScale = 600;
CMTime inTime = CMTimeMake(value, preferredTimeScale);
CMTimeShow(inTime);
這里value表示視頻的幀數(shù),preferredTimeScale表示每秒的幀數(shù)。所以這里seconds是 10000/600 = 16.667
OK,以上其實(shí)理解起來沒問題,但是當(dāng)我們?cè)谔幚硪曨l的時(shí)候,常常要把后面的timeScale寫成600:
let sTime = CMTime(seconds: starSeconds, preferredTimescale: 600)
那么這里就有個(gè)問題:如果timeScale表示的幀率,這里的意思是視頻每秒的幀率是600幀么??
我們知道人眼可識(shí)別的幀率24幀就夠了,iPhone手機(jī)拍攝幀率為60fps,部分安卓手機(jī)的幀率甚至只有30fps。那么這里為什么要設(shè)置為600呢?
重新去查Apple的文檔,看到里面這么解釋:
CMTime
is a C structure that represents time 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. You frequently use a timescale of 600, because this is a multiple of several commonly used frame rates: 24 fps for film, 30 fps for NTSC (used for TV in North America and Japan), and 25 fps for PAL (used for TV in Europe). Using a timescale of 600, you can exactly represent any number of frames in these systems.
這里的意思是使用600幀,可以兼容各種視頻幀率(24fps, 30fps, 25fps等),是這些幀率的最小公倍數(shù)。不過這并不能解釋之前的困惑,設(shè)置成600以后,視頻的幀率真的達(dá)到600fps了么?這樣子GPU在處理照片的時(shí)候不會(huì)出現(xiàn)問題嗎?
那么我們?cè)賮碇匦抡J(rèn)識(shí)下這個(gè)CMTime吧!
假設(shè)我們需要在視頻文件中精確地指定一個(gè)時(shí)刻,比如35:06。通常的方法是把時(shí)間表示為一個(gè)雙精度的浮點(diǎn)數(shù)據(jù),比如:NSTimeInterval t = 2106.0; 那這個(gè)方法在大多數(shù)情況下是沒有問題的,但是當(dāng)我們把非常長的時(shí)間段劃分成非常小的切片時(shí),就會(huì)出現(xiàn)問題。不直接進(jìn)行浮點(diǎn)類型的運(yùn)算,而是把一個(gè)double類型可以容納大約16位有效數(shù)字(十進(jìn)制)的8個(gè)字節(jié)的內(nèi)存空間(在其他通用平臺(tái)上,sizeof(NSTimeInterval) == sizeof(Float64) == sizeof(double) == 8)。 再次普及double浮點(diǎn)型數(shù)據(jù)的換算過程和推算原理
浮點(diǎn)數(shù)存在一個(gè)大問題:重復(fù)操作(加法,乘法等)導(dǎo)致不精確的積累,于是在視頻時(shí)長很長的時(shí)候這個(gè)差異會(huì)被無限放大,從而在同步多個(gè)媒體流時(shí)可能導(dǎo)致錯(cuò)誤。
這里舉個(gè)栗子。一百萬個(gè)0.000001相加,結(jié)果約為1.0000000000079181。該錯(cuò)誤是由于1e-6不能以我們使用的double類型精確的表示,所以我們改為使用二進(jìn)制近似位,它的低有效位不同。這并不是一個(gè)大問題,但是當(dāng)你在運(yùn)行一個(gè)HTTP流服務(wù)器的時(shí)候,那么你可能會(huì)無限期的每秒去積累這種不精確度。
這就促使我們?nèi)フ业揭环N更精確表達(dá)時(shí)間的方式,通過消除double類型和他們固有的不精確性(不說他們的硬編碼舍入行為)。
CMTime
雖然Apple已經(jīng)有很多數(shù)據(jù)結(jié)構(gòu)來表示Mac和iOS平臺(tái)上的時(shí)間,但是在iOS4和Mac OS X 10.7 推出的時(shí)候,加上了CMTime和CMTimeRange。CMTime的類型定義如下:
typedef struct
{
CMTimeValue value;
CMTimeScale timescale;
CMTimeFlags flags;
CMTimeEpoch epoch;
} CMTime;
public typealias CMTimeValue = Int64
public typealias CMTimeScale = Int32
顯然,CMTime定義是一個(gè)C語言的結(jié)構(gòu)體,CMTime是以分?jǐn)?shù)的形式表示時(shí)間,value表示分子,timescale表示分母,flags是位掩碼,表示時(shí)間的指定狀態(tài)。
這里value,timescale是分別以64位和32位整數(shù)來存儲(chǔ)的,我們從上文已經(jīng)知道,這樣可以避免double類型帶來的精度丟失。另外,通過用64位整數(shù)來表示分子,我們可以為每個(gè)timescale表示90億個(gè)不同的正值,最多19位唯一的十進(jìn)制數(shù)字。
timescale
那么timescale又是什么? 它表示每秒分割的“切片”數(shù)。CMTime的整體精度就是受到這個(gè)限制的。比如:
如果timescale為1,則不能有對(duì)象表示小于1秒的時(shí)間戳,并且時(shí)間戳以1秒為增量。類似的,如果timescale是1000,則每秒被分割成1000個(gè),并且該value表示我們要顯示的毫秒數(shù)。
所以當(dāng)你試圖表示0.5秒的時(shí)候,你千萬不能這么寫:
CMTime interval = CMTimeMakeWithSeconds(0.5, 1);
這里interval實(shí)際上是0 而不是0.5。
所以為了能讓你選擇合理的時(shí)間尺度確保不被截?cái)啵珹pple建議我們使用600。如果你需要對(duì)音頻文件進(jìn)行更精確的所以,你可以把timescale設(shè)為60,000或更高。這里64位 value的好處就是,你仍然可以用這種方式來明確的表示580萬年的增量,即1/60,000秒。
所以,這里可以得出結(jié)論:
timescale只是為了保證時(shí)間精度而設(shè)置的幀率,并不一定是視頻最后實(shí)際的播放幀率。
相關(guān)資料:
Understanding CMTime