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
從圖中可以看到香璃,不僅僅是 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ō)明編譯成功了酒唉。
支持的格式里面已經(jīng)有了 h264_mediacodec 和 mpeg4_mediacodec 了。
接下來(lái)就進(jìn)行解碼了沸移。關(guān)于 FFmpeg 解碼的 API 調(diào)用痪伦,在公眾號(hào)以前發(fā)布的文章中說(shuō)過(guò)多次侄榴,就不詳細(xì)講解流程了,簡(jiǎn)單概況一下:
- 首先通過(guò) avformat_open_input 方法打開(kāi)文件网沾,得到 AVFormatContext 牲蜀。
- 然后通過(guò) avformat_find_stream_info 查找文件的視頻流信息。
- 得到文件相關(guān)信息和視頻流信息绅这,主要還是為了得到編碼格式信息涣达,然后好找到對(duì)應(yīng)的解碼器。也可以通過(guò) avcodec_find_decoder_by_name 方法直接找具體的解碼器证薇。
- 有了解碼器就可以創(chuàng)建解碼上下文 AVCodecContext度苔,并通過(guò) avcodec_open2 方法打開(kāi)解碼器
- 然后通過(guò) av_read_frame 讀取文件的內(nèi)容好進(jìn)行下一步的解碼。
- 接下來(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 獲取下載地址醒颖。