AVAudioFoundation(6):時間和媒體表示

本文轉自:AVAudioFoundation(6):時間和媒體表示 | www.samirchen.com

本文主要內容來自 AVFoundation Programming Guide

基于時間的音視頻數據,例如電影文件或視頻流,在 AVFoundation 框架中用 AVAsset 來表示。AV Foundation 用于表示時間和媒體的幾個底層數據結構,來自 Core Media 框架。

資源的表示方式

AVAsset 是 AVFoundation 框架中的核心類。它對基于時間的音視頻數據的進行了抽象,例如電影文件或視頻流。主要關系如下圖所示。在許多情況下,我們需要使用其子類之一。

image

Asset 包含了將在一起顯示或處理的多個軌道,每個軌道均包含(但不限于)音頻、視頻、文本、可隱藏字幕和字幕。 Asset 提供了關于資源的完整信息,比如時長、標題,以及渲染時的提示、視頻源尺寸等等。 資產還可以包含由 AVMetadataItem 表示的元數據。

軌道用 AVAssetTrack 的實例表示,如下圖所示。在典型的簡單情況下,一個軌道表示音頻分量,另一個表示視頻分量; 在復雜的組合中,音頻和視頻可能有多個重疊的軌道。

image

軌道具有許多屬性,例如其類型(視頻或音頻),視頻和/或音頻特征,元數據和時間軸。軌道有一個數組用來描述其格式。該數組包含一組 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 的特殊值,比如:kCMTimeZerokCMTimeInvalidkCMTimePositiveInfinitykCMTimeNegativeInfinity。要檢查一個表示非數值的 CMTime 是否是合法的,可以用 CMTIME_IS_INVALIDCMTIME_IS_POSITIVE_INFINITYCMTIME_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.
}

不要去拿一個 CMTimekCMTimeInvalid 做比較。

用對象的方式使用 CMTime

如果要使用 Core Foundation 中的一些容器來存儲 CMTime,我們需要進行 CMTimeCFDictionary 之間的轉換。這時我們需要用到 CMTimeCopyAsDictionaryCMTimeMakeFromDictionary 這些方法。我們還能用 CMTimeCopyDescription 來輸出描述 CMTime 的字符串。

紀元

通常 CMTime 中的 epoch 設置為 0,不過我們也可以設置它為其他值來區分不相關的時間軸。比如,我們可以在循環遍歷中遞增 epoch 的值,每一個 0 到 N 的循序遞增一下 epoch 來區分不同的輪回。

用 CMTimeRange 來表示時間范圍

CMTimeRange 表示的時間范圍包含兩個字段:開始時間(start)和時長(duration),這兩個字段都是 CMTime 類型。需要注意的是一個 CMTimeRange 不含開始時間加上時長算出來的那個時間點,即 [start, start + duration)

我們可以用 CMTimeRangeMakeCMTimeRangeFromTimeToTime 來創建 CMTimeRange。但是這里有一些限制:

  • CMTimeRange 不能跨越不同的紀元。
  • CMTime 中的 epoch 字段可能不為 0,但是我們只能對 start 字段具有相同 epochCMTimeRange 執行相關操作(比如 CMTimeRangeGetUnion)。
  • 表示 durationCMTime 結構中的 epoch 應始終為 0,value 必須為非負數。

使用 CMTimeRange

Core Media 提供了一系列操作 CMTimeRange 的方法,比如 CMTimeRangeContainsTimeCMTimeRangeEqualCMTimeRangeContainsTimeRangeCMTimeRangeGetUnion.

下面的代碼返回值永遠為 false

CMTimeRangeContainsTime(range, CMTimeRangeGetEnd(range))

CMTimeRange 的特殊值

Core Media 提供了 kCMTimeRangeZerokCMTimeRangeInvalid 表示零長度 range 和錯誤 range。在很多情況下,CMTimeRange 結構可能無效,或者是零或不定式(如果其中一個 CMTime 字段是不確定的)。如果要測試 CMTimeRange 結構是否有效、零或不確定,可以使用一個適當的宏:CMTIMERANGE_IS_VALIDCMTIMERANGE_IS_INVALIDCMTIMERANGE_IS_EMPTYCMTIMERANGE_IS_EMPTY

CMTimeRange myTimeRange = <#Get a CMTimeRange#>;
if (CMTIMERANGE_IS_EMPTY(myTimeRange)) {
    // The time range is zero.
}

不要拿任何 CMTimeRangekCMTimeRangeInvalid 做比較。

用對象的方式使用 CMTimeRange

如果要在 Core Foundation 提供的容器中使用 CMTimeRange 結構,則可以分別使用 CMTimeRangeCopyAsDictionaryCMTimeRangeMakeFromDictionaryCMTimeRange 結構轉換為 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 中,我們可以訪問實際的視頻數據。

除了視頻數據之外,您還可以檢索視頻幀的其他方面的數據:

  • 時間信息。我們可以分別使用 CMSampleBufferGetPresentationTimeStampCMSampleBufferGetDecodeTimeStamp 獲得原始演示時間和解碼時間的準確時間戳。
  • 格式信息。格式信息封裝在 CMFormatDescription 對象中。從格式描述中,可以分別使用 CMVideoFormatDescriptionGetCodecTypeCMVideoFormatDescriptionGetDimensions 獲取像素類型和視頻尺寸。
  • 元數據。元數據作為附件存儲在字典中。您使用 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);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容