最简单的基于FFmpeg的视频硬解码器-Android

FFmpeg源码分析:avcodec_find_decoder()查找解码器 FFmpeg提供两种方式查找解码器,通过codecId查找avcodec_find_decoder()与通过名字查找avcodec_find_decoder_by_name()。同样地,也提供两种方式查找编码器,通过codecId查找avcodec_find_encoder()与通过名字查找avcodec_find_encoder_by_name()。源码位于libavcodec/allcodecs.c中。 阅读详情

最近在做Android端的ffmpeg硬解码开发,用的ffmpeg4.2.1,发现网上相关资料比较少,大部分都是直接调用Android的MediaCodec进行硬解码的开发,用ffmpeg的比较少。在尝试使用ffmpeg自带的硬解码examples进行改造的过程中也发现有一些问题,一直阻塞着,前后花了两周时间,最终在公司大佬的指点下才调通了这一块。这里简单记录一下。

 

首先是解码函数,这里要注意硬解码使用的是 avcodec_find_decoder_by_name("h264_mediacodec")。解码最后的时候发了一个空包给解码器,空包的意思就是告诉解码器,数据发完了,可以结束了。

JNIEXPORT jint JNICALL Java_com_example_myapplication_NativeUtils_decode
        (JNIEnv *env, jobject obj, jstring input_jstr, jstring output_jstr) {
    AVFormatContext *pFormatCtx = NULL;
    int ret, videoIndex, err_code;
    AVCodecContext *pCodecCtx = NULL;
    AVCodec *pCodec = NULL;
    AVPacket *packet;
    FILE *fp_yuv;
    clock_t time_start, time_finish;
    double time_duration;
    AVStream *video = NULL;
    AVFrame *frame = NULL, *sw_frame = NULL;
    receive_cnt = 0;
    send_cnt = 0;

    char *input_str = (*env)->GetStringUTFChars(env, input_jstr, NULL);
    char *output_str = (*env)->GetStringUTFChars(env, output_jstr, NULL);
    LOGI("----input:%s----\n", input_str);
    LOGI("----output:%s----\n", output_str);

    av_log_set_callback(logcat_log);
    pFormatCtx = avformat_alloc_context();

    if (err_code = avformat_open_input(&pFormatCtx, input_str, NULL, NULL) != 0) {
        LOGE("Couldn't open input stream--- %s: %d\n---", input_str, err_code);
        return -1;
    }
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        LOGE("Couldn't find stream information.\n");
        return -1;
    }

    ret = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &pCodec, 0);
    if (ret < 0) {
        LOGE("Cannot find a video stream in the input file\n");
        return -1;
    }
    videoIndex = ret;

    //pCodec= avcodec_find_decoder(AV_CODEC_ID_H264);
    pCodec = avcodec_find_decoder_by_name("h264_mediacodec");

    if (!(pCodecCtx = avcodec_alloc_context3(pCodec)))
        return AVERROR(ENOMEM);

    video = pFormatCtx->streams[videoIndex];
    if (avcodec_parameters_to_context(pCodecCtx, video->codecpar) < 0)
        return -1;

    if ((ret = avcodec_open2(pCodecCtx, pCodec, NULL)) < 0) {
        LOGE("----Failed to open codec for stream :%d----\n", ret);
        return -1;
    }

    packet = (AVPacket *) av_malloc(sizeof(AVPacket));

    fp_yuv = fopen(output_str, "wb+");
    if (fp_yuv == NULL) {
        LOGE("----Cannot open output file----\n");
    }

    if (!(frame = av_frame_alloc()) || !(sw_frame = av_frame_alloc())) {
        LOGD("----frame init error----\n");
        return -1;
    }

    time_start = clock();
    int read_frame_ret = 0;
    int read_frame_eof = 0;

    while (1) {
        if (!read_frame_eof) {
            read_frame_ret = av_read_frame(pFormatCtx, packet);
            if (read_frame_ret == AVERROR_EOF) {
                read_frame_eof = 1;
                packet->data = NULL;
                packet->size = 0;
                packet->stream_index = videoIndex;
            } else if (read_frame_ret < 0) {
                av_packet_unref(packet);
                LOGE("----av_read_frame end by error, read_frame_ret:0x%x----\n", read_frame_ret);
                break;
            }

        }

        if (packet && (videoIndex == packet->stream_index)) {
            ret = decode_write(pCodecCtx, packet, fp_yuv, frame, sw_frame);
            if (ret == AVERROR_EOF) {
                av_packet_unref(packet);
                break;
            }
        }
        av_packet_unref(packet);
    }
    LOGD("----av_read_frame end, read_frame_ret:0x%x----\n", read_frame_ret);

    fclose(fp_yuv);
    time_finish = clock();
    time_duration = (double) (time_finish - time_start);
    LOGD("--------time:%fus\n", time_duration);
    av_frame_free(&frame);
    av_frame_free(&sw_frame);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
    av_buffer_unref(&hw_device_ctx);
    LOGD("----Decode End----\n");
    return 0;
}

 

