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

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

本文主要內容來自 AVFoundation Programming Guideios

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

資源的表示方式

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

image

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

軌道用 AVAssetTrack 的實例表示,以下圖所示。在典型的簡單狀況下,一個軌道表示音頻份量,另外一個表示視頻份量; 在複雜的組合中,音頻和視頻可能有多個重疊的軌道。框架

image

軌道具備許多屬性,例如其類型(視頻或音頻),視頻和/或音頻特徵,元數據和時間軸。軌道有一個數組用來描述其格式。該數組包含一組 CMFormatDescription 對象,每一個對象描述軌道對應的媒體樣本的格式。包含統一媒體的軌道(好比,都使用相同的設置來編碼)將提供一個計數爲 1 的數組。ide

軌道自己能夠被劃分紅多段,由 AVAssetTrackSegment 的實例表示。段是從源到資產軌道時間軸的時間映射。函數

時間的表示

AVFoundation 中用來表示時間的數據結構主要來自於 Core Media 框架。測試

用 CMTime 表示時長

CMTime 是一個 C 的數據結構,它表示時間爲有理數,包含分子和分母。分子表示時間值,分母表示時間刻度,分子除以分母則表示時間,單位爲秒。好比當時間刻度是 10,則表示每一個單位的時間值表示 1/10 秒。最經常使用的時間刻度是 600,由於在場景的場景中,咱們用 24 fps 的電影,30 fps 的 NTSC,25 fps 的 PAL。使用 600 的時間刻度,能夠準確地表示這些系統中的任何數量的幀。ui

除了簡單的時間值以外,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);
}
相關文章
相關標籤/搜索