iOS AVSampleBufferDisplayLayer在ijkplayer中的實現(xiàn)

目前iOS端播放器在視頻播放上大多采用VideoToolBox硬解碼+OpenGL ES渲染的方案,但如果只是為了渲染而沒有其他的后處理過程,推薦使用iOS 8.0推出的AVSampleBufferDisplayLayer,它既可以用來渲染解碼后的視頻數(shù)據(jù),也可以直接送入未解碼的視頻幀,它能夠同時完成解碼和渲染工作,和Android MediaCodecSurface解碼渲染邏輯一致。

經(jīng)過金山云多媒體SDK團隊測試,AVSampleBufferDisplayLayer性能表現(xiàn)要優(yōu)于使用VideoToolBox的方案。

ijkplayer由于其開源、跨平臺、功能豐富穩(wěn)定等特性,被廣泛用于移動端。本文則要介紹如何在ijkplayer中集成AVSampleBufferDisplayLayer

ijkplayer中的解碼框架在我們的另一篇文章ijkplayer框架深入剖析中第3.2章節(jié)有介紹,本文不再贅述。

一、AVSampleBufferDisplayLayer集成

1.1 總體思路

IJKFF_Pipenode結(jié)構(gòu)體相當于C++中的虛基類,它定義了解碼器的基本方法,在ijkplayer中,已經(jīng)有兩個文件實現(xiàn)了該基類:

  • ffpipenode_ffplay_vdec.c,提供基于ffmpeg的軟解功能;
  • ffpipenode_ios_videotoolbox_vdec.m,提供基于VideoToolBox的硬解功能;

如果要增加新的解碼器,則要實現(xiàn)該基類,我們定義該文件為ffpipenode_ios_displaylayer.m,其完成的主要功能是從PacketQueue中獲取視頻數(shù)據(jù),并送給AVSampleBufferDisplayLayer進行解碼渲染。

啟動播放后,播放器會優(yōu)先根據(jù)用戶選擇創(chuàng)建對應(yīng)的解碼器,如果AVSampleBufferDisplayLayerVideoToolBox不支持該文件的解碼,則再次嘗試創(chuàng)建軟件解碼器,流程圖如下:

圖1. 解碼器選擇流程

1.2 AVSampleBufferDisplayLayer的創(chuàng)建與關(guān)聯(lián)

我們選擇在IJKSDLGLView初始化時創(chuàng)建AVSampleBufferDisplayLayer對象。由于此時并不知道播放實際使用的解碼方式,因此只創(chuàng)建該對象,并不addIJKSDLGLView.layer上。

AVSampleBufferDisplayLayer *_avsplayer;
_avsplayer = [[AVSampleBufferDisplayLayer alloc] init];
_avsplayer.frame = self.bounds;
_avsplayer.position = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
_avsplayer.videoGravity = AVLayerVideoGravityResizeAspect;
_avsplayer.opaque = YES;
_avsplayer.backgroundColor = [UIColor blackColor].CGColor;

當啟動播放后確定使用AVSampleBufferDisplayLayer解碼方式,再將_avsplayer添加到IJKSDLGLViewlayer上:

[self.layer addSublayer:_avsplayer];

如果解碼失敗需要切換到軟解,則從IJKSDLGLView上移除_avsplayer:

[_avsplayer removeFromSuperlayer];

1.3 送入待解碼數(shù)據(jù)

由于送入待解碼數(shù)據(jù)時,從PacketQueue中獲取到視頻幀數(shù)據(jù),并非是CMSampleBufferRef封裝的,首先需要將其封裝為CMSampleBufferRef形式:

CMBlockBufferRef newBBufOut = NULL;
CMSampleBufferRef sBufOut = NULL;
    
CMBlockBufferCreateWithMemoryBlock(NULL, buffer, size, kCFAllocatorNull, NULL, 0, size, FALSE, &newBBufOut);
    
CMSampleBufferCreate(NULL,newBBufOut, TRUE, 0, 0, fmt_desc, 1, 0, NULL, 0, NULL, &sBufOut);

然后調(diào)用enqueueSampleBuffer方法送入待解碼數(shù)據(jù):

[_avsplayer enqueueSampleBuffer: sBufOut];

_avsplayer就是在IJKSDLGLView中創(chuàng)建的對象。

二、同步處理

同步處理是播放過程中必不可少的步驟,通常該步驟都是基于解碼后的原始音視頻頻數(shù)據(jù)的pts進行的,但是使用AVSampleBufferDisplayer時,我們無法得到解碼后的yuv數(shù)據(jù),此時如何進行同步呢?

