<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          基于 FFmpeg 的跨平臺(tái)播放器實(shí)現(xiàn)

          共 5880字,需瀏覽 12分鐘

           ·

          2021-06-22 15:38

          背景


          隨著游戲娛樂等直播業(yè)務(wù)的增長,在移動(dòng)端觀看直播的需求也日益迫切。但是移動(dòng)端原生的播放器對(duì)各種直播流的支持卻不是很好。


          Android 原生的 MediaPlayer 不支持 flv、hls 直播流,iOS 只支持標(biāo)準(zhǔn)的 HLS 流。本文介紹一種基于 ffplay 框架下的跨平臺(tái)播放器的實(shí)現(xiàn),且兼顧硬解碼的實(shí)現(xiàn)。


          播放器原理


          直觀的講,我們播放一個(gè)媒體文件一般需要5個(gè)基本模塊,按層級(jí)順序:文件讀取模塊(Source)、解復(fù)用模塊(Demuxer)、視頻頻解碼模塊(Decoder)、色彩空間轉(zhuǎn)換模塊(Color Space Converter)、音視頻渲染模塊(Render)。


          數(shù)據(jù)的流向如下圖所示,其中 ffmpeg 框架包含了文件讀取、音視頻解復(fù)用的模塊。


          • 文件讀取模塊(Source)的作用是為下級(jí)解復(fù)用模塊(Demuxer)以包的形式源源不斷的提供數(shù)據(jù)流,對(duì)于下一級(jí)的Demuxer來說,本地文件和網(wǎng)絡(luò)數(shù)據(jù)是一樣的。在ffmpeg框架中,文件讀取模塊可分為3層:


            • 協(xié)議層:pipe,tcp,udp,http等這些具體的本地文件或網(wǎng)絡(luò)協(xié)議

            • 抽象層:URLContext結(jié)構(gòu)來統(tǒng)一表示底層具體的本地文件或網(wǎng)絡(luò)協(xié)議

            • 接口層用:AVIOContext結(jié)構(gòu)來擴(kuò)展URLProtocol結(jié)構(gòu)成內(nèi)部有緩沖機(jī)制的廣泛意義上的文件,并且僅僅由最上層用AVIOContext對(duì)模塊外提供服務(wù),實(shí)現(xiàn)讀媒體文件功能。


          • 解復(fù)用模塊(Demuxer):的作用是識(shí)別文件類型,媒體類型,分離出音頻、視頻、字幕原始數(shù)據(jù)流,打上時(shí)戳信息后傳給下級(jí)的視頻頻解碼模塊(Decoder)??梢院?jiǎn)單的分為兩層,底層是 AVIContext,TCPContext,UDPContext 等等這些具體媒體的解復(fù)用結(jié)構(gòu)和相關(guān)的基礎(chǔ)程序,上層是 AVInputFormat 結(jié)構(gòu)和相關(guān)的程序。


            上下層之間由 AVInputFormat 相對(duì)應(yīng)的 AVFormatContext 結(jié)構(gòu)的 priv_data 字段關(guān)聯(lián) AVIContext 或 TCPContext 或 UDPContext 等等具體的文件格式。

            AVInputFormat 和具體的音視頻編碼算法格式由 AVFormatContext 結(jié)構(gòu)的 streams 字段關(guān)聯(lián)媒體格式,streams 相當(dāng)于 Demuxer 的 output pin,解復(fù)用模塊分離音視頻裸數(shù)據(jù)通過 streams 傳遞給下級(jí)音視頻解碼器。


          • 視頻頻解碼模塊(Decoder)的作用就是解碼數(shù)據(jù)包,并且把同步時(shí)鐘信息傳遞下去。


          • 色彩空間轉(zhuǎn)換模塊(Color Space Converter)顏色空間轉(zhuǎn)換過濾器的作用是把視頻解碼器解碼出來的數(shù)據(jù)轉(zhuǎn)換成當(dāng)前顯示系統(tǒng)支持的顏色格式


          • 音視頻渲染模塊(Render)的作用就是在適當(dāng)?shù)臅r(shí)間渲染相應(yīng)的媒體,對(duì)視頻媒體就是直接顯示圖像,對(duì)音頻就是播放聲音


          跨平臺(tái)實(shí)現(xiàn)


          在播放器得5個(gè)模塊中文件讀取模塊(Source)、解復(fù)用模塊(Demuxer)和色彩空間轉(zhuǎn)換模塊(Color Space Converter)這三個(gè)模塊都可以用 ffmpeg 的框架進(jìn)行實(shí)現(xiàn),而 FFmpeg 本身就是跨平臺(tái)的。


          因此,實(shí)現(xiàn)跨平臺(tái)的播放器的就需要抽象一層平臺(tái)無關(guān)的音視頻解碼、渲染接口。Android、iOS、Window 等平臺(tái)只需要實(shí)現(xiàn)各自平臺(tái)的渲染、硬件解碼(如果支持的話)就可以構(gòu)建一個(gè)標(biāo)準(zhǔn)的基于 FFmpeg 的播放器了。


          下圖是基于ffplay的基本播放流程圖:

          圖中紅色部分是需要抽象的接口的,結(jié)構(gòu)如下:


          其中 FF_Pipenode.run_sync 視頻解碼線程,默認(rèn)有 libavcodec 的軟解碼實(shí)現(xiàn),其他平臺(tái)可以增加自己的硬解碼實(shí)現(xiàn)。SDL_VideoOut 為視頻渲染抽象層,這里 overlay 可以是 Android的 NativeWindow,或者是 OpenGL 的 Texture。


          SDL_AudioOut 是音頻播放抽象層,可以直接操作聲卡驅(qū)動(dòng),SDL2.0 里就支持 ALSA、OSS 接口,當(dāng)然也可以用 Android、iOS SDK 中的音頻 API 實(shí)現(xiàn)。


          這里順便提下,隨著 Android、iOS 平臺(tái)的普及,ffmpeg 版本的也逐步支持了 Android、iOS 的硬件解碼器,如f fmpeg 在很早之前就支持了 libstagefright,最新的 ffmpeg2.8 也已經(jīng)支持了 iOS 的硬件解碼庫 VideoToolBox。從下面重點(diǎn)介紹下視頻硬解碼以及音視頻渲染模塊在移動(dòng)平臺(tái)上的實(shí)現(xiàn)。


          Android

          1.硬解碼模塊:


          Android 的硬解碼模塊目前有 2 種實(shí)現(xiàn)方案:


          libstagefright_h264:


          libstagefright 是 Android2.3 之后版本的多媒體庫,ffmpeg 早在 0.9 版本時(shí)就已經(jīng)將libstagefright_h264 收錄到自己的解碼庫中了,從 libstagefright.cpp 包括的頭文件路徑來看,是基于 Android2.3 版本的源碼。因此編譯 libstagefright 需要 Android2.3 的相關(guān)源碼以及動(dòng)態(tài)鏈接庫。


          ffmpeg 中的 libstagefright 目前只實(shí)現(xiàn)了 h264 格式的解碼,由于 Android 機(jī)型、版本的碎片化相當(dāng)嚴(yán)重,這種基于某個(gè) Android 版本編譯出來的 libstagefright 也存在很嚴(yán)重的兼容性問題,我在 Android4.4 的機(jī)型上就遇到無法解碼的問題。


          MediaCodec:


          MediaCodec 是 Google 在 Android4.1(API16)以后新提供的硬件編解碼 API,其工作原理如圖所示:



          以解碼為例,先從 Codec 獲取 inputBuffer,將待解碼數(shù)據(jù)填充到 inputbuffer,再將 inputbuffer 交給Codec,接下來就可以從 Codec 的 outputBuffer 中拿到新鮮出爐的圖像和聲音信息了。下面的這段實(shí)例代碼也許更能說明問題:


          MediaCodec codec = MediaCodec.createByCodecName(name);
          codec.configure(format,);
          MediaFormat outputFormat = codec.getOutputFormat(); // option B
          codec.start();
          for (;;) {
          int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
          if (inputBufferId >= 0) {
          ByteBuffer inputBuffer = codec.getInputBuffer();
          // fill inputBuffer with valid data

          codec.queueInputBuffer(inputBufferId,);
          }
          int outputBufferId = codec.dequeueOutputBuffer();
          if (outputBufferId >= 0) {
          ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
          MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
          // bufferFormat is identical to outputFormat
          // outputBuffer is ready to be processed or rendered.

          codec.releaseOutputBuffer(outputBufferId,);
          } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
          // Subsequent data will conform to new format.
          // Can ignore if using getOutputFormat(outputBufferId)
          outputFormat = codec.getOutputFormat(); // option B
          }
          }
          codec.stop();
          codec.release();


          令人沮喪的是,MediaCodec 只提供了 java 層的 API [現(xiàn)在提供了Native層的 API 了],而我們的播放器是基于 ffplay 架構(gòu)的,核心的解碼模塊是不可能移到 java 層的。


          既然我們移不上去,就只能把 MediaCodec 拉到 Native 層了,通過 (*JNIEnv)->CallxxxMethod 的方式將 MediaCodec 相關(guān)的 API 在 Native 層做了一套接口。嗯,現(xiàn)在我們可以來實(shí)現(xiàn)視頻的硬件解碼了:


          queue_picture 的實(shí)現(xiàn)如下圖所示:


          2.視頻渲染模塊:


          在渲染之前,我們必須先指定一個(gè)渲染的畫布,在android上這個(gè)畫布可以是ImageView,SurfaceView,TextureView或者是GLSurfaceView。


          關(guān)于在Native層渲染圖片的方法,我曾看過一篇文章,文中介紹了四種渲染方法:


          • Java Surface JNI

          • OpenGL ES 2 Texture

          • NDK ANativeWindow API

          • Private C++ API


          如果是用 ffmpeg 的 libavcodec 進(jìn)行軟解碼,那么使用 NDK ANativeWindow API 將是最高效簡(jiǎn)單的方案,主要實(shí)現(xiàn)代碼:


          ANativeWindow* window = ANativeWindow_fromSurface(env, javaSurface);

          ANativeWindow_Buffer buffer;
          if (ANativeWindow_lock(window, &buffer, NULL) == 0) {
          memcpy(buffer.bits, pixels, w * h * 2);
          ANativeWindow_unlockAndPost(window);
          }

          ANativeWindow_release(window);


          示例代碼中的 javaSurface 來自 java 層的 SurfaceHolder,pixels 指向 RGB 圖像數(shù)據(jù)。


          如果是使用了 MediaCodec 進(jìn)行解碼,那么視頻渲染將變得異常簡(jiǎn)單,只需在 MediaCodec 配置時(shí)(MediaCodec.configure)指定圖像渲染的 Surface,然后再解碼完每一幀圖像的時(shí)候調(diào)用 releaseOutputBuffer (index, true),MediaCodec 內(nèi)部就會(huì)將圖像渲染到指定的 Surface 上。


          3.音頻播放模塊


          Android 支持 2 套音頻接口,分別是 AudioTrack 和 OpenSL ES,這里以 AudioTrack 為例介紹下音頻的部分流程:


          由于 AudioTrack 只有 java 層的 API,我們也得像 MediaCodec 一樣在 Native 層重做一套 AudioTrack 的接口。



          這里解碼和播放是 2 個(gè)獨(dú)立的線程,audioCallback 負(fù)責(zé)從 Audio Frame queue 中獲取解碼后的音頻數(shù)據(jù),如果解碼后的音頻采樣率不是 AudioTrack 所支持的,就需要用 libswresample 進(jìn)行重采樣。


          iOS

          1. 硬解碼模塊


          從 iOS8 開始,開放了硬解碼和硬編碼 API,就是名為 VideoToolbox.framework 的 API,支持 h264 的硬件編解碼,不過需要 iOS 8 及以上的版本才能使用。這套硬解碼 API 是幾個(gè)純 C 函數(shù),在任何 OC 或者 C++ 代碼里都可以使用。首先要把 VideoToolbox.framework 添加到工程里,并且包含以下頭文件。


          解碼主要需要以下四個(gè)函數(shù):

          • VTDecompositionSessionCreate 創(chuàng)建解碼session

          • VTDecompressionSessionDecodeFrame 解碼一個(gè)frame

          • VTDecompressionOutputCallback 解碼完一個(gè)frame后的回調(diào)

          • VTDecompressionSessionInvalidate 銷毀解碼session


          解碼流程如圖所示:

          2. 視頻渲染模塊


          視頻的渲染采用 OpenGL ES2 紋理貼圖的形式。


          3. 音頻播放模塊


          采用 iOS 的 AudioToolbox.frameworks 進(jìn)行播放。數(shù)據(jù)流程和 Android 平臺(tái)是相同,不同的是,Android 平臺(tái)把 PCM 數(shù)據(jù)喂給 AudioTrack,iOS 上把 PCM 數(shù)據(jù)喂給 AudioQueue。

          總結(jié)

          其實(shí) ffpmeg 自帶的播放器實(shí)例 ffplay 就是一個(gè)跨平臺(tái)的播放器,得益于其依賴的多媒體庫 SDL 實(shí)現(xiàn)了多平臺(tái)的音視頻渲染。但是 SDL 庫過于龐大,并不適合整體移植到移動(dòng)端。本文介紹的跨平臺(tái)實(shí)現(xiàn)方案也是借鑒了 SDL2.0 的內(nèi)部實(shí)現(xiàn),只是重新設(shè)計(jì)了渲染接口。


          作者:許斌盛

          來源:https://cloud.tencent.com/developer/article/1004561


          推薦:

          Android FFmpeg 實(shí)現(xiàn)帶濾鏡的微信小視頻錄制功能

          全網(wǎng)最全的 Android 音視頻和 OpenGL ES 干貨,都在這了

          Android OpenGL ES 從入門到精通系統(tǒng)性學(xué)習(xí)教程

          瀏覽 75
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  中国婬乱a| 久久三级精品视频 | 后入少妇视频 | 日韩人妻无码一区二区 | 日韩AV中文字幕大香 |