日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

搜狐影音播放器内核设计

發(fā)布時(shí)間:2023/12/29 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 搜狐影音播放器内核设计 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

播放器內(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ò)程:

  • 首先維護(hù)一個(gè)當(dāng)前需要讀取的視頻Sample的索引,這個(gè)索引將會(huì)持續(xù)累加;
  • 查詢stsc box,得到視頻Sample索引對(duì)應(yīng)的Chunk索引,以及該Sample在Chunk內(nèi)的偏移數(shù);
  • 查詢stco box,得到該Chunk的偏移;
  • 查詢stsz,通過(guò)累加Chunk的偏移和在Chunk的各個(gè)Sample的大小,得到指定Sample的偏移以及大小;
  • 從文件中的指定偏移讀取指定大小的數(shù)據(jù),得到一個(gè)視頻Sample,但是該Sample在送入待解碼視頻Sample隊(duì)列之前需要先做一些調(diào)整;
  • 如果該Sample是文件第一個(gè)Sample,那么是IDR幀,需要重置解碼器,傳遞一些解碼參數(shù)。在第一個(gè)Sample前需要增加AVC Decoder Configuration Record,該信息位于moov->trak->mdia->minf->stbl->avc1->avcC中,主要包含sps、pps等數(shù)據(jù),目的是將解碼器需要的一些參數(shù)告訴解碼器,否則解碼器無(wú)法正常工作;
  • 在Sample中包含若干個(gè)NALU,視頻數(shù)據(jù)送入解碼器之前需要將每個(gè)NALU前面的x個(gè)字節(jié)的NALU長(zhǎng)度修改成分隔符“00 00 00 01”,x也是從 AVC Decoder Configuration Record中獲得,一般是4,mp4中放x個(gè)字節(jié)的NALU長(zhǎng)度是為了方便讀取NALU,而解碼器需要NALU之間通過(guò)“00 00 00 01”進(jìn)行分隔,否則解碼器無(wú)法正常解碼;
  • 經(jīng)過(guò)上述步驟讀取并處理后的Sample是一個(gè)解碼器可以處理的Sample,可以放入待解碼視頻Sample隊(duì)列。
    根據(jù)上述描述可以知道,解析視頻Sample需要用到的mp4 box如下:
  • 序號(hào)Box字段作用
    1.stscFirst Chunk、Sample Per Chunk可以從Sample索引獲得Chunk索引,以及Sample在Chunk內(nèi)的索引。
    2.stcoChunk Offset每個(gè)Chunk的Offset。
    3.stszSample Size每個(gè)Sample的大小。
    4.avcCAVC 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) 初始化:
  • //注冊(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();//找到H264解碼器 AVCodec *pAVCodec = avcodec_find_decoder(AV_CODEC_ID_H264); //創(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_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ù)用到的成員如下:

    //拷貝Y分量 for(i = 0; i < pFlyfoxAVFrame->height; i++) { pSrc = pFlyfoxAVFrame->data[0] + i * pFlyfoxAVFrame->linesize[0]; memcpy(pDst, pSrc, pFlyfoxAVFrame->width);pDst += pFlyfoxAVFrame->width; }//拷貝U分量 for(i = 0; i< pFlyfoxAVFrame ->height/2; i++) //行數(shù)減半 { pSrc = pFlyfoxAVFrame->data[1] + i * pFlyfoxAVFrame->linesize[1]; memcpy(pDst, pSrc, pFlyfoxAVFrame->width/2);pDst += pFlyfoxAVFrame->width/2; //列數(shù)減半 }//拷貝V分量 for(i = 0; i < g_pFlyfoxAVFrame->height/2; i++) //行數(shù)減半 { pSrc = pFlyfoxAVFrame->data[2] + i * pFlyfoxAVFrame->linesize[2]; memcpy(pDst, pSrc, pFlyfoxAVFrame->width/2); pDst += pFlyfoxAVFrame->width/2; //列數(shù)減半 }
  • 音頻解碼步驟:
    音頻解碼過(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以及提供的信息如下:

    序號(hào)Box字段作用
    1mdhdtime scale1秒的時(shí)間單位數(shù),其倒數(shù)就是時(shí)間單位;
    2sttsSample delta每個(gè)Sample的時(shí)間單位數(shù),Sample的時(shí)間=Sample delta / time scale;
    3stssSample number這個(gè)表存放視頻關(guān)鍵幀的索引,可以通過(guò)該表得到距離指定Sample最近的關(guān)鍵幀Sample索引。
    4stscFirst Chunk、Sample Per Chunk可以從Sample索引獲得Chunk索引,以及Sample在Chunk內(nèi)的索引。
    5stcoChunk Offset每個(gè)Chunk的Offset
    6stszSample Size每個(gè)Sample的大小

    視頻Seek的步驟:

  • 通過(guò)視頻trak->mdia->mdhd box得到Time Scale,也就是一秒有多少個(gè)時(shí)間單位;
  • 查詢stts box,這個(gè)box里面是一個(gè)表,每個(gè)記錄包含:一個(gè)Sample包含的時(shí)間單位數(shù),擁有相同時(shí)間單位數(shù)的Sample的個(gè)數(shù),遍歷這個(gè)表,累加訪問(wèn)到的每個(gè)Sample的時(shí)間,直到累積時(shí)間≥seek time,這個(gè)時(shí)候得到最接近seek time的Sample索引;
  • 查詢stss,這個(gè)box里面是一個(gè)表,里面有序地記錄了每個(gè)關(guān)鍵幀的索引,通過(guò)二分查找,可以得到距離步驟2中的Sample索引最近的關(guān)鍵幀Sample索引,之所以尋找關(guān)鍵幀,是因?yàn)橹挥嘘P(guān)鍵幀才能夠獨(dú)立解碼,普通幀需要參考幀,仍然需要等待關(guān)鍵幀;
  • 再次查詢stts box,得到該關(guān)鍵幀對(duì)應(yīng)的時(shí)間real seek time,也就是實(shí)際上能夠seek到的時(shí)間;
  • 查詢stsc box,該box是一個(gè)表,里面的每個(gè)記錄是一個(gè)Chunk組,組內(nèi)的每個(gè)Chunk包含相同的Sample個(gè)數(shù),每個(gè)記錄包含:Chunk組的開(kāi)始Chunk索引,每個(gè)Chunk的Sample數(shù),從表開(kāi)始遍歷這個(gè)表,疊加Sample數(shù),直到累加的Sample數(shù)=關(guān)鍵幀的Sample索引,這個(gè)時(shí)候可以得到其所處的Chunk索引以及在Chunk內(nèi)的Sample偏移數(shù);
  • 查詢stco,得到步驟5中的Chunk索引對(duì)應(yīng)的偏移;
  • 查詢stsz,利用關(guān)鍵幀Sample索引和其在Chunk內(nèi)的Sample偏移數(shù),得到這個(gè)關(guān)鍵幀Sample相對(duì)于Chunk開(kāi)始位置的偏移,這個(gè)偏移加上Chunk的偏移,就得到關(guān)鍵幀Sample的偏移。
    音頻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的智能連接。
    自定義連接流程:

  • 定義一個(gè)FilterInfo.xml,描述了所有支持的Filter以及信息,初始化時(shí)將這些Filter的信息全部加載;
  • 從Source Filter開(kāi)始,先枚舉這項(xiàng)Filter信息,找到所有Source Filter,檢查該Filter是否支持傳入的文件url和媒體類型,如果支持則設(shè)置其為使用的Source Filter并從該Source Filter開(kāi)始渲染;
  • 進(jìn)入渲染Filter流程,不論是什么類型的Filter都走這個(gè)流程:
    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)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 国精产品一区一区三区mba下载 | 久久激情影院 | 亚洲精品乱码久久久久久蜜桃欧美 | 久久久亚洲综合 | 精品欧美一区二区在线观看 | free欧美性69护士呻吟 | 精品玖玖玖 | 国产日韩精品一区二区三区在线 | 91性视频 | www,99| 亚洲视频h | 日日鲁鲁鲁夜夜爽爽狠狠视频97 | 日韩一卡| 91福利视频网站 | 偷自在线 | 亚洲男人皇宫 | 三级男人添奶爽爽爽视频 | 国产999久久久 | 久久99久久99精品免观看粉嫩 | 欧美性猛交xxxx乱大交hd | 免费av导航 | 欧美黄色一级生活片 | 久久不射影院 | a免费看| 中文字幕电影一区 | 日本xxxxwwwww | 婷婷成人综合 | www.操| 精品人妻人人做人人爽 | 91免费 看片 | 成人区人妻精品一区二 | 久久精品视频一区二区三区 | 天天综合天天做 | 日韩欧美国产一区二区三区在线观看 | 视频这里只有精品 | 国产九区 | 亚洲精品高潮久久久久久久 | 91伦理在线| 黄色一级片 | 精品自拍一区 | 国模私拍视频在线 | 91麻豆精品91久久久久同性 | 美女视频黄色在线观看 | 伊人91在线 | 在线看成人 | 四虎影视大全 | 97超碰在线资源 | 激情综合网五月激情 | 秋霞视频一区二区 | 日韩精品在线观看视频 | 天天干夜夜干 | 可以免费观看的毛片 | 无码人妻丰满熟妇啪啪网站 | 亚洲美女精品视频 | 激情文学亚洲 | 日本中文字幕在线观看视频 | 成年男女免费视频网站 | 国产精品综合久久久 | 午夜私人福利 | avwww.| 欧美小视频在线观看 | 欧美性猛交性大交 | 欧美三级电影在线观看 | av卡一卡二 | 精品播放 | 久久97人妻无码一区二区三区 | 久久综合亚洲色hezyo国产 | 免费国产黄色 | 日本免费一区二区三区四区五六区 | 国产又白又嫩又爽又黄 | 嫩草精品 | 欧美性猛交ⅹ乱大交3 | 永久精品网站 | 久久国产激情视频 | 国产资源av | 精品人妻伦九区久久aaa片 | 欧美精品aa | 午夜一区二区三区 | 成人免费看片入口 | 色播日韩| 国内精品第一页 | 欧美精品国产一区二区 | 筱田优av | 吃奶摸下的激烈视频 | 玖色视频| 国产成人精品二区三区亚瑟 | 中文字幕在线观看视频网站 | www.色香蕉 | 午夜不卡在线观看 | 男生看的污网站 | 欧美日韩在线高清 | 黑人欧美一区二区三区 | 一区二区三区四区高清视频 | 久久视频免费 | 无码人妻精品一区二区三区66 | 精品视频在线免费看 | 专干老肥女人88av | 夜夜操夜夜爱 | 天天干视频 |