AVFoundation框架解析(十五)—— VAssetWriter和AVAssetReader的Timecode支持(二)

版本記錄

版本號 時間
V1.0 2017.09.01

前言

AVFoundation框架是ios中很重要的框架,所有與視頻音頻相關(guān)的軟硬件控制都在這個框架里面韧献,接下來這幾篇就主要對這個框架進(jìn)行介紹和講解。感興趣的可以看我上幾篇研叫。
1. AVFoundation框架解析(一)—— 基本概覽
2. AVFoundation框架解析(二)—— 實現(xiàn)視頻預(yù)覽錄制保存到相冊
3. AVFoundation框架解析(三)—— 幾個關(guān)鍵問題之關(guān)于框架的深度概括
4. AVFoundation框架解析(四)—— 幾個關(guān)鍵問題之AVFoundation探索(一)
5. AVFoundation框架解析(五)—— 幾個關(guān)鍵問題之AVFoundation探索(二)
6. AVFoundation框架解析(六)—— 視頻音頻的合成(一)
7. AVFoundation框架解析(七)—— 視頻組合和音頻混合調(diào)試
8. AVFoundation框架解析(八)—— 優(yōu)化用戶的播放體驗
9. AVFoundation框架解析(九)—— AVFoundation的變化(一)
10. AVFoundation框架解析(十)—— AVFoundation的變化(二)
11. AVFoundation框架解析(十一)—— AVFoundation的變化(三)
12. AVFoundation框架解析(十二)—— AVFoundation的變化(四)
13. AVFoundation框架解析(十三)—— 構(gòu)建基本播放應(yīng)用程序
14. AVFoundation框架解析(十四)—— VAssetWriter和AVAssetReader的Timecode支持(一)

AVAssetReader Reading Timecode

AVAssetReader對象用于獲取資產(chǎn)的媒體數(shù)據(jù)。 讀取存儲在時間碼軌道中的時間碼媒體樣本以與用于使用AVAssetReader讀取任何其他媒體(如音頻或視頻媒體)相同的方式執(zhí)行璧针。

將AVAssetReader對象與要讀取的資產(chǎn)分配后嚷炉,為時間碼軌道創(chuàng)建一個AVAssetReaderTrackOutput,然后調(diào)用 - (BOOL)startReading來準(zhǔn)備讀取器從資產(chǎn)讀取樣本緩沖區(qū)探橱。 然后將- (CMSampleBufferRef)copyNextSampleBuffer方法發(fā)送到軌道輸出對象以接收時間碼采樣申屹。

包含時間碼示例的返回的CMSampleBufferRef可以被解釋為應(yīng)用程序所需的绘证,例如,返回的幀號可以轉(zhuǎn)換為CVSMPTETime表示哗讥。 有關(guān)實用程序功能嚷那,請參閱本文檔的“時間碼實用程序函數(shù)”部分,該功能允許您執(zhí)行kCMTimeCodeFormatType_TimeCode32時間碼樣本格式類型的轉(zhuǎn)換杆煞。 檢索描述時間碼示例調(diào)用CMSampleBufferGetFormatDescription的格式詳細(xì)信息的格式說明魏宽。

下面代碼展示了如何為時間碼媒體軌道創(chuàng)建AVAssetReader對象和AVAssetReaderTrackOutput對象。

...
 
// Create asset reader
assetReader = [[AVAssetReader alloc] initWithAsset:localAsset error:&localError];
success = (assetReader != nil);
 
// Create asset reader output for the first timecode track of the asset
if (success) {
    AVAssetTrack *timecodeTrack = nil;
 
    // Grab first timecode track, if the asset has them
    NSArray *timecodeTracks = [localAsset tracksWithMediaType:AVMediaTypeTimecode];
    if ([timecodeTracks count] > 0)
        timecodeTrack = [timecodeTracks objectAtIndex:0];
 
    if (timecodeTrack) {
        timecodeOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:timecodeTrack outputSettings:nil];
        [assetReader addOutput:timecodeOutput];
    } else {
        NSLog(@"%@ has no timecode tracks", localAsset);
    }
}
 
...

下面提供了兩種方法决乎。 第一個演示如何從AVAssetReaderTrackOutput讀取采樣緩沖區(qū)队询,第二個演示如何檢索采樣數(shù)據(jù)并進(jìn)行解釋。 在這種情況下构诚,從本文檔的Timecode Utility功能部分調(diào)用其中一個時間碼實用程序函數(shù)蚌斩,將采樣數(shù)據(jù)轉(zhuǎn)換為CVSMPTETime,然后簡單地輸出值范嘱。

//  Read a Timecode Sample Buffer and print out the CVSMPTETime.