以下是解码的核心代码,这里最主要的是要理解avcodec_send_packet方法和avcodec_receive_frame方法是异步的。此外AVERROR(EAGAIN)即-11代表过一会再尝试,解码过程中报这个是正常的。

int decode_write(AVCodecContext *avctx, AVPacket *packet, FILE *output_file, AVFrame *frame, AVFrame *sw_frame) {
    int size;
    int tempRet = 0;
    int sendRet = 0;
    int receiveRet = 0;
    int again = 0;
    int readEnd = 0;
    uint8_t *buffer = NULL;

    sendRet = avcodec_send_packet(avctx, packet);
    if (sendRet < 0) {
        LOGD("----avcodec_send_packet error, send_cnt:%d, ret:0x%x----\n", send_cnt, sendRet);
        if (sendRet == AVERROR(EAGAIN)) {
            again = 1;
        }
    } else {
        send_cnt++;
    }
    LOGD("----avcodec_send_packet successful, send_cnt:%d, ret:0x%x----\n", send_cnt, sendRet);

    while (1) {
        receiveRet = avcodec_receive_frame(avctx, frame);

        if ((receiveRet == AVERROR(EAGAIN)) || receiveRet == AVERROR_EOF) {

            if (buffer) {
                av_freep(&buffer);
            }
            LOGD("----avcodec_receive_frame error1, receive_cnt:%d, ret:0x%x----\n", receive_cnt, receiveRet);
            readEnd = 1;
            break;
        } else if (receiveRet < 0) {
            LOGD("----avcodec_receive_frame error2, receive_cnt:%d ret:%d, 0x%x----\n", receive_cnt, receiveRet);
            goto fail;
        }

        if (!readEnd) {
            AVFrame *tmp_frame;
            tmp_frame = frame;
            receive_cnt++;
            LOGD("----avcodec_receive_frame successful, receive_cnt:%d ret:0x%x----\n", receive_cnt, receiveRet);

            size = av_image_get_buffer_size(tmp_frame->format, tmp_frame->width,
                                            tmp_frame->height, 1);
            buffer = av_malloc(size);
            if (!buffer) {
                LOGE("Can not alloc buffer\n");
                tempRet = AVERROR(ENOMEM);
                goto fail;
            }
            tempRet = av_image_copy_to_buffer(buffer, size,
                                              (const uint8_t *const *) tmp_frame->data,
                                              (const int *) tmp_frame->linesize, tmp_frame->format,
                                              tmp_frame->width, tmp_frame->height, 1);
            if (tempRet < 0) {
                LOGE("Can not copy image to buffer\n");
                goto fail;
            }

            if ((tempRet = fwrite(buffer, 1, size, output_file)) < 0) {
                LOGE("----fwrite buffer error!----\n");
                goto fail;
            } else {
                LOGD("----fwrite buffer successful!----\n");
            }
        }

        fail:
        av_freep(&buffer);
        if (tempRet < 0)
            return tempRet;
    }

    if (again) {
        return decode_write(avctx, packet, output_file, frame, sw_frame);
    } else if (readEnd) {
        return receiveRet;
    }
}

 

最后不要忘记实现jni.h里的方法,这是一个固定的方法名,类似android的生命周期,加载jni的时候会自己调用 ,ffmpeg需要靠这个来调用Android的MediaCodec。

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
    LOGI("ffmpeg JNI_OnLoad");
    av_jni_set_java_vm(vm, reserved);
    return JNI_VERSION_1_6;
}

 

