前面我们介绍了如何使用FFmpeg6.0调用MediaCodec进行视频硬解码得到YUV数据,那么对于熟悉Android开发的同学就会问了,在java中使用MediaCodec是可以直接硬解码到渲染到SurfaceView上的,此时如果通过FFmpeg6.0调用MediaCodec进行硬解码的话,是否也能直接解码渲染到SurfaceView上呢?答案是肯定的,更加准确地来说是解码到Surface上,今天我们就来实现这样的一个小需求。
至于FFmpeg如何可以直接解码到surfaceView上,可以参考一下这个回复:
http://mplayerhq.hu/pipermail/ffmpeg-devel/2016-March/191700.html
总的来说,使用FFMpeg直接硬解码到Surface上的步骤是在前一篇文章《FFmpeg6.0调用MediaCodec解码》 的基础上增加部分代码即可,正所谓一理通百理用就是这个道理,冰冻三尺,非一日之寒, 很多看似庞大的工程背后其实也就是日积月累,一沙一石的堆砌,学习如此,日常工作中所开发的项目更是如此。
下面我们就说说在《FFmpeg6.0调用MediaCodec解码》的基础上如何实现FFmpeg硬解码到Surface上。
- 将Java层Surface对象传递到NDK层,并与解码器上下文挂钩
这个步骤只需要两句代码即可,够简单吧?
// 与Surface挂钩
AVMediaCodecContext *mediaCodecContext = av_mediacodec_alloc_context();
// decoder_ctx就是解码器上下文
av_mediacodec_default_init(decoder_ctx, mediaCodecContext, surface);
- 给FFmpeg设置JavaVM
在上一篇文章中我们可是说过FFmpeg6.0之后是通过NDK直接调用MediaCodec进行硬编解码,不是再通过JNI调用Java的MediaCodec了,怎么这里又要给FFmpeg设置JavaVM了呢?
啪啪啪打脸来的真快…
确实如果不给FFmpeg设置JavaVM也能进行硬解码,但是解码得到的数据并不能直接渲染到Surface上,因为一般介么得到的AVFrame的数据格式一般是NV12的(也就是AV_PIX_FMT_NV12), 而如果要能直接渲染到Surface上的数据格式就要求是mediaCodec的(也就是AV_PIX_FMT_MEDIACODEC)。因此想要直接解码到Surface上的话还是需要给ffmpeg设置JavaVM的哦。
这一步骤的代码就是在函数JNI_OnLoad
设置一下即可:
JNIEXPORT
jint JNI_OnLoad(JavaVM *vm, void *res) {
av_jni_set_java_vm(vm, 0);
return JNI_VERSION_1_4;
}
增加完这两个步骤的代码之后,我们就可以直接将视频数据解码到Surface上啦,这时候是用SurfaceView还是用TextureView那就随便你啦…
完整代码如下:
void FFDecoder::decodeVideoToSurface(const char *videoPath, jobject surface) {
AVFormatContext *input_ctx = nullptr;
int video_stream, ret;
AVStream *video = nullptr;
AVCodecContext *decoder_ctx = nullptr;
const AVCodec *decoder = nullptr;
AVPacket *avPacket = {nullptr};
AVFrame *avFrame = {nullptr};
enum AVHWDeviceType type;
int i;
do {
// 查询下mediacodec
const char *mediacodec = "mediacodec";
type = av_hwdevice_find_type_by_name(mediacodec);
if (type == AV_HWDEVICE_TYPE_NONE) {
LOGD_E("FFDecoder","Device type %s is not supported.\n", mediacodec);
while ((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE)
LOGD_E("FFDecoder"," %s", av_hwdevice_get_type_name(type));
break;
}
// 打开文件流
if (avformat_open_input(&input_ctx, videoPath, nullptr, nullptr) != 0) {
LOGD_E("FFDecoder","Cannot open input file '%s'\n", videoPath);
break;
}
if (avformat_find_stream_info(input_ctx, nullptr) < 0) {
LOGD_E("FFDecoder","Cannot find input stream information.\n");
break;
}
// 查找视频流
ret = av_find_best_stream(input_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
if (ret < 0) {
LOGD_E("FFDecoder","Cannot find a video stream in the input file\n");
break;
}
video_stream = ret;
// 这里以h264为例
if (!(decoder = avcodec_find_decoder_by_name("h264_mediacodec"))) {
LOGD_E("FFDecoder","avcodec_find_decoder_by_name failed.\n");
break;
}
// 配置硬解码
for (i = 0;; i++) {
const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
if (!config) {
LOGD_E("FFDecoder","Decoder %s does not support device type %s.\n",
decoder->name, av_hwdevice_get_type_name(type));
break;
}
if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
config->device_type == type) {
hw_pix_fmt = config->pix_fmt;
break;
}
}
if (!(decoder_ctx = avcodec_alloc_context3(decoder))){
LOGD_E("FFDecoder","avcodec_alloc_context3 failed\n");
break;
}
video = input_ctx->streams[video_stream];
if (avcodec_parameters_to_context(decoder_ctx, video->codecpar) < 0){
LOGD_E("FFDecoder","avcodec_parameters_to_context failed\n");
break;
}
// 硬件解码器初始化
decoder_ctx->get_format = get_hw_format;
AVBufferRef *hw_device_ctx = nullptr;
ret = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_MEDIACODEC,
nullptr, nullptr, 0);
decoder_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
if (ret < 0) {
LOGD_E("FFDecoder","Failed to create specified HW device");
break;
}
// 与Surface挂钩
AVMediaCodecContext *mediaCodecContext = av_mediacodec_alloc_context();
av_mediacodec_default_init(decoder_ctx, mediaCodecContext, surface);
if ((ret = avcodec_open2(decoder_ctx, decoder, NULL)) < 0) {
LOGD_E("FFDecoder","Failed to open codec for stream #%u\n", video_stream);
break;
}
// 循环读取解码
avPacket = av_packet_alloc();
avFrame = av_frame_alloc();
while (ret >= 0 || ret == -11) {
ret = av_read_frame(input_ctx, avPacket);
if(ret != 0){
// 读完了,空包冲刷解码器
ret = avcodec_send_packet(decoder_ctx, nullptr);
av_packet_unref(avPacket);
} else{
if(avPacket->stream_index == video_stream){
ret = avcodec_send_packet(decoder_ctx, avPacket);
av_packet_unref(avPacket);
} else{
// 非视频数据
av_packet_unref(avPacket);
continue;
}
}
// 获取解码后的数据
while (ret == 0){
LOGD_D("FFDecoder","获取解码数据成功:%s",av_get_pix_fmt_name(static_cast<AVPixelFormat>(avFrame->format)));
LOGD_D("FFDecoder","hw_pix_fmt:%s",av_get_pix_fmt_name(static_cast<AVPixelFormat>(hw_pix_fmt)));
LOGD_D("FFDecoder","linesize0:%d,linesize1:%d,linesize2:%d",avFrame->linesize[0],avFrame->linesize[1],avFrame->linesize[2]);
LOGD_D("FFDecoder","width:%d,height:%d",avFrame->width,avFrame->height);
ret = avcodec_receive_frame(decoder_ctx,avFrame);
LOGD_D("FFDecoder","avcodec_receive_frame ret:%d",ret);
if(ret == 0){
// 可以使用av_hwframe_transfer_data将数据从GPU拷贝到cpu
if (avFrame->format == hw_pix_fmt) {
AVMediaCodecBuffer *buffer = (AVMediaCodecBuffer *) avFrame->data[3];
// 渲染
av_mediacodec_release_buffer(buffer, 1);
} else{
LOGD_E("FFDecoder","avFrame->format error");
// av_hwframe_transfer_data(mediacodecFrame, avFrame, 0)???
}
}
}
}
} while (0);
// 释放资源
if(nullptr != input_ctx){
avformat_free_context(input_ctx);
input_ctx = nullptr;
}
if(nullptr != decoder_ctx){
avcodec_free_context(&decoder_ctx);
decoder_ctx = nullptr;
}
if(nullptr != avPacket){
av_packet_free(&avPacket);
avPacket = nullptr;
}
if(nullptr != avFrame){
av_frame_free(&avFrame);
avFrame = nullptr;
}
}
切记切记,不要忘了设置av_jni_set_java_vm(vm, 0)
;如果没有设置的话,解码到的数据一般就是NV12,至于NV12数据怎么渲染呢? 那就看之前Opengl ES的系列文章啦!!!
关注我,一起进步,有全量音视频开发进阶路径、资料、踩坑记等你来学习…
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/31968.html