AVSampleBufferDisplayLayer中提供了一個時鐘相關(guān)的屬性:

@property (retain, nullable) __attribute__((NSObject)) CMTimebaseRef controlTimebase;

我們可以通過設(shè)置該屬性完成同步的工作,基本思路就是在音頻設(shè)備通過callback獲取Audio Frame時,將待送入輸出的Audio Frame PTS設(shè)置給AVSampleBufferDisplayLayer.controlTimebase,用于設(shè)置和校驗MasterClock,以達到音視頻同步播放的效果。示意圖如下:

圖2. 同步機制

2.1 CMTimebaseRef創(chuàng)建

AVSampleBufferDisplayLayer中默認的controlTimebasenil,需要外部創(chuàng)建并設(shè)置:

//創(chuàng)建CMTimebaseRef對象
CMTimebaseRef controlTimebase;
CMTimebaseCreateWithMasterClock( CFAllocatorGetDefault(), CMClockGetHostTimeClock(), &controlTimebase);

//設(shè)置給AVSampleBufferDisplayLayer對象
_avsplayer.controlTimebase = controlTimebase;

2.2 時鐘的設(shè)置

CMTimebaseRef提供了兩個必用方法:

  • 設(shè)置時鐘基準時間
CM_EXPORT OSStatus 
CMTimebaseSetTime( 
        CMTimebaseRef CM_NONNULL timebase,
        CMTime time )
  • 設(shè)置時鐘速率
CM_EXPORT OSStatus 
CMTimebaseSetRate( 
        CMTimebaseRef CM_NONNULL timebase,
        Float64 rate )

在創(chuàng)建時鐘時,音頻可能還尚未開始播放,因此需先將時鐘置于暫停狀態(tài),

CMTimebaseSetRate(_avslayer.controlTimebase, 0);

渲染第一幀音頻時,將時鐘置于1倍速狀態(tài),

CMTimebaseSetRate(_avslayer.controlTimebase, 1);

在渲染每一幀音頻時,需要將音頻時鐘時間設(shè)置給時鐘。

CMTimebaseSetTime(_avslayer.controlTimebase,  CMTimeMake(audiopts, TIMEBASE_SCALE));

說明: 實際實現(xiàn)過程中不必每次都需要將音頻時間戳設(shè)置給AVSamplebufferDisplayLayer的時鐘,可以先判斷下當前時間與音頻時間戳的差值,如果在容忍范圍內(nèi),則無需設(shè)置**

2.3 CMSampleBufferRef的設(shè)置

2.2章節(jié)中說明了AVSampleBufferDisplayLayer的時鐘設(shè)置方法,同理需要給待解碼的視頻幀設(shè)置時間戳以達到同步效果。

CMSampleBuffer的附件字典中有關(guān)鍵字:

kCMSampleAttachmentKey_DisplayImmediately

如果該關(guān)鍵字的值為True,表示解碼后立刻進行渲染,不考慮同步;如果設(shè)置為False,AVSampleBufferDisplayLayer會根據(jù)時鐘時間來渲染視頻畫面。

//同步顯示
CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);
CFMutableDictionaryRef dict = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
CFDictionarySetValue(dict, kCMSampleAttachmentKey_DisplayImmediately, kCFBooleanFalse);

//設(shè)置CMSampleBufferRef的時間戳
CMSampleBufferSetOutputPresentationTimeStamp(sampleBuffer, CMTimeMake(pts, TIMEBASE_SCALE));

三、后臺播放

使用AVSampleBufferDisplayLayer解碼時,如果APP切入后臺,會造成渲染失敗,出現(xiàn)黑屏的現(xiàn)象,再次回到前臺時,依然是黑屏的現(xiàn)象,針對這個問題,并不需要重新創(chuàng)建AVSampleBufferDisplayLayer對象。

官方提供的解決方案是:如果AVSampleBufferDisplayLayer的狀態(tài)為failed,可以使用flush方法來重新讓layer渲染生效。

if(AVQueuedSampleBufferRenderingStatusFailed == _avslayer.status)
    [_avslayer flush];

調(diào)用flush方法后,送給AVSampleBufferDisplayLayer的第一個視頻幀應(yīng)為I幀,如果不是I幀,則無法成功解碼,會黑屏一段時間,直到送入新I幀。

四、性能比較

我們對VideoToolBoxAVSampleBufferDisplayLayer兩種解碼方式進行了性能測試,測試方案及結(jié)果如下:

