FFmpeg 調(diào)用 Android MediaCodec 進(jìn)行硬解碼(附源碼)

FFmpeg 在 3.1 版本之后支持調(diào)用平臺(tái)硬件進(jìn)行解碼浆竭,也就是說(shuō)可以通過(guò) FFmpeg 的 C 代碼去調(diào)用 Android 上的 MediaCodec 了私沮。

在官網(wǎng)上有對(duì)應(yīng)說(shuō)明,地址如下:

https://trac.ffmpeg.org/wiki/HWAccelIntro

image

從圖中可以看到香璃,不僅僅是 Android 上支持 MediaCodec这难,iOS 上也支持 VideoToolbox,連 Windows 上的 Direct3D 11 都有支持了葡秒。

注意:Android MediaCodec 目前僅支持解碼雁佳,還不支持編碼呢。

不過(guò)同云,為了驗(yàn)證是否可行糖权,做個(gè)簡(jiǎn)單的演示,最后會(huì)有完整的的代碼給出炸站。

首先是 FFmpeg 的編譯星澳。它的編譯有很多開(kāi)關(guān)選項(xiàng),要確保打開(kāi)了 mediacodec 相關(guān)的選項(xiàng)旱易,具體如下:

--enable-mediacodec
--enable-decoder=h264_mediacodec
--enable-decoder=hevc_mediacodec
--enable-decoder=mpeg4_mediacodec
--enable-hwaccel=h264_mediacodec

可以看出 mediacodec 支持的編碼格式有 h264禁偎、hevc、mpeg4 三種可選阀坏,不在范圍內(nèi)的就還是考慮軟解吧如暖。

關(guān)于如何編譯,就不詳細(xì)闡述了忌堂,后面再專門(mén)寫(xiě)一篇來(lái)介紹盒至。

編譯出對(duì)應(yīng)的 so 之后,可以打印一下 AVCodec 支持的格式列表士修,看看有沒(méi)有 mediacodec 枷遂。

具體代碼如下:

char info[40000] = {0};

AVCodec *c_temp = av_codec_next(NULL);
while (c_temp != NULL) {
    if (c_temp->decode != NULL) {
        sprintf(info, "%s[Dec]", info);
    } else {
        sprintf(info, "%s[Enc]", info);
    }

    switch (c_temp->type) {
        case AVMEDIA_TYPE_VIDEO:
            sprintf(info, "%s[Video]", info);
            break;
        case AVMEDIA_TYPE_AUDIO:
            sprintf(info, "%s[Audio]", info);
            break;
        default:
            sprintf(info, "%s[Other]", info);
            break;
    }
    sprintf(info, "%s %10s\n", info, c_temp->name);
    c_temp = c_temp->next;
}

通過(guò) AVCodec 的 next 指針進(jìn)行遍歷,然后打印出結(jié)果棋嘲,看到下面的內(nèi)容說(shuō)明編譯成功了酒唉。

image

支持的格式里面已經(jīng)有了 h264_mediacodec 和 mpeg4_mediacodec 了。


接下來(lái)就進(jìn)行解碼了沸移。關(guān)于 FFmpeg 解碼的 API 調(diào)用痪伦,在公眾號(hào)以前發(fā)布的文章中說(shuō)過(guò)多次侄榴,就不詳細(xì)講解流程了,簡(jiǎn)單概況一下:

  1. 首先通過(guò) avformat_open_input 方法打開(kāi)文件网沾,得到 AVFormatContext 牲蜀。
  2. 然后通過(guò) avformat_find_stream_info 查找文件的視頻流信息。
  3. 得到文件相關(guān)信息和視頻流信息绅这,主要還是為了得到編碼格式信息涣达,然后好找到對(duì)應(yīng)的解碼器。也可以通過(guò) avcodec_find_decoder_by_name 方法直接找具體的解碼器证薇。
  4. 有了解碼器就可以創(chuàng)建解碼上下文 AVCodecContext度苔,并通過(guò) avcodec_open2 方法打開(kāi)解碼器
  5. 然后通過(guò) av_read_frame 讀取文件的內(nèi)容好進(jìn)行下一步的解碼。
  6. 接下來(lái)就是熟悉的 avcodec_send_packet 發(fā)送給解碼器浑度,avcodec_receive_frame 從解碼器取回解碼后的數(shù)據(jù)寇窑。

