搜狐影音播放器内核设计
播放器內(nèi)核設(shè)計(jì)
- 1 背景
- 2 主要功能
- 3 模塊層次結(jié)構(gòu)
- 4 線程模型/數(shù)據(jù)流
- 5 在線播放關(guān)鍵用例
- 5.1 Play mp4
- 5.1.1 單個(gè)分段
- 5.1.1.1 解封裝
- 5.1.1.2 解碼
- 5.1.1.3 渲染
- 5.1.1.4 音視頻同步
- 5.1.2 多個(gè)分段
- 5.2 Seek MP4
- 5.2.1 Seek前的準(zhǔn)備
- 5.2.2 實(shí)際的seek
- 5.3 Play flv
- 5.4 Seek flv
- 5.5 軟字幕的實(shí)現(xiàn)
- 5.6 緩存策略
- 5.7 VMR9和D3D的應(yīng)用-全景視頻播放
- 6 本地播放
1 背景
前幾年負(fù)責(zé)過(guò)搜狐影音的播放器內(nèi)核,這里主要記錄、總結(jié)一下。
2 主要功能
- 播放在線mp4/flv;
- 疊加字幕/彈幕。
3 模塊層次結(jié)構(gòu)
- PlayerEngine.dll:主要提供對(duì)外接口,并進(jìn)行音視頻的同步;
- PlayerCodecs.dll:主要負(fù)責(zé)音視頻分離、音視頻解碼,也就是Demux和Decoder;
- VADSDisplay.dll:主要調(diào)用DirectSound、DirectShow的Filter實(shí)現(xiàn)音視頻的渲染;
- flyfoxDSFilter.dll:實(shí)現(xiàn)了一個(gè)自定義的Source Filter,用于將視頻數(shù)據(jù)推送給DirectShow渲染器;
- flyfoxLocalPlayer.dll:實(shí)現(xiàn)了一個(gè)由DirectShow Filter組成的本地播放器。
4 線程模型/數(shù)據(jù)流
- 外部線程將視頻數(shù)據(jù)寫(xiě)入臨時(shí)文件;
- Demux線程將數(shù)據(jù)從臨時(shí)文件讀入,并解析成未解碼的音頻、視頻Sample,放入各自的未解碼Samle隊(duì)列中;
- 音頻解碼線程、視頻解碼線程分別從未解碼的音頻、視頻隊(duì)列中獲取Sample,進(jìn)行解碼,并放入各自的已解碼Sample隊(duì)列中;
- 音頻渲染線程從已解碼的音頻Sample隊(duì)列讀取已經(jīng)解碼的PCM數(shù)據(jù),放入DirectSound緩沖中進(jìn)行播放;
- 視頻渲染線程從已解碼的視頻Sample隊(duì)列中讀取已經(jīng)解碼的YUV圖像幀,以音頻當(dāng)前的播放時(shí)間戳為準(zhǔn)進(jìn)行音視頻同步,同步后視頻數(shù)據(jù)交給視頻渲染器進(jìn)行渲染。
5 在線播放關(guān)鍵用例
5.1 Play mp4
5.1.1 單個(gè)分段
單個(gè)mp4分段是一個(gè)完整的mp4文件,為了播放一個(gè)mp4文件,需要:
- 解析mp4中的音、視頻Sample;
- 解碼這些音、視頻Sample;
- 渲染這些音、視頻Sample。
實(shí)際上播放任何一個(gè)文件都需要3個(gè)模塊來(lái)分別提供這些能力: - Demux:分離器,將封裝在一個(gè)文件中的音、視頻Sample分離出來(lái);
- Decoder:解碼器,將未解碼的音頻Sample解碼成PCM數(shù)據(jù),將未解碼的視頻Sample解碼成YUV數(shù)據(jù),分為音頻解碼器和視頻解碼器;
- Render:渲染器,分為音頻渲染和視頻渲染,分別用于渲染音頻、視頻數(shù)據(jù),在系統(tǒng)底層將這些數(shù)據(jù)送入指定的硬件,從而實(shí)現(xiàn)播放聲音、視頻的效果。
5.1.1.1 解封裝
由Demux進(jìn)行音、視頻Sample的解析,這里啟動(dòng)一個(gè)Demux線程,讀取文件中的音、視頻Sample,分別放入待解碼音、視頻隊(duì)列。
線程函數(shù):flyfox_player_mov_demux_read_cb
這個(gè)線程首先要解析mp4頭,只有解析完mp4頭才能進(jìn)行后續(xù)的音、視頻Sample的讀取。下面通過(guò)讀取視頻Sample來(lái)展示需要哪些mp4頭中的信息。
在緩存數(shù)據(jù)足夠的情況下,讀取視頻Sample的過(guò)程:
根據(jù)上述描述可以知道,解析視頻Sample需要用到的mp4 box如下:
| 1. | stsc | First Chunk、Sample Per Chunk | 可以從Sample索引獲得Chunk索引,以及Sample在Chunk內(nèi)的索引。 |
| 2. | stco | Chunk Offset | 每個(gè)Chunk的Offset。 |
| 3. | stsz | Sample Size | 每個(gè)Sample的大小。 |
| 4. | avcC | AVC Decoder Configuration Record | 獲得NALU長(zhǎng)度以及ssp、pps等解碼參數(shù)。 |
對(duì)音頻Sample的讀取來(lái)說(shuō)就比較簡(jiǎn)單,直接通過(guò)stsc、stco、stsz獲取到指定的Sample的偏移、大小,然后從文件中讀取Sample即可,也就是只需要實(shí)現(xiàn)讀取視頻Sample的1~5步對(duì)應(yīng)的操作,然后將讀到的音頻Sample直接放入待解碼音頻Sample隊(duì)列。
5.1.1.2 解碼
這里使用ffmpeg進(jìn)行音、視頻的解碼。
以H264解碼為例:
a) 初始化:
b) 解碼
//設(shè)置輸入?yún)?shù) packet.data = src; packet.size = size;//解碼 int got_pic = false; avcodec_decode_video2(&pAVCodecContext,pFlyfoxAVFrame,&got_pic,&packet);c) 拷貝數(shù)據(jù)
解碼出來(lái)的YUV數(shù)據(jù)存放在AVFrame結(jié)構(gòu)中,Pixel Format默認(rèn)為YUV420P(I420P)類型,拷貝數(shù)據(jù)用到的成員如下:
音頻解碼過(guò)程跟視頻解碼基本相同,主要區(qū)別:
- 尋找的解碼器是AAC的解碼器;
- 在解碼前,在輸入的音頻數(shù)據(jù)前要加一個(gè)AAC ADTS頭;
- 要調(diào)用avcodec_decode_audio4解碼;
- 解碼后得到AV_SAMPLE_FMT_FLTP類型的數(shù)據(jù),根據(jù)渲染器需要必須轉(zhuǎn)換成AV_SAMPLE_FMT_S16類型,也就是浮點(diǎn)類型轉(zhuǎn)換成16位整型。
其他步驟完全相同。
a) 初始化:
//注冊(cè)所有編解碼器。 avcodec_register_all(); //初始化AVPacket,作為輸入?yún)?shù)。 AVPacket packet; //聲明AVPacket。 av_init_packet(&packet); //輸入Sample將傳入packet。//創(chuàng)建AVFrame,作為解碼輸出參數(shù)。 AVFrame* pFlyfoxAVFrame = avcodec_alloc_frame();//找到AAC解碼器 AVCodec *pAVCodec = avcodec_find_decoder(CODEC_ID_AAC); //創(chuàng)建AVCodecContext AVCodecContext* pAVCodecContext = avcodec_alloc_context3(pAVCodec);//打開(kāi)解碼器 avcodec_open2(pAVCodecContext, pAVCodec,0);b) 解碼
//設(shè)置輸入?yún)?shù) packet.data = src; packet.size = size;//解碼 int got_audio = false; avcodec_decode_audio4 (&pAVCodecContext,pFlyfoxAVFrame,&got_audio,&packet);//如果是AV_SAMPLE_FMT_FLTP類型需要轉(zhuǎn)換成AV_SAMPLE_FMT_S16 SwrContext *swr = NULL; swr = swr_alloc_set_opts(swr,AV_SAMPLE_FMT_S16); swr_init(swr); if (pAVCodecContext->sample_fmt == AV_SAMPLE_FMT_FLTP) { swr_convert(swr,dst_buffer); }5.1.1.3 渲染
這里采用的是DirectShow框架來(lái)播放視頻,在DirectShow框架中,所有的功能被封裝在各個(gè)Filter中。DirectShow中提供了一些Render Filer來(lái)渲染視頻,例如evr、vmr7、vmr9等,可以充分利用顯卡進(jìn)行硬件加速,達(dá)到比較好的顯示效果。為了將已經(jīng)解碼的視頻數(shù)據(jù)交給Render Filer,需要建立一個(gè)Source Filer,通過(guò)管腳將Source Filer和Render Filer進(jìn)行連接,類似一個(gè)管道,在Source Filer的輸出管腳上PushFrame時(shí),Render Filer將能通過(guò)輸入管腳獲得視頻數(shù)據(jù),交給顯卡渲染播放。
flyfoxDSFilter.dll實(shí)現(xiàn)了一個(gè)視頻的Source Filer,作為一個(gè)COM組件,其實(shí)現(xiàn)了COM組件的相關(guān)接口,并實(shí)現(xiàn)了一個(gè)IFlyfoxDirectSrc接口,用于向外部提供Source Filter的相關(guān)功能,PushFrame就是其中一個(gè)方法,用于推送視頻數(shù)據(jù)。
在VADSDisplay.dll中,封裝了音、視頻Render Filer的相關(guān)接口,并將Render Filter與Source Filter連接,解碼器在解碼得到裸數(shù)據(jù)后,播放線程調(diào)用VADSDisplayer的接口,將解碼后的視頻數(shù)據(jù)分別送入各自的Render Filer。
注意這里沒(méi)有使用DirectShow的音頻DirectSound Filter,而是直接調(diào)用DirectSound的接口,這樣可以不用實(shí)現(xiàn)音頻的Source Filter,同時(shí)更靈活運(yùn)用DirectSound的特性。
5.1.1.4 音視頻同步
這里采用的時(shí)鐘源是音頻流的時(shí)鐘,因?yàn)槁暱ɑ径加凶约旱臅r(shí)鐘源。
在打開(kāi)文件后,從文件頭中獲得了音頻的碼率,從而得到了音頻數(shù)據(jù)偏移跟音頻播放時(shí)間戳的關(guān)系:音頻播放時(shí)間戳=音頻數(shù)據(jù)偏移/音頻碼率。
這個(gè)播放時(shí)間在DirectShow中稱為流時(shí)間(Stream Time),是描述相對(duì)于某個(gè)參考位置(如開(kāi)始播放時(shí)間)的時(shí)間差。
音頻播放會(huì)調(diào)用DirectSound的接口,DirectSound維護(hù)了當(dāng)前播放的音頻數(shù)據(jù)緩沖的播放偏移,因此可以通過(guò)上述公式換算成音頻時(shí)間戳。
視頻渲染線程首先獲得這個(gè)音頻時(shí)間戳ta,然后檢查當(dāng)前準(zhǔn)備播放的視頻Sample的時(shí)間戳tv,如果ta≥tv,則該視頻Sample沒(méi)有提前到來(lái),應(yīng)該立刻播放,如果時(shí)間差過(guò)大,則考慮丟棄;如果ta<tv,說(shuō)明還沒(méi)有到該Sample的顯示時(shí)間,應(yīng)該等待tv-ta的時(shí)間。
5.1.2 多個(gè)分段
搜狐視頻的單個(gè)劇一般由多個(gè)分段構(gòu)成,每個(gè)分段一般是5分鐘,在播放一個(gè)劇的多個(gè)分段的過(guò)程中需要面臨的一個(gè)問(wèn)題是不同分段之前的切換,如果分段切換的過(guò)程中出現(xiàn)卡頓,則會(huì)影響用戶的觀看體驗(yàn),這里采用了預(yù)加載下一分段的辦法來(lái)解決這個(gè)問(wèn)題。
在flyfox_demux_decoder_filter中維護(hù)一個(gè)Demux demux[2]的數(shù)組,分別表示播放中的和預(yù)加載中的Demux,每個(gè)Demux將處理不同的分段文件。Decoder與Demux是解耦的,使用一個(gè)nSourceFilterIndex索引來(lái)表示當(dāng)前正在使用的Demux,在需要切換的時(shí)候,關(guān)閉掉當(dāng)前播放的Demux后,nSourceFilterIndex設(shè)置為正在預(yù)加載的Demux的索引,Decoder只需要從nSourceFilterIndex指定的Demux獲取數(shù)據(jù)即可。
注意預(yù)加載啟動(dòng)的時(shí)機(jī),如果太快啟動(dòng)預(yù)加載則可能會(huì)造成浪費(fèi)。比如用戶可能看到分段中間位置就Seek到文件的最后一個(gè)分段,而不是下一個(gè)分段。這里采取的策略是在當(dāng)前分段播放到最后30秒的時(shí)候啟動(dòng)下一個(gè)分段的預(yù)加載,這個(gè)時(shí)候可以認(rèn)為用戶期望觀看下一分段。
5.2 Seek MP4
5.2.1 Seek前的準(zhǔn)備
在進(jìn)行實(shí)際的mp4 seek之前有一些操作,因?yàn)閟eek的時(shí)間可能不在當(dāng)前已經(jīng)緩存的數(shù)據(jù)里面。
影音的seek是基于整劇時(shí)間的seek,由于一個(gè)劇是由多段文件組成,在進(jìn)行實(shí)際的mp4文件seek前,需要將seek絕對(duì)時(shí)間換算成對(duì)應(yīng)的分段i和分段內(nèi)的時(shí)間偏移t,如果已經(jīng)緩存了第i個(gè)分段mp4的完整數(shù)據(jù),那么直接在這個(gè)分段內(nèi)seek到時(shí)間t,否則需要下載第i個(gè)分段的數(shù)據(jù)。注意這里并不是下載整個(gè)分段的完整數(shù)據(jù),而是下載seek到時(shí)間t后的分段mp4,對(duì)播控來(lái)說(shuō)實(shí)際上是一個(gè)完整的mp4,但是并不是原始的完整分段,這樣做可以減少mp4頭的下載量,提高啟播速度。播控需要維護(hù)這個(gè)偏移時(shí)間t,在下次seek的時(shí)候,如果定位到同一分段i的時(shí)間t1,并且t1>=t,那么可以直接在上次下載的mp4文件中seek到t1-t位置。下面需要判斷數(shù)據(jù)是否足夠,如果數(shù)據(jù)足夠就直接在mp4文件內(nèi)seek,如果不夠的話,需要等待或者去請(qǐng)求t1開(kāi)始的文件。
如果seek的時(shí)間被定位到預(yù)加載的分段里面,那么上述的時(shí)間判斷可以省去,因?yàn)轭A(yù)加載的是完整的分段數(shù)據(jù),這個(gè)時(shí)候只需要判斷數(shù)據(jù)是否足夠即可。
5.2.2 實(shí)際的seek
Seek的目標(biāo)就是將時(shí)間換算成文件偏移offset,從offset開(kāi)始的位置開(kāi)始讀取音、視頻的Sample,達(dá)到修改播放時(shí)間的目的。
mp4文件內(nèi)的seek跟mp4頭關(guān)系緊密,需要解析mp4頭中的若干box才能提供足夠的信息。主要涉及的Box以及提供的信息如下:
| 1 | mdhd | time scale | 1秒的時(shí)間單位數(shù),其倒數(shù)就是時(shí)間單位; |
| 2 | stts | Sample delta | 每個(gè)Sample的時(shí)間單位數(shù),Sample的時(shí)間=Sample delta / time scale; |
| 3 | stss | Sample number | 這個(gè)表存放視頻關(guān)鍵幀的索引,可以通過(guò)該表得到距離指定Sample最近的關(guān)鍵幀Sample索引。 |
| 4 | stsc | First Chunk、Sample Per Chunk | 可以從Sample索引獲得Chunk索引,以及Sample在Chunk內(nèi)的索引。 |
| 5 | stco | Chunk Offset | 每個(gè)Chunk的Offset |
| 6 | stsz | Sample Size | 每個(gè)Sample的大小 |
視頻Seek的步驟:
音頻Seek的步驟和視頻Seek基本一樣,但是比視頻Seek簡(jiǎn)單,沒(méi)有查找關(guān)鍵幀的過(guò)程,直接通過(guò)普通Sample查找其偏移即可。
5.3 Play flv
由于flv的音頻、視頻編碼與mp4完全相同,所以復(fù)用解碼的邏輯。實(shí)際上除了封裝格式不同,flv播放的其他邏輯跟mp4播放基本相同(56的flv只有一個(gè)分段,比多段的mp4更好處理),因此只需要實(shí)現(xiàn)一個(gè)flv Demux來(lái)解析flv中的頭、音頻、視頻數(shù)據(jù)。
flv的解析包括解析flv頭和數(shù)據(jù)tag,實(shí)際上,flv除了9個(gè)字節(jié)的沒(méi)有多少信息的固定頭之外,所有數(shù)據(jù)都是由tag組成。flv有3種tag,分為Metadata Tag(或者叫腳本tag)、Video Tag、Audio Tag。其前3個(gè)tag比較特殊,基本可以認(rèn)為是flv頭,解析了這3個(gè)頭,就可以進(jìn)行正常的flv播放、seek。這個(gè)3個(gè)tag分別是:
- 第1個(gè)Tag,Metadata Tag:包含了關(guān)鍵幀列表、視頻寬高、時(shí)間長(zhǎng)度、文件大小等;
- 第2個(gè)tag,Vidao Tag:主要包含AVC Decoder Configuration Record、視頻的格式等;
- 第3個(gè)tag,Audio Tag主要包含Audio Specifig Config、音頻的格式等。
后續(xù)的所有tag基本都是攜帶音頻、視頻數(shù)據(jù)的Tag,可以由前面的3個(gè)tag的信息進(jìn)行解析。例如視頻IDR幀需要的pps、sps來(lái)自第1個(gè)Video Tag的AVC Decoder Configuration Record,音頻解碼需要的一些ADTS信息來(lái)自第1個(gè)Audio Tag的Audio Specifig Config。
5.4 Seek flv
Flv的seek操作與mp4有所區(qū)別,mp4的seek基于時(shí)間,將seek的絕對(duì)時(shí)間換算成分段號(hào)和分段內(nèi)的時(shí)間偏移之后,向服務(wù)器請(qǐng)求某個(gè)分段某個(gè)時(shí)間偏移的mp4文件。這個(gè)操作成立的前提是:CDN支持這樣的接口。
但是對(duì)flv來(lái)說(shuō)CDN并沒(méi)有這樣的接口,只有基于Http Range請(qǐng)求的接口,也就是基于文件偏移。因此,flv的Seek需要播放引擎先獲得flv seek依賴的完整信息,最主要的是關(guān)鍵幀列表,注意這個(gè)關(guān)鍵幀列表可能位于Script Tag中,但它并不是標(biāo)準(zhǔn)。如果沒(méi)有關(guān)鍵幀列表,那么flv的seek將會(huì)變得低效,必須按照碼率計(jì)算seek時(shí)間對(duì)應(yīng)的文件偏移,這個(gè)偏移并不是準(zhǔn)確的tag偏移,只是估計(jì)tag偏移,必須下載這個(gè)位置之前的flv數(shù)據(jù),累加tag的偏移,直達(dá)指針位置首次超過(guò)seek的估計(jì)tag偏移,并且需要繼續(xù)尋找關(guān)鍵幀對(duì)應(yīng)的tag,這個(gè)時(shí)候才能完成一次seek。
幸好,絕大多數(shù)flv都遵守這么一個(gè)規(guī)則,在Script Tag中按照時(shí)間順序存放了關(guān)鍵幀列表,這個(gè)列表中,每個(gè)字段包含了關(guān)鍵幀的時(shí)間和偏移,這樣flv的Seek就只有一個(gè)簡(jiǎn)單的二分查表的過(guò)程,比mp4的seek更簡(jiǎn)單。
Seek過(guò)程:
- 檢查flv的頭是否完整,如果不,則繼續(xù)下載flv頭;
- 如果flv頭完整,則檢查關(guān)鍵幀列表,如果沒(méi)有,則返回失敗;
- 二分查找flv關(guān)鍵幀列表,獲得離目標(biāo)時(shí)間最接近的關(guān)鍵幀位置和偏移;
- 從關(guān)鍵幀偏移位置開(kāi)始讀取tag和里面的Sample。
5.5 軟字幕的實(shí)現(xiàn)
在Source Filter和Render Filer中間增加一個(gè)VsFilter,由VsFilter實(shí)現(xiàn)所有的字幕疊加操作,并向外提供相關(guān)的接口。其主要原理:VsFilter通過(guò)獲取輸出的文字路徑點(diǎn)集合,轉(zhuǎn)化成形狀,并光柵化成Bitmap像素,然后與輸入的圖像進(jìn)行Alpha混合,達(dá)到字幕疊加的目的,實(shí)際上是圖像疊加。所有的操作都是使用CPU進(jìn)行計(jì)算,所以會(huì)明顯增加CPU的開(kāi)銷。
官方老版本的VsFilter性能較差,這里使用的是第3方優(yōu)化后的XyVsFilter,與官方版本的主要區(qū)別是進(jìn)行了大量的緩存,省去一些重復(fù)性的渲染以降低CPU使用率。
主要流程:
- 加載VsFilter.dll,創(chuàng)建VsFilter的com實(shí)例,并獲取IID_IDirectVobSub接口,此時(shí)將創(chuàng)建文件監(jiān)控線程;
- 連接Source Filter、VsFilter、Render Filter,此時(shí)將創(chuàng)建字幕圖片生成線程;
- 調(diào)用IID_IDirectVobSub接口的put_FileName方法,設(shè)置字幕文件,VsFilter將加載該字幕文件,同時(shí)字幕圖片生成線程將建立字幕圖片緩存;
- Filter Graph開(kāi)始工作后,CDirectVobSubFilter::Transform函數(shù)獲得輸入Sample以及時(shí)間戳,通過(guò)輸入sample的時(shí)間戳查找SubPic緩存隊(duì)列中的圖片,如果查不到,則從Entry中查找并生成bitmap,然后將字幕bitmap與輸入sample的surface進(jìn)行疊加(alphablt),疊加完成進(jìn)行適當(dāng)?shù)霓D(zhuǎn)換(轉(zhuǎn)成YUY2),然后拷貝到輸出Sample。SubPic緩存隊(duì)列維持長(zhǎng)度為10,每消耗一條,則補(bǔ)充一條。
5.6 緩存策略
上層的數(shù)據(jù)會(huì)寫(xiě)入一個(gè)臨時(shí)文件,在加載完成后,該臨時(shí)文件是一個(gè)完整的mp4分段文件,并在播放完成后刪除。在Demux內(nèi)部維護(hù)一個(gè)初始大小為2M的內(nèi)存緩存,Demux從臨時(shí)文件一次讀64KB數(shù)據(jù)到這個(gè)緩存,每次解析mp4頭、解析Sample的時(shí)候其數(shù)據(jù)源都直接來(lái)自這個(gè)內(nèi)存緩存。
在播放DRM視頻時(shí),對(duì)緩存有了新的需求:
- 涉及版權(quán)安全,不能寫(xiě)文件;
- 單個(gè)文件可能比較大,mp4頭可能會(huì)大于2M,內(nèi)存緩存無(wú)法一次容納一個(gè)mp4頭;
因此在播放DRM視頻時(shí),虛擬了一個(gè)內(nèi)存的文件緩存,與磁盤(pán)文件緩存提供一樣的接口,用于為Demux提供數(shù)據(jù)。同時(shí)Demux的內(nèi)存緩存大小可變,在緩存大小不夠時(shí)進(jìn)行擴(kuò)展。
由于DRM mp4文件在內(nèi)存中緩存,所以不能將整個(gè)mp4完全緩存在內(nèi)存中,任何時(shí)刻,內(nèi)存虛擬文件中只保留mp4連續(xù)的一部分?jǐn)?shù)據(jù)。內(nèi)存虛擬文件接收到Demux的讀取請(qǐng)求后檢查緩存內(nèi)是否有指定偏移的數(shù)據(jù),如果有則拷貝返回,如果沒(méi)有則向傳輸層“拉”數(shù)據(jù)。每次內(nèi)存虛擬文件的數(shù)據(jù)被讀走,檢查有效的可用數(shù)據(jù)大小如果不到一半的緩存總大小,則再次向傳輸層“拉”數(shù)據(jù),期望填充滿整個(gè)緩存。
5.7 VMR9和D3D的應(yīng)用-全景視頻播放
VMR9有3種模式:Window、Windowless、Renderless。
- Window模式:需要Render創(chuàng)建自己的播放窗口并設(shè)置為上層應(yīng)用窗口的子窗口,為響應(yīng)父窗口的消息,需要進(jìn)行父窗口與子窗口的消息交互;
- Windowless模式:Render無(wú)需創(chuàng)建自己的播放窗口,直接在上層應(yīng)用窗口上繪制,在應(yīng)用窗口重繪、修改窗口時(shí)需要通知Render;
- Renderless模式:VMR不再內(nèi)部渲染,需要在外部借助D3D等手段自行渲染。
在使用VMR9渲染的情況下,使用的策略是優(yōu)先使用Renderless模式,如果無(wú)法使用Renderless模式,則使用Windowless模式。
在Renderless模式下,需要?jiǎng)?chuàng)建一個(gè)Presenter-Allocator,用于替代VMR9內(nèi)部的渲染機(jī)制,并接受VMR9傳遞的圖像數(shù)據(jù),使用D3D進(jìn)行渲染。
D3D對(duì)圖像數(shù)據(jù)的處理流程:創(chuàng)建某個(gè)形狀的頂點(diǎn)緩沖,并創(chuàng)建一個(gè)紋理,將圖像數(shù)據(jù)拷貝到紋理上,然后將紋理貼圖到頂點(diǎn)緩沖對(duì)應(yīng)的形狀,之后就是D3D內(nèi)部的渲染過(guò)程。
CPlaneScene與CSphereScene的區(qū)別是CPlaneScene創(chuàng)建矩形頂點(diǎn)緩沖而CSphereScene創(chuàng)建球面頂點(diǎn)緩沖,分別對(duì)應(yīng)普通視頻播放和全景視頻播放。
在全景視頻播放時(shí),圖像一般是2:1的寬高比,可以完整覆蓋在球面上,這樣通過(guò)球面的扭曲可以將本身就扭曲的360°全景視頻在視場(chǎng)內(nèi)還原,另外通過(guò)響應(yīng)鼠標(biāo)、鍵盤(pán)等事件調(diào)整視點(diǎn)、透視矩陣,可以完整實(shí)現(xiàn)全景視頻的播放以及旋轉(zhuǎn)、拉近、拉遠(yuǎn)等交互。
6 本地播放
本地播放視頻全部通過(guò)DirectShow的Filter實(shí)現(xiàn)。
在DirectShow內(nèi)部,Filter的連接一般由GraphBuilder自動(dòng)完成,稱為智能連接。但是在這里自定義了類似智能連接的過(guò)程,使用策略是:先使用自定義的的連接策略,如果自定義連接策略失敗則使用DirectShow的智能連接。
自定義連接流程:
1) 枚舉輸出管腳,渲染該管腳:
1> 枚舉該管腳上的所有媒體類型,渲染該媒體類型;
a) 枚舉Filter Graph上的所有Filter,嘗試用該媒體類型連接這些Filter;
b) 如果連接成功,則遞歸調(diào)用渲染Filter流程繼續(xù)向下渲染該Filter。
用以上迭代+遞歸的辦法直到連接到Render Filter,最后完成整個(gè)連接過(guò)程。一個(gè)典型的Filter連接圖如下:
- File Source(Async.):Source Filter,實(shí)現(xiàn)文件的讀取,屬于“拉”模式;
- MP4 Splitter:Transform Filter,Demux,用于分離音、視頻流;
- CoreAVC Video Decoder:Transform Filter,Decoder,用于視頻解碼;
- VsFilter:Transform Filter,字幕插件;
- Video Render(EVR):Render Filter,用于視頻渲染;
- CoreAAC Audio Decoder,Transform Filter,Decoder,用于音頻解碼;
- AudioSwitcher:Transform Filter,用于切換聲道;
- Default DirectSound Device:Render Filter,用于聲音渲染。
總結(jié)
以上是生活随笔為你收集整理的搜狐影音播放器内核设计的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 轮播插件——flexslider
- 下一篇: 淘宝API接口,获得淘宝app商品详情原