- (BOOL)startReadingAndPrintingOutputReturningError:(NSError **)outError
{
    BOOL success = YES;
    NSError *localError = nil;
 
    // Instruct the asset reader to get ready to do work
    success = [assetReader startReading];
 
    if (!success) {
        localError = [assetReader error];
    } else {
        CMSampleBufferRef currentSampleBuffer = NULL;
 
        while ((currentSampleBuffer = [timecodeOutput copyNextSampleBuffer])) {
            [self outputTimecodeDescriptionForSampleBuffer:currentSampleBuffer];
        }
 
        if (currentSampleBuffer) {
            CFRelease(currentSampleBuffer);
        }
    }
 
    if (!success && outError)
        *outError = localError;
 
    return success;
}
 
- (void)outputTimecodeDescriptionForSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
    CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
    CMFormatDescriptionRef formatDescription =  CMSampleBufferGetFormatDescription(sampleBuffer);
 
    if (blockBuffer && formatDescription) {
 
        size_t length = 0;
        size_t totalLength = 0;
        char *rawData = NULL;
 
        OSStatus status = CMBlockBufferGetDataPointer(blockBuffer, 0, &length, &totalLength, &rawData);
        if (status != kCMBlockBufferNoErr) {
            NSLog(@"Could not get data from block buffer");
        }
        else {
 
            CMMediaType type = CMFormatDescriptionGetMediaSubType(formatDescription);
 
            if (type == kCMTimeCodeFormatType_TimeCode32) {
                int32_t *frameNumberRead = (int32_t *)rawData;
                CVSMPTETime timecode = timecodeForFrameNumber32UsingFormatDescription(*frameNumberRead, formatDescription);
 
                BOOL dropFrame = CMTimeCodeFormatDescriptionGetTimeCodeFlags(formatDescription) & kCMTimeCodeFlag_DropFrame;
                char separator = dropFrame ? ',' : '.';
                NSLog(@"%@",[NSString stringWithFormat:@"HH:MM:SS%cFF => %02d:%02d:%02d%c%02d (frame number: %d)", separator, timecode.hours, timecode.minutes, timecode.seconds,  separator, timecode.frames, (int)Endian32_Swap(*frameNumberRead)]);
            }
        }
    }
}

時間碼實用函數(shù)

下面代碼提供了實用程序函數(shù)送膳,演示了如何將CVSMPTETime轉(zhuǎn)換為幀編號,并將幀編號轉(zhuǎn)換為kCMTimeCodeFormatType_TimeCode32時間代碼媒體樣本格式的CVSMPTETime丑蛤。

enum {
    tcNegativeFlag = 0x80    /* negative bit is in minutes */
};
//CVSMPTETime to Frame Number (kCMTimeCodeFormatType_TimeCode32 Media Sample)

int32_t frameNumber32ForTimecodeUsingFormatDescription(CVSMPTETime timecode, CMTimeCodeFormatDescriptionRef formatDescription)
{
    int32_t frameNumber = 0;
 
    if (CMTimeCodeFormatDescriptionGetFormatType(formatDescription) == kCMTimeCodeFormatType_TimeCode32) {
        int32_t frameQuanta = CMTimeCodeFormatDescriptionGetFrameQuanta(formatDescription);
 
        frameNumber = timecode.frames;
        frameNumber += timecode.seconds * frameQuanta;
        frameNumber += (timecode.minutes & ~tcNegativeFlag) * frameQuanta * 60;
        frameNumber += timecode.hours * frameQuanta * 60 * 60;
 
        int32_t fpm = frameQuanta * 60;
 
        if (CMTimeCodeFormatDescriptionGetTimeCodeFlags(formatDescription) & kCMTimeCodeFlag_DropFrame) {
            int32_t fpm10 = fpm * 10;
            int32_t num10s = frameNumber / fpm10;
            int32_t frameAdjust = -num10s*(9*2);
            int32_t numFramesLeft = frameNumber % fpm10;
 
            if (numFramesLeft > 1) {
                int32_t num1s = numFramesLeft / fpm;
                if (num1s > 0) {
                    frameAdjust -= (num1s-1)*2;
                    numFramesLeft = numFramesLeft % fpm;
                    if (numFramesLeft > 1)
                        frameAdjust -= 2;
                    else
                        frameAdjust -= (numFramesLeft+1);
                }
            }
            frameNumber += frameAdjust;
        }
 
        if (timecode.minutes & tcNegativeFlag) {
            frameNumber = -frameNumber;
        }
    }
 
    return EndianS32_NtoB(frameNumber);
}
// Frame Number (kCMTimeCodeFormatType_TimeCode32 Media Sample) to CVSMPTETime