重點(diǎn)講解一下調(diào)用硬件解碼和普通解碼的一些區(qū)別:

第一步是要在 so 加載的 JNI_OnLoad 方法中將 JavaVM 設(shè)置給 FFmpeg 。

jint JNI_OnLoad(JavaVM *vm, void *res) {
    av_jni_set_java_vm(vm, 0);
    return JNI_VERSION_1_4;
}

缺少這一步就不能反射調(diào)用 Java 方法了箩张。


接下來(lái)還是判斷硬件解碼類型支不支持甩骏,上面是通過(guò) AVCodec 來(lái)判斷的,實(shí)際上 FFmpeg 都給出了硬件類型的定義先慷,在 AVHWDeviceType 枚舉變量中饮笛。

enum AVHWDeviceType {
    AV_HWDEVICE_TYPE_NONE,
    AV_HWDEVICE_TYPE_VDPAU,
    AV_HWDEVICE_TYPE_CUDA,
    AV_HWDEVICE_TYPE_VAAPI,
    AV_HWDEVICE_TYPE_DXVA2,
    AV_HWDEVICE_TYPE_QSV,
    AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
    AV_HWDEVICE_TYPE_D3D11VA,
    AV_HWDEVICE_TYPE_DRM,
    AV_HWDEVICE_TYPE_OPENCL,
    AV_HWDEVICE_TYPE_MEDIACODEC,
    AV_HWDEVICE_TYPE_VULKAN,
};

通過(guò) av_hwdevice_get_type_name 方法可以將這些枚舉值轉(zhuǎn)換成對(duì)應(yīng)的字符串,比如 AV_HWDEVICE_TYPE_MEDIACODEC 對(duì)應(yīng)的字符串就是 mediacodec 论熙,其實(shí)在源碼里面也是有的:

static const char *const hw_type_names[] = {
    [AV_HWDEVICE_TYPE_CUDA]   = "cuda",
    [AV_HWDEVICE_TYPE_DRM]    = "drm",
    [AV_HWDEVICE_TYPE_DXVA2]  = "dxva2",
    [AV_HWDEVICE_TYPE_D3D11VA] = "d3d11va",
    [AV_HWDEVICE_TYPE_OPENCL] = "opencl",
    [AV_HWDEVICE_TYPE_QSV]    = "qsv",
    [AV_HWDEVICE_TYPE_VAAPI]  = "vaapi",
    [AV_HWDEVICE_TYPE_VDPAU]  = "vdpau",
    [AV_HWDEVICE_TYPE_VIDEOTOOLBOX] = "videotoolbox",
    [AV_HWDEVICE_TYPE_MEDIACODEC] = "mediacodec",
    [AV_HWDEVICE_TYPE_VULKAN] = "vulkan",
};

和遍歷 AVCodec 一樣福青,也要遍歷 FFmpeg 是否支持 mediacodec 。

type = av_hwdevice_find_type_by_name(mediacodec);
if (type == AV_HWDEVICE_TYPE_NONE) {
    LOGE("Device type %s is not supported.\n", mediacodec);
    LOGE("Available device types:");
    while((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE)
        LOGE(" %s", av_hwdevice_get_type_name(type));
    LOGE("\n");
    return -1;
}

確定支持 mediacodec 脓诡,那么解碼就可以用了无午。前面提到,獲取文件信息主要是為了打開(kāi)解碼器的祝谚,但比如文件編碼格式的 H.264 宪迟,而支持 H.264 的解碼器除了軟解,還有 mediacodec 要怎么選擇呢交惯?

為了方便次泽,直接 avcodec_find_decoder_by_name 找到 mediacodec 的解碼器就行。

if (!(decoder = avcodec_find_decoder_by_name("h264_mediacodec"))) {
    LOGE("avcodec_find_decoder_by_name failed.\n");
    return -1;
}

找到解碼器之后商玫,還要得到該解碼器的一些配置信息箕憾,比如解碼出的格式是什么樣子的?mediacodec 解碼就是 NV21 這種拳昌。

for (i = 0;; i++) {
    // 解碼器的配置
    const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
    if (!config) {
        LOGE("Decoder %s does not support device type %s.\n",
                decoder->name, av_hwdevice_get_type_name(type));
        return -1;
    }
    if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
        config->device_type == type) {
        // 硬解的格式
        hw_pix_fmt = config->pix_fmt;
        break;
    }
}