4.1 測試環(huán)境

  • 機型:iPhone6s;
  • 網(wǎng)絡(luò):本地播放,無需網(wǎng)絡(luò);
  • 測試文件:
    1. 碼率為2048kb/s,幀率為30fps1080P視頻;
    2. 碼率為1126kb/s,幀率為25fps720P視頻;
  • 評估維度:cpu占用、內(nèi)存占用、發(fā)熱情況及耗電量(由于使用AVSampleBufferDisplayLayer解碼方式時獲取不到GPU占用,因此GPU不作為評估維度);
  • 測試步驟:本地循環(huán)播放測試文件,總測試時長為60分鐘,每分鐘采集一次內(nèi)存和cpu占用情況,開始測試和結(jié)束測試時采集溫度(前屏)和電量。

4.2 測試結(jié)果

統(tǒng)計結(jié)果中的內(nèi)存和cpu占用值是對每分鐘采集的數(shù)據(jù)取均值,耗電量和發(fā)熱為測試結(jié)束時與開始時對應(yīng)的差值。

  • 1080P視頻的測試結(jié)果
Type AVSampleBufferDisplayLayer VideoToolBox
CPU占用 20.30% 24%
內(nèi)存占用 32.14MB 40.00MB
耗電量 -4% -12%
發(fā)熱情況 +1.8度 +3.5度
  • 720P視頻的測試結(jié)果
Type AVSampleBufferDisplayLayer * VideoToolBox*
CPU占用 19.49% 22.52%
內(nèi)存占用 30.87MB 40.06MB
耗電量 -2% -9%
發(fā)熱情況 +1.5度 +2.5度

從上述結(jié)果可以看出,使用AVSampleBufferDisplayLayer解碼的播放器在性能表現(xiàn)上要明顯優(yōu)于使用VideoToolBox,上述數(shù)據(jù)是在全功能demo中測試得出的,相信在精簡版上差距會更加明顯。

五、結(jié)束語

金山云多媒體SDK團隊定義本解碼方案為高性能硬解,區(qū)別于
VideoToolBox硬解和FFmpeg軟解。高性能硬解的實現(xiàn),是犧牲了解碼后YUV數(shù)據(jù)可見為前提的,這意味著解碼后的畫面旋轉(zhuǎn)、畫面鏡像、實時裁剪、視頻后處理濾鏡、錄屏等工作都沒辦法實現(xiàn)。在業(yè)務(wù)使用時,請根據(jù)需求取舍。

本文只是簡單介紹了如何在ijkplayer中集成AVSampleDisplayDisplayLayer完成視頻解碼和渲染的工作,還有許多細節(jié)的地方尚未考慮和優(yōu)化,希望您能夠給提供更優(yōu)秀的解決方法或思路。歡迎大家加入我們QQ群一起討論。

KSYMediaPlayer_iOS已經(jīng)完全實現(xiàn)了AVSampleBufferDisplayLayer,支持H.264、MPEG4HEVC的硬解和渲染,歡迎試用。

轉(zhuǎn)載請注明:
作者金山視頻云,首發(fā)簡書 Jianshu.com


同時也歡迎大家使用我們的直播、短視頻等SDK:
https://github.com/ksvc

金山云多媒體SDK相關(guān)的QQ交流群:

  • 視頻云技術(shù)交流群:574179720
  • 視頻云iOS技術(shù)交流:621137661
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 隨著互聯(lián)網(wǎng)技術(shù)的飛速發(fā)展,移動端播放視頻的需求如日中天,由此也催生了一批開源/閉源的播放器,但是無論這個播放器功能...
    金山視頻云閱讀 47,001評論 28 170
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,625評論 4 61
  • 一、個人見解(直播難與易) 直播難:個人認為要想把直播從零開始做出來,絕對是牛逼中的牛逼,大牛中的大牛,因為直播中...
    i愛吃土豆的貓閱讀 489評論 0 1
  • 我是五月份開始復(fù)習的,北郵軟院考的是數(shù)一和英一,專業(yè)課是兩門,其中數(shù)據(jù)結(jié)構(gòu)是必考,然后操作系統(tǒng)和數(shù)據(jù)庫是二選一。我...
    _我不喜歡吃芋頭閱讀 6,444評論 1 17
  • · 一、首先是一段廢話 第一次用簡書寫文章,都說Markdown很好用,于是就了解了下~感覺確實好好用,大家也用起...
    Dave_hz閱讀 871評論 0 2

友情鏈接更多精彩內(nèi)容