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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

搜狐影音播放器内核设计

發布時間:2023/12/29 编程问答 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 搜狐影音播放器内核设计 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

播放器內核設計

    • 1 背景
    • 2 主要功能
    • 3 模塊層次結構
    • 4 線程模型/數據流
    • 5 在線播放關鍵用例
      • 5.1 Play mp4
        • 5.1.1 單個分段
          • 5.1.1.1 解封裝
          • 5.1.1.2 解碼
          • 5.1.1.3 渲染
          • 5.1.1.4 音視頻同步
        • 5.1.2 多個分段
      • 5.2 Seek MP4
        • 5.2.1 Seek前的準備
        • 5.2.2 實際的seek
      • 5.3 Play flv
      • 5.4 Seek flv
      • 5.5 軟字幕的實現
      • 5.6 緩存策略
      • 5.7 VMR9和D3D的應用-全景視頻播放
    • 6 本地播放

1 背景

前幾年負責過搜狐影音的播放器內核,這里主要記錄、總結一下。

2 主要功能

  • 播放在線mp4/flv;
  • 疊加字幕/彈幕。

3 模塊層次結構

  • PlayerEngine.dll:主要提供對外接口,并進行音視頻的同步;
  • PlayerCodecs.dll:主要負責音視頻分離、音視頻解碼,也就是Demux和Decoder;
  • VADSDisplay.dll:主要調用DirectSound、DirectShow的Filter實現音視頻的渲染;
  • flyfoxDSFilter.dll:實現了一個自定義的Source Filter,用于將視頻數據推送給DirectShow渲染器;
  • flyfoxLocalPlayer.dll:實現了一個由DirectShow Filter組成的本地播放器。

4 線程模型/數據流

  • 外部線程將視頻數據寫入臨時文件;
  • Demux線程將數據從臨時文件讀入,并解析成未解碼的音頻、視頻Sample,放入各自的未解碼Samle隊列中;
  • 音頻解碼線程、視頻解碼線程分別從未解碼的音頻、視頻隊列中獲取Sample,進行解碼,并放入各自的已解碼Sample隊列中;
  • 音頻渲染線程從已解碼的音頻Sample隊列讀取已經解碼的PCM數據,放入DirectSound緩沖中進行播放;
  • 視頻渲染線程從已解碼的視頻Sample隊列中讀取已經解碼的YUV圖像幀,以音頻當前的播放時間戳為準進行音視頻同步,同步后視頻數據交給視頻渲染器進行渲染。

5 在線播放關鍵用例

5.1 Play mp4

5.1.1 單個分段

單個mp4分段是一個完整的mp4文件,為了播放一個mp4文件,需要:

  • 解析mp4中的音、視頻Sample;
  • 解碼這些音、視頻Sample;
  • 渲染這些音、視頻Sample。
    實際上播放任何一個文件都需要3個模塊來分別提供這些能力:
  • Demux:分離器,將封裝在一個文件中的音、視頻Sample分離出來;
  • Decoder:解碼器,將未解碼的音頻Sample解碼成PCM數據,將未解碼的視頻Sample解碼成YUV數據,分為音頻解碼器和視頻解碼器;
  • Render:渲染器,分為音頻渲染和視頻渲染,分別用于渲染音頻、視頻數據,在系統底層將這些數據送入指定的硬件,從而實現播放聲音、視頻的效果。
5.1.1.1 解封裝