ffmpeg 之avcodec_find_decoder_by_name分析 avcodec_find_decoder_by_name(“h264_cuvid”); std::string name{}; if (id == AV_CODEC_ID_H264) name = ((m_gpu < 0) ? "h264" : "h264_cuvid"); else if (id == AV_CODEC_ID_HEVC) name = ((m_gpu < 0) ? "hevc" : "hevc_cuvid"); 从这里开始分析: 阅读详情

相关推荐

ffmpeg视频软编码流程说明

1 软编码流程 2 流程图说明 第一节的图中虚线框部分为视频格式封装部分,此处不说明。 编码主要步骤如下: 1)查找编码器 可以通过接口AVCodec *avcodec_find_encoder(enum AVCodecID id)设置, 比如,查找H264编码,设置id= AV_CODEC_ID_H264,即可找到H264编码器; 也可以通过AVCodec *avcodec_find_enco...

candylover的博客 823

FFmpeg 源代码:avcodec_find_encoder()和avcodec_find_encoder_by_name()

avcodec_find_encoder()用于通过codec id 查找FFmpeg的编码器 avcodec_find_encoder_by_name()用于通过codec name 查找FFmpeg的编码器 avcodec_find_encoder()和avcodec_find_encoder_by_name() 的声明位于libavcodec\codec.h,如下: /** * Find a registered encoder with a matching codec ID. .

幽雨雨幽 3166

FFmpeg之硬解码

导读 前面我们已经使用NDK编译出了FFmpeg并且已经集成到了Android Studio中去,相关文章:NDK21编译ffmpeg5.0.1 众所周知,软解码虽然兼容性一流,但是却非常依赖CPU,所以性能消耗笔记大;硬解码使用内置的DSP芯片进行解码,性能高,但是兼容性一般。 虽说硬解码兼容性不太好,但是在实际开发中出于对性能的考虑我们依然会采用能硬解则硬解,不能硬解则软解兜底的方案。 我们知道安卓上可以使用MediaCodec进行硬解码,新版本FFmpeg内部也支持了MediaCodec硬解码,今天我

u012944685的专栏 8895

ffmpeg源码笔记-查找编解码器(一)

avcodec_find_encoder_by_name和avcodec_find_encoder

qq_39048131的博客 3980

Android 利用 FFmpeg 解码音视频数据

在 avcodec_send_packet与avcodec_receive_frame应该是异步操作的,avcodec_send_packet 会将 AvPacket 放入到缓存队列中去解码,avcodec_receive_frame初次被调用时因为异步的原因可能没有获取到,也有可能可以获取多个 AvFrame,主要还是依赖解码的速度,因此通过循环去调用 avcodec_receive_frame 是比较妥当的做法。本文是基于之前写的几篇文章的,因此之前描述过的内容,在本文就不再重复了。

yinshipin007的博客 1183

一文读懂 Android FFmpeg 视频解码过程与实战分析

使用返回视频媒体流下一帧到AVPacket中。如果函数返回的 int 值是 0 则是Success,如果小于 0 则是Error或者EOF。因此如果在播放视频时返回的是小于 0 的值,调用函数重置解码器的状态,flush 缓冲区中的内容,然后再 seek 到当前传入的时间戳处,完成解码后的回调,再让同步锁进行等待。// 读取码流中的音频若干帧或者视频一帧,// 这里是读取视频一帧(完整的一帧),获取的是一帧视频的压缩数据,接下来才能对其进行解码// 防止解最后一帧时视频已经没有数据。

2401_84149836的博客 1489

最简单的基于FFmpeg的移动端例子:Android 视频解码器

本文记录一个安卓平台下基于FFmpeg的视频解码器。该视频解码器C语言的源代码来自于《100行代码实现最简单的基于FFMPEG+SDL的视频播放器》。相关的概念就不再重复记录了。源代码项目的目录结构如图所示。Java源代码位于src目录,而C代码位于jni目录。Android程序Java端代码位于src\com\leixiaohua1020\sffmpegandroiddecoder\MainAc

雷霄骅(leixiaohua1020)的专栏 5万+

Android平台FFmpeg 3.4.2视频硬解软解与Nativewindow渲染教程

FFmpeg是一个非常强大的开源多媒体框架,它提供了从视频和音频文件中读取、编码、解码、转码、混合、过滤、流、抓取和播放几乎所有已知格式的功能。在Android平台上,FFmpeg被广泛应用于视频处理和播放器开发中,为开发者提供了高度的灵活性和丰富的媒体处理能力。FFmpeg 3.4.2版本引入了多项改进和新功能,增强了库的性能和灵活性。新版本改进了音频和视频编解码器,增加了对新格式的支持,并优化了多线程处理能力。

weixin_33298352的博客 1350

最简单的基于FFmpeg的移动端例子:Android 推流器

本文记录一个安卓平台下基于FFmpeg的视频推流器。该推流器C语言的源代码来自于《最简单的基于FFMPEG的推流器》。相关的概念就不再重复记录了。源代码项目的目录结构如图所示。Java源代码位于src目录,而C代码位于jni目录。 Android程序Java端代码位于src\com\leixiaohua1020\sffmpegandroidstreamer\MainActivity.java,如下

雷霄骅(leixiaohua1020)的专栏 5万+

手把手教你用Pico4UE搭建100平米VR大空间:从设备检查、系统升级到地图绘制全流程

本文详细介绍了使用Pico4UE搭建100平米VR大空间的完整流程,包括设备检查、系统升级、地图绘制和性能优化等关键步骤。通过实战案例和专业技术建议,帮助用户高效部署企业级VR解决方案,适用于展厅漫游、工业仿真等多种场景。

weixin_28273593的博客 500

保姆级教程:在摩尔线程MTT S80上快速部署Ollama运行DeepSeek R1模型(附性能测试)

本文提供了一份详细的保姆级教程,指导用户在摩尔线程MTT S80显卡上部署Ollama并运行DeepSeek R1模型。内容涵盖从驱动安装、Docker环境配置到模型拉取、性能测试的全流程,并分享了实测数据与优化技巧,旨在帮助开发者快速在国产GPU平台上实现高效的大语言模型推理。

weixin_29091837的博客 518

最简单的基于FFmpeg的移动端例子:Android 视频解码器-单个库版

本文记录另一个安卓平台下基于FFmpeg的视频解码器。与前一篇文章记录的解码器不同,本文记录的解码器不再使用libavcodec.so、libavformat.so等类库,而只使用了一个类库——libffmpeg.so。该视频解码器C语言的源代码来自于《最简单的基于FFMPEG+SDL的视频播放器》。相关的概念就不再重复记录了。FFmpeg类库的打包记录一下FFmpeg类库打包的方

雷霄骅(leixiaohua1020)的专栏 2万+

Chromium实用技巧篇-如何下载指定版本的Chromium(一)

通过本指南,您已成功掌握如何在Windows系统上下载并安装最新版本的Chromium浏览器。我们详细介绍了从进入Chromium官网,到查找最新版本号,再到下载和安装的每一个步骤。通过这些步骤,您不仅能够体验到最新的浏览器功能,还可以参与开源项目的开发,享受高速、稳定、安全的浏览体验。

守城小轩的技术窝棚 1万+

最简单的基于FFmpeg的移动端例子:Android 视频转码器

本文记录一个安卓平台下基于FFmpeg的视频转码器。该转码器实际上移植自ffmpeg工程中的ffmpeg.c源代码。有关ffmpeg.c的源代码可以参考文章《ffmpeg.c函数结构简单分析(画图)》,在这里就不重复记录了。源代码项目的目录结构如图所示。Java源代码位于src目录,而C代码位于jni目录。Android程序Java端代码位于src\com\leixiaohua1020\sffmp

雷霄骅(leixiaohua1020)的专栏 8万+

PINN——加入物理约束的神经网络

【摘要】 基于物理信息的神经网络(Physics-informed Neural Network, 简称PINN),是一类用于解决有监督学习任务的神经网络,它不仅能够像传统神经网络一样学习到训练数据样本的分布规律,而且能够学习到数学方程描述的物理定律。与纯数据驱动的神经网络学习相比,PINN在训练过程中施加了物理信息约束,因而能用更少的数据样本学习到更具泛化能力的模型。本文主要解析这种神经网络以...

qq_39444290的博客 4582

别再为倍福EL6021/EL6022的RS485接线头疼了!手把手教你硬件跳线与软件配置(附Modbus RTU主站例程)

本文详细解析了倍福EL6021/EL6022模块的RS485硬件跳线与软件配置方法,包括Modbus RTU主站编程实战。从端子定义、跳线操作到TwinCAT 3环境设置,提供完整的通信例程和故障排查技巧,帮助工程师快速解决工业现场通信问题。

weixin_30634661的博客 589

最简单的基于FFmpeg的移动端例子:Android HelloWorld

从本文开始打算记录一系列FFmpeg在Android/IOS开发的示例程序。前面几篇文章记录FFmpeg安卓端开发的例子,后面几篇文章记录FFmpeg IOS端开发的例子。这些例子中FFmpeg相关的代码源自于《FFmpeg示例合集》中的程序。本文记录第一个程序:安卓平台下基于FFmpeg的HelloWorld程序。Android程序FFmpeg类库使用说明Android应用程序使用FFmpeg类

雷霄骅(leixiaohua1020)的专栏 11万+

抓包网络调试工具 reqable抓包教程

如果CA根证书未安装成功,网络图标是黄色,点击盾牌图标可以进入CA根证书安装界面,详细步骤请阅读。在API调试中,获取流量是第一步也是最基本的操作,这里讲解下如何使用Reqable获取流量,通俗地讲就是抓包。我们可以选择一个图片请求,双击打开详情面板,能够看到该请求的详细信息。如果系统代理未设置,网络图标是黄色,点击网络图标可以自动设置系统代理。此域名的结构树,可以查看该域名下所有请求的目录结构,点击选择可以查看该请求的详情。首先,需要启用Reqable的调试功能,点击最右上角的。打开浏览器,输入地址。

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 2552

最简单的基于FFmpeg的移动端例子 Android 视频解码器

最简单的基于FFmpeg的移动端例子 Android 视频解码器

hffgjh的博客 507

华为od面试手撕真题【分解质因数】

华为od面试手撕真题【分解质因数】

MISAYAONE的博客 1127

18、AI在基础设施管理与运维中的应用及未来趋势

本文探讨了人工智能(AI)在基础设施管理和运维中的广泛应用,包括性能优化、成本节约、网络安全等多个场景。文章详细介绍了AI在不同领域的应用工具、实际案例以及面临的挑战,并展望了未来AI在基础设施管理中的发展趋势。此外,还分析了AI与Site Reliability Engineering(SRE)框架的结合优势,展示了AI如何提升运维效率和系统可靠性。

n7o8p的博客 147

【FFmpeg之如何新增一个硬件解码器】

最近在鸿蒙上开发音视频相关功能,在适配好SDL2之后,接入FFmpeg软解即可播放音视频,然对于4k大码率的视频,播放时却非常卡顿。于是乎琢磨着在鸿蒙上加一个FFmpeg的硬解码,PS:鸿蒙官方目前只提供解码相关SDK。不过应该如何入手呢?想到这个鸿蒙跟安卓还是有点类似,解码都是异步回调机制,于是先捋一遍安卓下硬解码流程吧。步骤关键操作1. 配置编译修改 configure 和 Makefile,添加新解码器选项2. 定义结构体注册 AVCodec,实现编解码器上下文3. 初始化与配置。

did_you的博客 2063

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

Android FFmpeg开发(二),实现视频解码和渲染

上一篇文章我们对FFmpeg进行交叉编译,得到了编译产物:libffmpeg.so,然后创建了一个Android项目,对libffmpeg.so库实现了集成,还没阅读上一篇文章建议先阅读:Android FFmpeg开发(一),FFmpeg编译与集成 本文将利用FFmpeg对本地MP4视频文件进行视频流解码,然后使用FFmpeg中的libswscale模块将原始视频YUV帧转成RGB帧,最后将RGB帧数据刷到SurfaceView的surface中实现视频画面的渲染。 接下来,介绍下利用FFmpeg完成

Lorien Zhang的博客 6842

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

最简单的基于FFmpeg的移动端例子 Android 视频解码器-单个库版

最简单的基于FFmpeg的移动端例子 Android 视频解码器-单个库版

hffgjh的博客 301

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

上一篇: 调用DialogFragment做进度条报java.lang.IllegalStateException: Fragment already added问题的最终解决方案
h7key
博客等级 码龄16年 1粉丝 2原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值