短視訊從無到有 (三)CMTime詳解
CMTime定義
通常開發者認為時間的呈現格式應該是浮點資料,我們一般使用NSTimeInterval,實際上它是簡單的雙精度double型別,可以表示不同場景中的時間。實際上,AV Foundation在AVAudioPlayer和AVAudioRecorder中處理時間問題時本身也會使用這個型別。雖然很多通用的開發環境使用雙精度型別無法應用於更多的高階時基媒體的開發中。比如,一個單一舍入錯誤就會導致丟幀或音訊丟失。於是蘋果在Core Media框架中定義了CMTime資料型別作為時間的格式,型別定義如下:
typedef struct
CMTimeValue value;
CMTimeScale timescale;
CMTimeFlags flags;
CMTimeEpoch epoch;
} CMTime;
複製程式碼
上面結構中最相關的三個元件是value、timescale和flags。CMTimeValue和CMTimeScale分別是64位和32位有符號整型變數,是CMTime元素的分數形式。CMTimeFlags是一個位掩碼用以表示時間的指定狀態,比如判斷資料是否有效、不確定或是否出現舍入值等。CMTime例項可標記特定的時間點或用於表示持續時間。
CMTime建立
有多種方法可以建立CMTime例項,不過最常見的方法是使用CMTimeMake函式,指定一個64位的value引數和32位的timescale引數,比如,建立一個代表5s的CMTime表示式有下面幾種不同的方式:
CMTime t1 =CMTimeMake(5,1);
CMTime t2 =CMTimeMake(3000,600);
CMTime t3 =CMTimeMake(5000,1000);
複製程式碼
使用CMTimeShow函式將這些值列印到控制檯會出現如下結果:
{5/1 = 5.000}
{3000/600 = 5.000}
{5000/1000 = 5.000}
複製程式碼
注:在處理視訊內容時常見的時間刻度為600,這是大部分常用視訊幀率24FPS、25FPS、30FPS的公倍數。音訊資料常見的時間刻度就是取樣率,比如44 100(44.1kHZ)或48 000(kHZ)。
CMTime計算
相加
CMTime t4 =CMTimeAdd(t1,t2);
複製程式碼
相減
CMTime t5 =CMTimeSubtract(t3,t1);
複製程式碼
注:相乘是CMTimeMultiply 大家可自己參閱CMTime檔案。
CMTimeMakeWithSeconds 與 CMTimeMake的區別
這兩個的區別是 :CMTimeMake(a,b) a當前第幾幀,b每秒鐘多少幀,當前播放時間a/b。 CMTimeMakeWithSeconds(a,b) a當前時間,b每秒鐘多少幀。下面用程式碼來說明它:
Float64 seconds = 3;
int32_t preferredTimeScale = 600; //處理視訊內容事常見的時間刻度為600,這是大部分視訊幀率24fps、25fps、30fps的公倍數。音訊資料常見的時間刻度就是取樣率,譬如44 100(44.1kHZ)或48 000(48kHZ)。
CMTime inTime = CMTimeMakeWithSeconds(seconds,preferredTimeScale);
CMTimeShow(inTime);
輸出: {1800/600 = 3.000}
代表當前時間為3s,視訊一共有1800幀,一秒鐘600幀
CMTimeMake
int64_t value = 1100;
int32_t preferredTimeScale = 600;
CMTime inTime = CMTimeMake(value,preferredTimeScale);
CMTimeShow(inTime);
OUTPUT: {1100/600 = 1.833}
代表時間為1.833s,視訊一共1100幀,每秒600幀 。 其實,在我們這裡,我們關心的只有最後那個總時間。 換句話說,我們把那個(0,600)換成(x,600) 是沒問題的。
requestedTimeTolerance
那麼為什麼,效果差了這麼多呢?我們可以把CGImageRef image = [gen copyCGImageAtTime:time
actualTime:&actualTime
error:&error];
返回的 actualTime實際時間輸出一下
CMTimeShow(actualTime)
就會發現時間差的很遠。 這是為什麼呢? 首先 actualTime 使用的 fps * 1000 當每秒的幀率,順便普及下fps的獲取方法 :float fps = [[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] nominalFrameRate];
然後我們來思考為什麼要有 requestTime 和 actualTime 呢? 開始對這個api 很困惑: 為什麼我request的時間 不等於actualTime?後來查了一下檔案,當你想要一個時間點的某一幀的時候,它會在一個範圍內找,如果有快取,或者有在索引內的關鍵幀,就直接返回,從而優化效能。
這個定義範圍的API就是 requestedTimeToleranceAfter 和 requestedTimeToleranceBefore
如果我們要精確時間,那麼可以設定:
gen.requestedTimeToleranceAfter = kCMTimeZero;
gen.requestedTimeToleranceBefore = kCMTimeZero;
複製程式碼
注:在使用AVAssetImageGenerator獲取視訊縮圖的時候,發現設定gen.requestedTimeToleranceAfter = kCMTimeZero;
和gen.requestedTimeToleranceAfter = kCMTimeZero;
會有機率失敗,去掉這兩個設定就好了。
CMTimeRange
Core Media框架還為時間範圍提供了一個資料型別,稱為CMTimeRange,它在有關資源編輯的API中扮演著重要的角色,定義如下:
typedef struct
{
CMTime start;
CMTime duration;
} CMTimeRange;
複製程式碼
其中start表示時間的起點的CMTime值,duratin表示時間範圍的持續時長的CMTime值。一般使用CMTimeRangeMake和CMTimeRangeFromTimeToTime建立如下:
CMTimeRange timeRange1 = CMTimeRangeMake(t1,t2);
CMTimeRange timeRange2 = CMTimeRangeFromTimeToTime(t4,t3);
複製程式碼
CMTimeRange的交集和並集
//0-5s
CMTimeRange range1 =CMTimeRangeMake(kCMTimeZero,CMTimeMake(5,1));
//2-5s
CMTimeRange range2 =CMTimeRangeMake(CMTimeMake(2,1),1));
//交叉時間範圍 2-5s
CMTimeRange intersectionRange =CMTimeRangeGetIntersection(range1,range2);
CMTimeRangeShow(intersectionRange);
//總和時間範圍 7s
CMTimeRange unionRange =CMTimeRangeGetUnion(range1,range2);
CMTimeRangeShow(unionRange);
複製程式碼
列印結果如下:
{{2/1 = 2.000},{3/1 = 3.000}}
{{0/1 = 0.000},{7/1 = 7.000}}
複製程式碼