由Demux進行音、視頻Sample的解析,這里啟動一個Demux線程,讀取文件中的音、視頻Sample,分別放入待解碼音、視頻隊列。
線程函數:flyfox_player_mov_demux_read_cb
這個線程首先要解析mp4頭,只有解析完mp4頭才能進行后續的音、視頻Sample的讀取。下面通過讀取視頻Sample來展示需要哪些mp4頭中的信息。
在緩存數據足夠的情況下,讀取視頻Sample的過程:

  • 首先維護一個當前需要讀取的視頻Sample的索引,這個索引將會持續累加;
  • 查詢stsc box,得到視頻Sample索引對應的Chunk索引,以及該Sample在Chunk內的偏移數;
  • 查詢stco box,得到該Chunk的偏移;
  • 查詢stsz,通過累加Chunk的偏移和在Chunk的各個Sample的大小,得到指定Sample的偏移以及大小;
  • 從文件中的指定偏移讀取指定大小的數據,得到一個視頻Sample,但是該Sample在送入待解碼視頻Sample隊列之前需要先做一些調整;
  • 如果該Sample是文件第一個Sample,那么是IDR幀,需要重置解碼器,傳遞一些解碼參數。在第一個Sample前需要增加AVC Decoder Configuration Record,該信息位于moov->trak->mdia->minf->stbl->avc1->avcC中,主要包含sps、pps等數據,目的是將解碼器需要的一些參數告訴解碼器,否則解碼器無法正常工作;
  • 在Sample中包含若干個NALU,視頻數據送入解碼器之前需要將每個NALU前面的x個字節的NALU長度修改成分隔符“00 00 00 01”,x也是從 AVC Decoder Configuration Record中獲得,一般是4,mp4中放x個字節的NALU長度是為了方便讀取NALU,而解碼器需要NALU之間通過“00 00 00 01”進行分隔,否則解碼器無法正常解碼;
  • 經過上述步驟讀取并處理后的Sample是一個解碼器可以處理的Sample,可以放入待解碼視頻Sample隊列。
    根據上述描述可以知道,解析視頻Sample需要用到的mp4 box如下:
  • 序號Box字段作用
    1.stscFirst Chunk、Sample Per Chunk可以從Sample索引獲得Chunk索引,以及Sample在Chunk內的索引。
    2.stcoChunk Offset每個Chunk的Offset。
    3.stszSample Size每個Sample的大小。
    4.avcCAVC Decoder Configuration Record獲得NALU長度以及ssp、pps等解碼參數。

    對音頻Sample的讀取來說就比較簡單,直接通過stsc、stco、stsz獲取到指定的Sample的偏移、大小,然后從文件中讀取Sample即可,也就是只需要實現讀取視頻Sample的1~5步對應的操作,然后將讀到的音頻Sample直接放入待解碼音頻Sample隊列。

    5.1.1.2 解碼

    這里使用ffmpeg進行音、視頻的解碼。

  • 視頻解碼步驟
    以H264解碼為例:
    a) 初始化:
  • //注冊所有編解碼器。 avcodec_register_all(); //初始化AVPacket,作為輸入參數。 AVPacket packet; //聲明AVPacket。 av_init_packet(&packet); //輸入Sample將傳入packet。//創建AVFrame,作為解碼輸出參數。 AVFrame* pFlyfoxAVFrame = avcodec_alloc_frame();//找到H264解碼器 AVCodec *pAVCodec = avcodec_find_decoder(AV_CODEC_ID_H264); //創建AVCodecContext AVCodecContext* pAVCodecContext = avcodec_alloc_context3(pAVCodec);//打開解碼器 avcodec_open2(pAVCodecContext, pAVCodec,0);

    b) 解碼

    //設置輸入參數 packet.data = src; packet.size = size;//解碼 int got_pic = false; avcodec_decode_video2(&pAVCodecContext,pFlyfoxAVFrame,&got_pic,&packet);

    c) 拷貝數據
    解碼出來的YUV數據存放在AVFrame結構中,Pixel Format默認為YUV420P(I420P)類型,拷貝數據用到的成員如下:

    //拷貝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++) //行數減半 { pSrc = pFlyfoxAVFrame->data[1] + i * pFlyfoxAVFrame->linesize[1]; memcpy(pDst, pSrc, pFlyfoxAVFrame->width/2);pDst += pFlyfoxAVFrame->width/2; //列數減半 }//拷貝V分量 for(i = 0; i < g_pFlyfoxAVFrame->height/2; i++) //行數減半 { pSrc = pFlyfoxAVFrame->data[2] + i * pFlyfoxAVFrame->linesize[2]; memcpy(pDst, pSrc, pFlyfoxAVFrame->width/2); pDst += pFlyfoxAVFrame->width/2; //列數減半 }
  • 音頻解碼步驟:
    音頻解碼過程跟視頻解碼基本相同,主要區別:
    • 尋找的解碼器是AAC的解碼器;
    • 在解碼前,在輸入的音頻數據前要加一個AAC ADTS頭;
    • 要調用avcodec_decode_audio4解碼;
    • 解碼后得到AV_SAMPLE_FMT_FLTP類型的數據,根據渲染器需要必須轉換成AV_SAMPLE_FMT_S16類型,也就是浮點類型轉換成16位整型。
      其他步驟完全相同。

    a) 初始化:

    //注冊所有編解碼器。 avcodec_register_all(); //初始化AVPacket,作為輸入參數。 AVPacket packet; //聲明AVPacket。 av_init_packet(&packet); //輸入Sample將傳入packet。//創建AVFrame,作為解碼輸出參數。 AVFrame* pFlyfoxAVFrame = avcodec_alloc_frame();//找到AAC解碼器 AVCodec *pAVCodec = avcodec_find_decoder(CODEC_ID_AAC); //創建AVCodecContext AVCodecContext* pAVCodecContext = avcodec_alloc_context3(pAVCodec);//打開解碼器 avcodec_open2(pAVCodecContext, pAVCodec,0);

    b) 解碼

    //設置輸入參數 packet.data = src; packet.size = size;//解碼 int got_audio = false; avcodec_decode_audio4 (&pAVCodecContext,pFlyfoxAVFrame,&got_audio,&packet);//如果是AV_SAMPLE_FMT_FLTP類型需要轉換成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框架來播放視頻,在DirectShow框架中,所有的功能被封裝在各個Filter中。DirectShow中提供了一些Render Filer來渲染視頻,例如evr、vmr7、vmr9等,可以充分利用顯卡進行硬件加速,達到比較好的顯示效果。為了將已經解碼的視頻數據交給Render Filer,需要建立一個Source Filer,通過管腳將Source Filer和Render Filer進行連接,類似一個管道,在Source Filer的輸出管腳上PushFrame時,Render Filer將能通過輸入管腳獲得視頻數據,交給顯卡渲染播放。

    flyfoxDSFilter.dll實現了一個視頻的Source Filer,作為一個COM組件,其實現了COM組件的相關接口,并實現了一個IFlyfoxDirectSrc接口,用于向外部提供Source Filter的相關功能,PushFrame就是其中一個方法,用于推送視頻數據。

    在VADSDisplay.dll中,封裝了音、視頻Render Filer的相關接口,并將Render Filter與Source Filter連接,解碼器在解碼得到裸數據后,播放線程調用VADSDisplayer的接口,將解碼后的視頻數據分別送入各自的Render Filer。

    注意這里沒有使用DirectShow的音頻DirectSound Filter,而是直接調用DirectSound的接口,這樣可以不用實現音頻的Source Filter,同時更靈活運用DirectSound的特性。

    5.1.1.4 音視頻同步

    這里采用的時鐘源是音頻流的時鐘,因為聲卡基本都有自己的時鐘源。

    在打開文件后,從文件頭中獲得了音頻的碼率,從而得到了音頻數據偏移跟音頻播放時間戳的關系:音頻播放時間戳=音頻數據偏移/音頻碼率。

    這個播放時間在DirectShow中稱為流時間(Stream Time),是描述相對于某個參考位置(如開始播放時間)的時間差。
    音頻播放會調用DirectSound的接口,DirectSound維護了當前播放的音頻數據緩沖的播放偏移,因此可以通過上述公式換算成音頻時間戳。

    視頻渲染線程首先獲得這個音頻時間戳ta,然后檢查當前準備播放的視頻Sample的時間戳tv,如果ta≥tv,則該視頻Sample沒有提前到來,應該立刻播放,如果時間差過大,則考慮丟棄;如果ta<tv,說明還沒有到該Sample的顯示時間,應該等待tv-ta的時間。

    5.1.2 多個分段

    搜狐視頻的單個劇一般由多個分段構成,每個分段一般是5分鐘,在播放一個劇的多個分段的過程中需要面臨的一個問題是不同分段之前的切換,如果分段切換的過程中出現卡頓,則會影響用戶的觀看體驗,這里采用了預加載下一分段的辦法來解決這個問題。

    在flyfox_demux_decoder_filter中維護一個Demux demux[2]的數組,分別表示播放中的和預加載中的Demux,每個Demux將處理不同的分段文件。Decoder與Demux是解耦的,使用一個nSourceFilterIndex索引來表示當前正在使用的Demux,在需要切換的時候,關閉掉當前播放的Demux后,nSourceFilterIndex設置為正在預加載的Demux的索引,Decoder只需要從nSourceFilterIndex指定的Demux獲取數據即可。

    注意預加載啟動的時機,如果太快啟動預加載則可能會造成浪費。比如用戶可能看到分段中間位置就Seek到文件的最后一個分段,而不是下一個分段。這里采取的策略是在當前分段播放到最后30秒的時候啟動下一個分段的預加載,這個時候可以認為用戶期望觀看下一分段。

    5.2 Seek MP4

    5.2.1 Seek前的準備

    在進行實際的mp4 seek之前有一些操作,因為seek的時間可能不在當前已經緩存的數據里面。

    影音的seek是基于整劇時間的seek,由于一個劇是由多段文件組成,在進行實際的mp4文件seek前,需要將seek絕對時間換算成對應的分段i和分段內的時間偏移t,如果已經緩存了第i個分段mp4的完整數據,那么直接在這個分段內seek到時間t,否則需要下載第i個分段的數據。注意這里并不是下載整個分段的完整數據,而是下載seek到時間t后的分段mp4,對播控來說實際上是一個完整的mp4,但是并不是原始的完整分段,這樣做可以減少mp4頭的下載量,提高啟播速度。播控需要維護這個偏移時間t,在下次seek的時候,如果定位到同一分段i的時間t1,并且t1>=t,那么可以直接在上次下載的mp4文件中seek到t1-t位置。下面需要判斷數據是否足夠,如果數據足夠就直接在mp4文件內seek,如果不夠的話,需要等待或者去請求t1開始的文件。

    如果seek的時間被定位到預加載的分段里面,那么上述的時間判斷可以省去,因為預加載的是完整的分段數據,這個時候只需要判斷數據是否足夠即可。

    5.2.2 實際的seek

    Seek的目標就是將時間換算成文件偏移offset,從offset開始的位置開始讀取音、視頻的Sample,達到修改播放時間的目的。
    mp4文件內的seek跟mp4頭關系緊密,需要解析mp4頭中的若干box才能提供足夠的信息。主要涉及的Box以及提供的信息如下:

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

    視頻Seek的步驟:

  • 通過視頻trak->mdia->mdhd box得到Time Scale,也就是一秒有多少個時間單位;
  • 查詢stts box,這個box里面是一個表,每個記錄包含:一個Sample包含的時間單位數,擁有相同時間單位數的Sample的個數,遍歷這個表,累加訪問到的每個Sample的時間,直到累積時間≥seek time,這個時候得到最接近seek time的Sample索引;
  • 查詢stss,這個box里面是一個表,里面有序地記錄了每個關鍵幀的索引,通過二分查找,可以得到距離步驟2中的Sample索引最近的關鍵幀Sample索引,之所以尋找關鍵幀,是因為只有關鍵幀才能夠獨立解碼,普通幀需要參考幀,仍然需要等待關鍵幀;
  • 再次查詢stts box,得到該關鍵幀對應的時間real seek time,也就是實際上能夠seek到的時間;
  • 查詢stsc box,該box是一個表,里面的每個記錄是一個Chunk組,組內的每個Chunk包含相同的Sample個數,每個記錄包含:Chunk組的開始Chunk索引,每個Chunk的Sample數,從表開始遍歷這個表,疊加Sample數,直到累加的Sample數=關鍵幀的Sample索引,這個時候可以得到其所處的Chunk索引以及在Chunk內的Sample偏移數;
  • 查詢stco,得到步驟5中的Chunk索引對應的偏移;
  • 查詢stsz,利用關鍵幀Sample索引和其在Chunk內的Sample偏移數,得到這個關鍵幀Sample相對于Chunk開始位置的偏移,這個偏移加上Chunk的偏移,就得到關鍵幀Sample的偏移。
    音頻Seek的步驟和視頻Seek基本一樣,但是比視頻Seek簡單,沒有查找關鍵幀的過程,直接通過普通Sample查找其偏移即可。
  • 5.3 Play flv

    由于flv的音頻、視頻編碼與mp4完全相同,所以復用解碼的邏輯。實際上除了封裝格式不同,flv播放的其他邏輯跟mp4播放基本相同(56的flv只有一個分段,比多段的mp4更好處理),因此只需要實現一個flv Demux來解析flv中的頭、音頻、視頻數據。
    flv的解析包括解析flv頭和數據tag,實際上,flv除了9個字節的沒有多少信息的固定頭之外,所有數據都是由tag組成。flv有3種tag,分為Metadata Tag(或者叫腳本tag)、Video Tag、Audio Tag。其前3個tag比較特殊,基本可以認為是flv頭,解析了這3個頭,就可以進行正常的flv播放、seek。這個3個tag分別是:

    • 第1個Tag,Metadata Tag:包含了關鍵幀列表、視頻寬高、時間長度、文件大小等;
    • 第2個tag,Vidao Tag:主要包含AVC Decoder Configuration Record、視頻的格式等;
    • 第3個tag,Audio Tag主要包含Audio Specifig Config、音頻的格式等。
      后續的所有tag基本都是攜帶音頻、視頻數據的Tag,可以由前面的3個tag的信息進行解析。例如視頻IDR幀需要的pps、sps來自第1個Video Tag的AVC Decoder Configuration Record,音頻解碼需要的一些ADTS信息來自第1個Audio Tag的Audio Specifig Config。

    5.4 Seek flv

    Flv的seek操作與mp4有所區別,mp4的seek基于時間,將seek的絕對時間換算成分段號和分段內的時間偏移之后,向服務器請求某個分段某個時間偏移的mp4文件。這個操作成立的前提是:CDN支持這樣的接口。
    但是對flv來說CDN并沒有這樣的接口,只有基于Http Range請求的接口,也就是基于文件偏移。因此,flv的Seek需要播放引擎先獲得flv seek依賴的完整信息,最主要的是關鍵幀列表,注意這個關鍵幀列表可能位于Script Tag中,但它并不是標準。如果沒有關鍵幀列表,那么flv的seek將會變得低效,必須按照碼率計算seek時間對應的文件偏移,這個偏移并不是準確的tag偏移,只是估計tag偏移,必須下載這個位置之前的flv數據,累加tag的偏移,直達指針位置首次超過seek的估計tag偏移,并且需要繼續尋找關鍵幀對應的tag,這個時候才能完成一次seek。
    幸好,絕大多數flv都遵守這么一個規則,在Script Tag中按照時間順序存放了關鍵幀列表,這個列表中,每個字段包含了關鍵幀的時間和偏移,這樣flv的Seek就只有一個簡單的二分查表的過程,比mp4的seek更簡單。
    Seek過程:

    • 檢查flv的頭是否完整,如果不,則繼續下載flv頭;
    • 如果flv頭完整,則檢查關鍵幀列表,如果沒有,則返回失敗;
    • 二分查找flv關鍵幀列表,獲得離目標時間最接近的關鍵幀位置和偏移;
    • 從關鍵幀偏移位置開始讀取tag和里面的Sample。

    5.5 軟字幕的實現

    在Source Filter和Render Filer中間增加一個VsFilter,由VsFilter實現所有的字幕疊加操作,并向外提供相關的接口。其主要原理:VsFilter通過獲取輸出的文字路徑點集合,轉化成形狀,并光柵化成Bitmap像素,然后與輸入的圖像進行Alpha混合,達到字幕疊加的目的,實際上是圖像疊加。所有的操作都是使用CPU進行計算,所以會明顯增加CPU的開銷。
    官方老版本的VsFilter性能較差,這里使用的是第3方優化后的XyVsFilter,與官方版本的主要區別是進行了大量的緩存,省去一些重復性的渲染以降低CPU使用率。
    主要流程:

    • 加載VsFilter.dll,創建VsFilter的com實例,并獲取IID_IDirectVobSub接口,此時將創建文件監控線程;
    • 連接Source Filter、VsFilter、Render Filter,此時將創建字幕圖片生成線程;
    • 調用IID_IDirectVobSub接口的put_FileName方法,設置字幕文件,VsFilter將加載該字幕文件,同時字幕圖片生成線程將建立字幕圖片緩存;
    • Filter Graph開始工作后,CDirectVobSubFilter::Transform函數獲得輸入Sample以及時間戳,通過輸入sample的時間戳查找SubPic緩存隊列中的圖片,如果查不到,則從Entry中查找并生成bitmap,然后將字幕bitmap與輸入sample的surface進行疊加(alphablt),疊加完成進行適當的轉換(轉成YUY2),然后拷貝到輸出Sample。SubPic緩存隊列維持長度為10,每消耗一條,則補充一條。

    5.6 緩存策略

    上層的數據會寫入一個臨時文件,在加載完成后,該臨時文件是一個完整的mp4分段文件,并在播放完成后刪除。在Demux內部維護一個初始大小為2M的內存緩存,Demux從臨時文件一次讀64KB數據到這個緩存,每次解析mp4頭、解析Sample的時候其數據源都直接來自這個內存緩存。
    在播放DRM視頻時,對緩存有了新的需求:

    • 涉及版權安全,不能寫文件;
    • 單個文件可能比較大,mp4頭可能會大于2M,內存緩存無法一次容納一個mp4頭;
      因此在播放DRM視頻時,虛擬了一個內存的文件緩存,與磁盤文件緩存提供一樣的接口,用于為Demux提供數據。同時Demux的內存緩存大小可變,在緩存大小不夠時進行擴展。

      由于DRM mp4文件在內存中緩存,所以不能將整個mp4完全緩存在內存中,任何時刻,內存虛擬文件中只保留mp4連續的一部分數據。內存虛擬文件接收到Demux的讀取請求后檢查緩存內是否有指定偏移的數據,如果有則拷貝返回,如果沒有則向傳輸層“拉”數據。每次內存虛擬文件的數據被讀走,檢查有效的可用數據大小如果不到一半的緩存總大小,則再次向傳輸層“拉”數據,期望填充滿整個緩存。

    5.7 VMR9和D3D的應用-全景視頻播放

    VMR9有3種模式:Window、Windowless、Renderless。

    • Window模式:需要Render創建自己的播放窗口并設置為上層應用窗口的子窗口,為響應父窗口的消息,需要進行父窗口與子窗口的消息交互;
    • Windowless模式:Render無需創建自己的播放窗口,直接在上層應用窗口上繪制,在應用窗口重繪、修改窗口時需要通知Render;
    • Renderless模式:VMR不再內部渲染,需要在外部借助D3D等手段自行渲染。
      在使用VMR9渲染的情況下,使用的策略是優先使用Renderless模式,如果無法使用Renderless模式,則使用Windowless模式。
      在Renderless模式下,需要創建一個Presenter-Allocator,用于替代VMR9內部的渲染機制,并接受VMR9傳遞的圖像數據,使用D3D進行渲染。

    D3D對圖像數據的處理流程:創建某個形狀的頂點緩沖,并創建一個紋理,將圖像數據拷貝到紋理上,然后將紋理貼圖到頂點緩沖對應的形狀,之后就是D3D內部的渲染過程。
    CPlaneScene與CSphereScene的區別是CPlaneScene創建矩形頂點緩沖而CSphereScene創建球面頂點緩沖,分別對應普通視頻播放和全景視頻播放。
    在全景視頻播放時,圖像一般是2:1的寬高比,可以完整覆蓋在球面上,這樣通過球面的扭曲可以將本身就扭曲的360°全景視頻在視場內還原,另外通過響應鼠標、鍵盤等事件調整視點、透視矩陣,可以完整實現全景視頻的播放以及旋轉、拉近、拉遠等交互。

    6 本地播放

    本地播放視頻全部通過DirectShow的Filter實現。
    在DirectShow內部,Filter的連接一般由GraphBuilder自動完成,稱為智能連接。但是在這里自定義了類似智能連接的過程,使用策略是:先使用自定義的的連接策略,如果自定義連接策略失敗則使用DirectShow的智能連接。
    自定義連接流程:

  • 定義一個FilterInfo.xml,描述了所有支持的Filter以及信息,初始化時將這些Filter的信息全部加載;
  • 從Source Filter開始,先枚舉這項Filter信息,找到所有Source Filter,檢查該Filter是否支持傳入的文件url和媒體類型,如果支持則設置其為使用的Source Filter并從該Source Filter開始渲染;
  • 進入渲染Filter流程,不論是什么類型的Filter都走這個流程:
    1) 枚舉輸出管腳,渲染該管腳:
    1> 枚舉該管腳上的所有媒體類型,渲染該媒體類型;
    a) 枚舉Filter Graph上的所有Filter,嘗試用該媒體類型連接這些Filter;
    b) 如果連接成功,則遞歸調用渲染Filter流程繼續向下渲染該Filter。
    用以上迭代+遞歸的辦法直到連接到Render Filter,最后完成整個連接過程。一個典型的Filter連接圖如下:
    • File Source(Async.):Source Filter,實現文件的讀取,屬于“拉”模式;
    • 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,用于聲音渲染。

    總結

    以上是生活随笔為你收集整理的搜狐影音播放器内核设计的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。