Android音視頻-初識FFmpeg

已經很久沒有寫過技術博客了,這段時間加入了新公司,主要時間花在熟悉新業務的技術上。而新的業務主要跟音視頻相關,關於音視頻的嘗試在加入新公司之前,自己有做相關demo的嘗試與學習,可以參看音視頻相關學習demo。當然,那都是自己“想當然”學習的一些東西,雖然實際工作中並沒有派上太大的用處,但讓我對音視頻相關的基礎知識有了一定的概念,對後面的技術嘗試做了鋪墊。第一個技術挑戰比較大的就是進行:視頻抽幀,關於視頻抽幀網上有很多很多文章進行講解,但……我始終沒有找到一個效率很高的解決方案。直到我遇見了 ffmpeg,彷彿打開了新世界的大門……

關於FFmpeg

剛接觸 ffmpeg 時,我一臉懵逼,完全不知道該怎麼做,也不知道在哪裏開始進行學習,後來在雷霄驊大神的博客中漸漸找到了感覺,膜拜!不過雷神的博客代碼是基於老版本的 ffmpeg api,推薦搭配官方example,先跑通雷聲的博客,再對照官方的例子對進行api相關接口的修改。

當然,想要使用 ffmpeg編寫代碼之前,我們首先要做的是對 FFmpeg 進行so庫編譯,這一步也是難倒了衆多的英雄好漢,引用FFmpeg so庫編譯作者的話:

爲什麼FFmpeg讓人覺得很難搞? 我想主要是因爲邁出第一步就很困難,連so庫都編譯不出來,後面的都是扯淡了。

參考FFmpeg so庫編譯文章能成功地打包出 ffmpeg.so,接下來就是添加在項目中運行。

踏上 FFmpeg 音視頻之路

關於音視頻等開發,無論是做特效渲染還是做視頻播放,那麼最重要也是最基本的步驟就是:音視頻解碼

衆所周知的是視頻是由一幀幀視頻幀(圖片)/音頻幀編碼組合而成

動畫

視頻解碼要做的就是解碼出視頻文件中的每一幀,我們以:將視頻轉化爲一幀幀的圖片作爲例進行學習。

FFmpeg 提取視頻每一幀圖像

在學習之前,我們思考一個問題:拋開 ffmpeg,如果讓你去設計一個提取的代碼,n你會怎麼設計?

因爲視頻是以文件流的形式存在,我相信很多人一上來就能想到這樣的結構:

while (!EOF) { //當文件流沒有結束
    Stream stream = getStream(); //獲取一定區域的stream
    Frame steam = getFrame(stream); //Stream轉化爲視頻幀
    Picture picture = decodeFrame(steam); //將視頻幀轉化爲 .jpeg等格式圖片
}

的確是這樣的,這裏是給出一份ffmpeg提取視頻幀圖片的核心邏輯:

    AVFrame frame = av_frame_alloc(); 
    while (true) {
        if (av_read_frame(fmt_ctx, &avpkt) >= 0) { // Return the next frame of a stream.
            if (avpkt.stream_index == video_stream_index) { //標識該AVPacket所屬的視頻/音頻流。
                avcodec_send_packet(codeCtx, &avpkt); //Supply raw packet data as input to a decoder.
                while (avcodec_receive_frame(codeCtx, frame) == 0) { //Return decoded output data from a decoder.
                    snprintf(buf, sizeof(buf), "%s/frame-%d.jpg", out_filename, frame_count);
                    saveJpg(frame, buf);
                }
                frame_count++;
            }
            av_packet_unref(&avpkt);
        } else {
            LOGE("//Exit");
            break;
        }
    }

上面的代碼塊就是 ffmpeg 進行視頻解碼最核心的邏輯了,主要的註釋也貼在了代碼上,完整代碼請查看video_to_jpeg.cpp,查看完整的代碼後,會感覺到很驚訝:爲什麼這麼複雜?特別是前面的初始化操作。放心,ffmpeg就像一套組合拳,有固定不變的套路,寫一次就足夠了,瞭解了其中的流程,之後理解起來就會很容易了。

上面的代碼我們還可以做一些其他處理,比如只獲取關鍵幀、查找指定時間戳位置的幀、視頻按2s一幀進行抽取、視頻不保存爲jpeg文件轉化爲Java的bitmap?

這些實現需求也都是基於上述核心模塊進行修改:

如果想只獲取關鍵幀,可以利用AVFrame對象的屬性AVFrame->key_frame進行判斷。

查找指定時間戳位置的幀:利用 av_seek_frame查找到指定幀時間最近的關鍵幀,然後依次進行編碼,直到pts與目標時間相近

視頻按2s一幀進行抽取:簡單的操作可以去獲取視頻fps,比如視頻25fps,可以使用一個計數器判斷if(frame_count%25==0),這時候則是剛好1s。當然這樣子性能不太好。如果需要追求性能,那麼也可以利用av_seek_frame,查找目標時間附近,然後循環進行解碼直到目標時間。

視頻不保存爲jpeg文件轉化爲Java的Bitmap:只需要對最終獲取的 AVFrame做不一樣的操作進行了,獲取到對應的buffer,再利用jni調用構造 Java 的 bitmap 對象。

可以做的還有很多……

總結

提取視頻圖片這個功能只是 FFmpeg 強大功能的九牛一毛,需要探究的還有很多很多……

如果能跑起來 FFmpeg 最簡單的例子,已經邁出了很大一步了,但如果要理解其中的原理,還需要更多的基礎知識,以及像AVPacketAVFrameAVCodec ……每一個類的數據結構,以及實現都需要仔細研究。

自己在網上找到的 FFmpeg 相關的教程,以及自己想要去實現的功能的資源太少,很多東西都需要自己去摸索。有時候我總在懷疑:爲什麼這麼基礎且很實用的功能沒有現成的輪子? 這可能也是現在音視頻相關開發的現狀吧,成熟可用的輪子相對而言較少,以及相關技術的分享可能不太好做。既然沒有,那就靠自己一點點積累吧。

學習之路,任重而道遠吶。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章