CVSMPTETime timecodeForFrameNumber32UsingFormatDescription(int32_t frameNumber, CMTimeCodeFormatDescriptionRef formatDescription)
{
    CVSMPTETime timecode = {0};
 
    if (CMTimeCodeFormatDescriptionGetFormatType(formatDescription) == kCMTimeCodeFormatType_TimeCode32) {
        frameNumber = EndianS32_BtoN(frameNumber);
 
        short fps = CMTimeCodeFormatDescriptionGetFrameQuanta(formatDescription);
        BOOL neg = FALSE;
 
        if (frameNumber < 0) {
            neg = TRUE;
            frameNumber = -frameNumber;
        }
 
        if (CMTimeCodeFormatDescriptionGetTimeCodeFlags(formatDescription) & kCMTimeCodeFlag_DropFrame) {
            int32_t fpm = fps*60 - 2;
            int32_t fpm10 = fps*10*60 - 9*2;
            int32_t num10s = frameNumber / fpm10;
            int32_t frameAdjust = num10s*(9*2);
            int32_t numFramesLeft = frameNumber % fpm10;
 
            if (numFramesLeft >= fps*60) {
                numFramesLeft -= fps*60;
                int32_t num1s = numFramesLeft / fpm;
                frameAdjust += (num1s+1)*2;
            }
            frameNumber += frameAdjust;
        }
 
        timecode.frames = frameNumber % fps;
        frameNumber /= fps;
        timecode.seconds = frameNumber % 60;
        frameNumber /= 60;
        timecode.minutes = frameNumber % 60;
        frameNumber /= 60;
 
        if (CMTimeCodeFormatDescriptionGetTimeCodeFlags(formatDescription) & kCMTimeCodeFlag_24HourMax) {
            frameNumber %= 24;
            if (neg && !(CMTimeCodeFormatDescriptionGetTimeCodeFlags(formatDescription) & kCMTimeCodeFlag_NegTimesOK)) {
                neg = FALSE;
                frameNumber = 23 - frameNumber;
            }
        }
        timecode.hours = frameNumber;
        if (neg) {
            timecode.minutes |= tcNegativeFlag;
        }
 
        timecode.flags = kCVSMPTETimeValid;
    }
 
    return timecode;
}

關(guān)于TimeCode64格式類型

在構(gòu)建基于AVFoundation的媒體應(yīng)用程序時叠聋,建議使用kCMTimeCodeFormatType_TimeCode64('tc64')格式,而使用本文檔中討論的kCMTimeCodeFormatType_TimeCode32格式應(yīng)被視為具有與舊版基于QuickTime的媒體應(yīng)用程序具有特定互操作性要求的應(yīng)用程序的解決方案盏阶, 支持'tmcd'時間碼采樣格式晒奕。

kCMTimeCodeFormatType_TimeCode64格式的媒體采樣存儲為Big-Endian SInt64

注意:'tc64'時間采樣碼數(shù)據(jù)格式名斟。

CMTimeCodeFormatType_TimeCode64 ('tc64') Timecode Sample Data Format.
 
The timecode media sample data format is a big-endian signed 64-bit integer representing a frame number that is typically converted to and from SMPTE timecodes representing hours, minutes, seconds, and frames, according to information carried in the format description.
 
Converting to and from the frame number stored as media sample data and a CVSMPTETime structure is performed using simple modular arithmetic with the expected adjustments for drop frame timecode performed using information in the format description such as the frame quanta and the drop frame flag.
 
The frame number value may be interpreted into a timecode value as follows:
 
Hours
A 16-bit signed integer that indicates the starting number of hours.
 
Minutes
A 16-bit signed integer that contains the starting number of minutes.
 
Seconds
A 16-bit signed integer indicating the starting number of seconds.
 
Frames
A 16-bit signed integer that specifies the starting number of frames. This field’s value cannot exceed the value of the frame quanta value in the timecode format description.

后記

未完脑慧,待續(xù)~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市砰盐,隨后出現(xiàn)的幾起案子闷袒,更是在濱河造成了極大的恐慌,老刑警劉巖岩梳,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件囊骤,死亡現(xiàn)場離奇詭異,居然都是意外死亡冀值,警方通過查閱死者的電腦和手機(jī)也物,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來列疗,“玉大人滑蚯,你說我怎么就攤上這事。” “怎么了告材?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵坤次,是天一觀的道長。 經(jīng)常有香客問我斥赋,道長缰猴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任疤剑,我火速辦了婚禮滑绒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘骚露。我一直安慰自己蹬挤,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布棘幸。 她就那樣靜靜地躺著焰扳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪误续。 梳的紋絲不亂的頭發(fā)上吨悍,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機(jī)與錄音蹋嵌,去河邊找鬼育瓜。 笑死,一個胖子當(dāng)著我的面吹牛栽烂,可吹牛的內(nèi)容都是我干的躏仇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼腺办,長吁一口氣:“原來是場噩夢啊……” “哼焰手!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起怀喉,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤书妻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后躬拢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體躲履,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年聊闯,在試婚紗的時候發(fā)現(xiàn)自己被綠了工猜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡菱蔬,死狀恐怖域慷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤犹褒,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站弛针,受9級特大地震影響叠骑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜削茁,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一宙枷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧茧跋,春花似錦慰丛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至粥烁,卻和暖如春贤笆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背讨阻。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工芥永, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钝吮。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓埋涧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親奇瘦。 傳聞我的和親對象是個殘疾皇子棘催,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容