目前 mediacodec 解碼還只有 buffer 模式,沒(méi)有直接解紋理的那種钠龙。

接下來(lái)就是給解碼上下文 AVCodecContext 添加一些硬件解碼的上下文炬藤。

static int hw_decoder_init(AVCodecContext *ctx, const enum AVHWDeviceType type)
{
    int err = 0;
    if ((err = av_hwdevice_ctx_create(&hw_device_ctx, type,
                                      NULL, NULL, 0)) < 0) {
        LOGE("Failed to create specified HW device.\n");
        return err;
    }
    // 硬解解碼的上下文
    ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
    return err;
}

完成了這一系列操作之后御铃,就是正常的解碼了,拿到解碼后的 AVFrame 內(nèi)容沈矿。

如果 AVFrame 格式和硬件解碼的配置格式一樣上真,那么要用 av_hwframe_transfer_data 方法將它做一下轉(zhuǎn)換,轉(zhuǎn)成正常的 YUV 格式羹膳。

if (frame->format == hw_pix_fmt) {
    /* retrieve data from GPU to CPU */
    if ((ret = av_hwframe_transfer_data(sw_frame, frame, 0)) < 0) {
        LOGE("Error transferring the data to system memory\n");
        goto fail;
    }
    tmp_frame = sw_frame;
} else
    tmp_frame = frame;

等完成這一些操作之后睡互,就已經(jīng)解碼成功了,實(shí)際運(yùn)行也是 OK 的陵像。

獲取完整源碼的話就珠,可以關(guān)注微信公眾號(hào):音視頻開(kāi)發(fā)進(jìn)階,回復(fù) 1019 獲取下載地址醒颖。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末妻怎,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子泞歉,更是在濱河造成了極大的恐慌逼侦,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腰耙,死亡現(xiàn)場(chǎng)離奇詭異榛丢,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)挺庞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)涕滋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人挠阁,你說(shuō)我怎么就攤上這事宾肺。” “怎么了侵俗?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵锨用,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我隘谣,道長(zhǎng)增拥,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任寻歧,我火速辦了婚禮掌栅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘码泛。我一直安慰自己猾封,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布噪珊。 她就那樣靜靜地躺著晌缘,像睡著了一般齐莲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上磷箕,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天选酗,我揣著相機(jī)與錄音,去河邊找鬼岳枷。 笑死芒填,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的空繁。 我是一名探鬼主播殿衰,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼家厌!你這毒婦竟也來(lái)了播玖?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤饭于,失蹤者是張志新(化名)和其女友劉穎蜀踏,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體掰吕,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡果覆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了殖熟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片局待。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖菱属,靈堂內(nèi)的尸體忽然破棺而出钳榨,到底是詐尸還是另有隱情,我是刑警寧澤纽门,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布薛耻,位于F島的核電站,受9級(jí)特大地震影響赏陵,放射性物質(zhì)發(fā)生泄漏饼齿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一蝙搔、第九天 我趴在偏房一處隱蔽的房頂上張望缕溉。 院中可真熱鬧,春花似錦吃型、人聲如沸证鸥。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)敌土。三九已至镜硕,卻和暖如春运翼,著一層夾襖步出監(jiān)牢的瞬間返干,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工血淌, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留矩欠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓悠夯,卻偏偏與公主長(zhǎng)得像癌淮,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子沦补,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容