目前iOS端播放器在視頻播放上大多采用VideoToolBox硬解碼+OpenGL ES渲染的方案,但如果只是為了渲染而沒有其他的后處理過程,推薦使用iOS 8.0推出的AVSampleBufferDisplayLayer,它既可以用來渲染解碼后的視頻數(shù)據(jù),也可以直接送入未解碼的視頻幀,它能夠同時完成解碼和渲染工作,和Android MediaCodec接Surface解碼渲染邏輯一致。
經(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)的解碼器,如果AVSampleBufferDisplayLayer和VideoToolBox不支持該文件的解碼,則再次嘗試創(chuàng)建軟件解碼器,流程圖如下:

1.2 AVSampleBufferDisplayLayer的創(chuàng)建與關(guān)聯(lián)
我們選擇在IJKSDLGLView初始化時創(chuàng)建AVSampleBufferDisplayLayer對象。由于此時并不知道播放實際使用的解碼方式,因此只創(chuàng)建該對象,并不add到IJKSDLGLView.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添加到IJKSDLGLView的layer上:
[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.1 CMTimebaseRef創(chuàng)建
AVSampleBufferDisplayLayer中默認的controlTimebase為nil,需要外部創(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幀。
四、性能比較
我們對VideoToolBox和AVSampleBufferDisplayLayer兩種解碼方式進行了性能測試,測試方案及結(jié)果如下:
4.1 測試環(huán)境
- 機型:iPhone6s;
- 網(wǎng)絡(luò):本地播放,無需網(wǎng)絡(luò);
- 測試文件:
- 碼率為2048kb/s,幀率為30fps的1080P視頻;
- 碼率為1126kb/s,幀率為25fps的720P視頻;
- 評估維度: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、MPEG4、HEVC的硬解和渲染,歡迎試用。
轉(zhuǎn)載請注明:
作者金山視頻云,首發(fā)簡書 Jianshu.com
同時也歡迎大家使用我們的直播、短視頻等SDK:
https://github.com/ksvc
金山云多媒體SDK相關(guān)的QQ交流群:
- 視頻云技術(shù)交流群:574179720
- 視頻云iOS技術(shù)交流:621137661