本文轉自:AVAudioFoundation(6):時間和媒體表示 | www.samirchen.com
本文主要內容來自 AVFoundation Programming Guide。
基于時間的音視頻數據,例如電影文件或視頻流,在 AVFoundation 框架中用 AVAsset
來表示。AV Foundation 用于表示時間和媒體的幾個底層數據結構,來自 Core Media 框架。
資源的表示方式
AVAsset
是 AVFoundation 框架中的核心類。它對基于時間的音視頻數據的進行了抽象,例如電影文件或視頻流。主要關系如下圖所示。在許多情況下,我們需要使用其子類之一。
Asset 包含了將在一起顯示或處理的多個軌道,每個軌道均包含(但不限于)音頻、視頻、文本、可隱藏字幕和字幕。 Asset 提供了關于資源的完整信息,比如時長、標題,以及渲染時的提示、視頻源尺寸等等。 資產還可以包含由 AVMetadataItem
表示的元數據。
軌道用 AVAssetTrack
的實例表示,如下圖所示。在典型的簡單情況下,一個軌道表示音頻分量,另一個表示視頻分量; 在復雜的組合中,音頻和視頻可能有多個重疊的軌道。
軌道具有許多屬性,例如其類型(視頻或音頻),視頻和/或音頻特征,元數據和時間軸。軌道有一個數組用來描述其格式。該數組包含一組 CMFormatDescription
對象,每個對象描述軌道對應的媒體樣本的格式。包含統一媒體的軌道(比如,都使用相同的設置來編碼)將提供一個計數為 1 的數組。
軌道本身可以被劃分成多段,由 AVAssetTrackSegment
的實例表示。段是從源到資產軌道時間軸的時間映射。
時間的表示
AVFoundation 中用來表示時間的數據結構主要來自于 Core Media 框架。
用 CMTime 表示時長
CMTime
是一個 C 的數據結構,它表示時間為有理數,包含分子和分母。分子表示時間值,分母表示時間刻度,分子除以分母則表示時間,單位為秒。比如當時間刻度是 10,則表示每個單位的時間值表示 1/10 秒。最常用的時間刻度是 600,因為在場景的場景中,我們用 24 fps 的電影,30 fps 的 NTSC,25 fps 的 PAL。使用 600 的時間刻度,可以準確地表示這些系統中的任何數量的幀。
除了簡單的時間值之外,CMTime
結構可以表示非數值的值:正無窮大,負無窮大和無定義。 它也可以指示時間是否在某一點被舍入,并且它能保持一個紀元數字。
使用 CMTime
下面是一些使用 CMTime
示例:
CMTime time1 = CMTimeMake(200, 2); // 200 half-seconds
CMTime time2 = CMTimeMake(400, 4); // 400 quarter-seconds
// time1 and time2 both represent 100 seconds, but using different timescales.
if (CMTimeCompare(time1, time2) == 0) {
NSLog(@"time1 and time2 are the same");
}
Float64 float64Seconds = 200.0 / 3;
CMTime time3 = CMTimeMakeWithSeconds(float64Seconds , 3); // 66.66... third-seconds
time3 = CMTimeMultiply(time3, 3);
// time3 now represents 200 seconds; next subtract time1 (100 seconds).
time3 = CMTimeSubtract(time3, time1);
CMTimeShow(time3);
if (CMTIME_COMPARE_INLINE(time2, ==, time3)) {
NSLog(@"time2 and time3 are the same");
}
CMTime 的特殊值
Core Media 提供了一些關于 CMTime
的特殊值,比如:kCMTimeZero
,kCMTimeInvalid
,kCMTimePositiveInfinity
,kCMTimeNegativeInfinity
。要檢查一個表示非數值的 CMTime
是否是合法的,可以用 CMTIME_IS_INVALID
,CMTIME_IS_POSITIVE_INFINITY
,CMTIME_IS_INDEFINITE
這些宏。
CMTime myTime = <#Get a CMTime#>;
if (CMTIME_IS_INVALID(myTime)) {
// Perhaps treat this as an error; display a suitable alert to the user.
}
不要去拿一個 CMTime
和 kCMTimeInvalid
做比較。
用對象的方式使用 CMTime
如果要使用 Core Foundation 中的一些容器來存儲 CMTime
,我們需要進行 CMTime
和 CFDictionary
之間的轉換。這時我們需要用到 CMTimeCopyAsDictionary
和 CMTimeMakeFromDictionary
這些方法。我們還能用 CMTimeCopyDescription
來輸出描述 CMTime
的字符串。
紀元
通常 CMTime
中的 epoch
設置為 0,不過我們也可以設置它為其他值來區分不相關的時間軸。比如,我們可以在循環遍歷中遞增 epoch
的值,每一個 0 到 N 的循序遞增一下 epoch
來區分不同的輪回。
用 CMTimeRange 來表示時間范圍
CMTimeRange
表示的時間范圍包含兩個字段:開始時間(start)和時長(duration),這兩個字段都是 CMTime
類型。需要注意的是一個 CMTimeRange
不含開始時間加上時長算出來的那個時間點,即 [start, start + duration)
。
我們可以用 CMTimeRangeMake
和 CMTimeRangeFromTimeToTime
來創建 CMTimeRange
。但是這里有一些限制:
-
CMTimeRange
不能跨越不同的紀元。 -
CMTime
中的epoch
字段可能不為 0,但是我們只能對start
字段具有相同epoch
的CMTimeRange
執行相關操作(比如CMTimeRangeGetUnion
)。 - 表示
duration
的CMTime
結構中的epoch
應始終為 0,value
必須為非負數。
使用 CMTimeRange
Core Media 提供了一系列操作 CMTimeRange
的方法,比如 CMTimeRangeContainsTime
、CMTimeRangeEqual
、CMTimeRangeContainsTimeRange
、CMTimeRangeGetUnion
.
下面的代碼返回值永遠為 false
:
CMTimeRangeContainsTime(range, CMTimeRangeGetEnd(range))
CMTimeRange 的特殊值
Core Media 提供了 kCMTimeRangeZero
和 kCMTimeRangeInvalid
表示零長度 range 和錯誤 range。在很多情況下,CMTimeRange
結構可能無效,或者是零或不定式(如果其中一個 CMTime
字段是不確定的)。如果要測試 CMTimeRange
結構是否有效、零或不確定,可以使用一個適當的宏:CMTIMERANGE_IS_VALID
,CMTIMERANGE_IS_INVALID
,CMTIMERANGE_IS_EMPTY
或 CMTIMERANGE_IS_EMPTY
。
CMTimeRange myTimeRange = <#Get a CMTimeRange#>;
if (CMTIMERANGE_IS_EMPTY(myTimeRange)) {
// The time range is zero.
}
不要拿任何 CMTimeRange
和 kCMTimeRangeInvalid
做比較。
用對象的方式使用 CMTimeRange
如果要在 Core Foundation 提供的容器中使用 CMTimeRange
結構,則可以分別使用 CMTimeRangeCopyAsDictionary
和 CMTimeRangeMakeFromDictionary
將 CMTimeRange
結構轉換為 CFDictionary
類型。 還可以使用 CMTimeRangeCopyDescription
函數獲取 CMTimeRange
結構的字符串表示形式。
媒體的表示
視頻數據及其關聯的元數據在 AVFoundation 中由 Core Media 框架中的對象表示。 Core Media 使用 CMSampleBuffer
表示視頻數據。CMSampleBuffer
是一種 Core Foundation 風格的類型。CMSampleBuffer
的一個實例在對應的 Core Video pixel buffer 中包含了視頻幀的數據(參見 CVPixelBufferRef)。 我們可以使用 CMSampleBufferGetImageBuffer
從樣本緩沖區訪問 pixel buffer:
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(<#A CMSampleBuffer#>);
從 pixel buffer 中,我們可以訪問實際的視頻數據。
除了視頻數據之外,您還可以檢索視頻幀的其他方面的數據:
- 時間信息。我們可以分別使用
CMSampleBufferGetPresentationTimeStamp
和CMSampleBufferGetDecodeTimeStamp
獲得原始演示時間和解碼時間的準確時間戳。 - 格式信息。格式信息封裝在
CMFormatDescription
對象中。從格式描述中,可以分別使用CMVideoFormatDescriptionGetCodecType
和CMVideoFormatDescriptionGetDimensions
獲取像素類型和視頻尺寸。 - 元數據。元數據作為附件存儲在字典中。您使用
CMGetAttachment
檢索字典。如下面代碼所示:
CMSampleBufferRef sampleBuffer = <#Get a sample buffer#>;
CFDictionaryRef metadataDictionary = CMGetAttachment(sampleBuffer, CFSTR("MetadataDictionary", NULL);
if (metadataDictionary) {
// Do something with the metadata.
}
將 CMSampleBuffer 轉換為 UIImage
以下代碼顯示如何將 CMSampleBuffer
轉換為 UIImage
對象。 使用前,我們要仔細考慮對應的需求。 執行轉換是比較昂貴的操作。例如,從每隔一秒鐘拍攝的視頻數據幀創建靜止圖像是合適的,但不應該使用它來實時操縱來自錄制設備的每一幀視頻。
// Create a UIImage from sample buffer data.
- (UIImage *)imageFromSampleBuffer:(CMSampleBufferRef)sampleBuffer {
// Get a CMSampleBuffer's Core Video image buffer for the media data
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// Lock the base address of the pixel buffer
CVPixelBufferLockBaseAddress(imageBuffer, 0);
// Get the number of bytes per row for the pixel buffer
void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
// Get the number of bytes per row for the pixel buffer
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
// Get the pixel buffer width and height
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
// Create a device-dependent RGB color space
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// Create a bitmap graphics context with the sample buffer data
CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8,
bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
// Create a Quartz image from the pixel data in the bitmap graphics context
CGImageRef quartzImage = CGBitmapContextCreateImage(context);
// Unlock the pixel buffer
CVPixelBufferUnlockBaseAddress(imageBuffer,0);
// Free up the context and color space
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
// Create an image object from the Quartz image
UIImage *image = [UIImage imageWithCGImage:quartzImage];
// Release the Quartz image
CGImageRelease(quartzImage);
return (image);
}