FFmpeg(9)-解码器解码代码演示(FFmpeg调用MediaCodec实现硬解码、多线程解码、及音视频解码性能测试)
一.AVFrame
用来存放解码后的数据。
【相关函数】
AVFrame *frame = av_frame_alloc(); // 空间分配,分配一个空间并初始化。
void av_frame_free(AVFrame **frame); // 空间释放。两种释放方式,一种是将引用计数-1,
int av_frame_ref(AVFrame *dst, const AVFrame *src); // 引用计数增加1。比如要在多线程访问的时候复制到另外一边,就可以利用引用计数的变化。
AVFrame *av_frame_clone(const AVFrame *src); // 复制。也是重新创建一个空间,然后引用计数加+1。与AVPacket的区别在于:AVFrame的复制开销更大。1920*1080p的视频,一帧可能就有几MB,一秒钟可能就有几百MB,所以做一帧画面的内存复制可能都耗费到毫秒级别,不像AVPacket可能只有微秒级别,会影响帧率。所以在它的空间复制上一定要慎重,所以我们一般用引用计数的方式来做。
void av_frame_unref(AVFrame *frame); // 直接把对象的引用计数-1.
【结构体包含的内容】
uint8_t *data[AV_NUM_DATA,POINTERS] // 存放的数据。
int linesize[AV_NUM_DATA,POINTERS] // 对于视频就是一行数据的大小。对于音频就是一个通道数据的大小。
int width, int height, int nb_samples // 视频部分, 音频相关(单通道的样本数量)
int64_ t pts // 实际这一帧的pts。
int64_t pkt_dts // 对应包当中的dts。
int sample_rate; // 样本率
uint64_t channel_layout; // 通道类型
int channels; // 通道数量
int format; // 像素格式。区分音频和视频。视频的话就是AVPixelFormat,音频的话就是AVSampleFormat
二.解码器解码代码演示
// 初始化解封装 av_register_all(); // 注册解码器 avcodec_register_all(); // 初始化网络 avformat_network_init(); // 打开文件 AVFormatContext *ic = NULL; char path[] = "sdcard/1080.mp4"; // char path[] = "/sdcard/qingfeng.flv"; int ret = avformat_open_input(&ic, path, 0, 0); if (ret != 0) { LOGE("avformat_open_input() called failed: %s", av_err2str(ret)); return env->NewStringUTF(hello.c_str()); } LOGI("avformat_open_input(): File open success."); LOGI("File duration is: %lld, nb_stream is: %d", ic->duration, ic->nb_streams); if (avformat_find_stream_info(ic, 0) >=0 ) { LOGI("File duration is: %lld, nb_stream is: %d", ic->duration, ic->nb_streams); } /**帧率*/ int fps = 0; /*视频流索引*/ int videoStream = 0; /*音频流索引*/ int audioStream = 1; // 遍历获得音/视频流索引 for (int i = 0; i < ic->nb_streams; i++) { AVStream *as = ic->streams[i]; if (as->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { LOGI("视频数据"); videoStream = i; fps = (int)r2d(as->avg_frame_rate); LOGI("fps = %d, width = %d, height = %d, codecid = %d, format = %d", fps, as->codecpar->width, as->codecpar->height, as->codecpar->codec_id, as->codecpar->format); } else if (as->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { LOGI("音频数据"); audioStream = i; LOGI("sample_rate = %d, channels = %d, sample_format = %d", as->codecpar->sample_rate, as->codecpar->channels, as->codecpar->format ); } } // 也可以利用av_find_best_stream()函数来查找音视频流索引 // audioStream = av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); // LOGI("av_find_best_stream, audio index is: %d", audioStream); // 查找视频解码器 AVCodec *vCodec = avcodec_find_decoder(ic->streams[videoStream]->codecpar->codec_id); // 软解 // vCodec = avcodec_find_decoder_by_name("h264_mediacodec"); // 硬解 if (!vCodec) { LOGE("avcodec_find_decoder() failed. can not found video decoder."); return env->NewStringUTF(hello.c_str()); } // 配置解码器上下文 AVCodecContext *vc = avcodec_alloc_context3(vCodec); // 将AVStream里面的参数复制到上下文当中 avcodec_parameters_to_context(vc, ic->streams[videoStream]->codecpar); vc->thread_count = 8; // 打开解码器 ret = avcodec_open2(vc, vCodec, 0); if (ret != 0) { LOGE("avcodec_open2() failed. can not open video decoder, line is: %d", __LINE__); return env->NewStringUTF(hello.c_str()); } // 查找音频解码器 AVCodec *aCodec = avcodec_find_decoder(ic->streams[audioStream]->codecpar->codec_id); // 软解 // aCodec= avcodec_find_decoder_by_name("h264_mediacodec"); // 硬解 if (!aCodec) { LOGE("avcodec_find_decoder() failed. can not found audio decoder."); return env->NewStringUTF(hello.c_str()); } // 配置解码器上下文 AVCodecContext *ac = avcodec_alloc_context3(aCodec); // 将AVStream里面的参数复制到上下文当中 avcodec_parameters_to_context(ac, ic->streams[audioStream]->codecpar); ac->thread_count = 8; // 打开解码器 ret = avcodec_open2(ac, aCodec, 0); if (ret != 0) { LOGE("avcodec_open2() failed. can not open audio decoder"); return env->NewStringUTF(hello.c_str()); } // 读取帧数据 AVPacket *packet = av_packet_alloc(); AVFrame *frame = av_frame_alloc(); int64_t start = getNowMs(); int frameCount = 0; for (;;) { if (getNowMs() - start >= 3000) { LOGI("now decoder fps is: %d", frameCount / 3); start = getNowMs(); frameCount = 0; } int ret = av_read_frame(ic, packet); if (ret != 0) { LOGE("读取到结尾处"); int pos = 20 * r2d(ic->streams[videoStream]->time_base); // 改变播放进度 av_seek_frame(ic, videoStream, pos, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME); continue; } // LOGI("Read a Packet. streamIndex=%d, size=%d, pts=%lld, flag=%d", // packet->stream_index, // packet->size, // packet->pts, // packet->flags // ); AVCodecContext *cc = vc; if (packet->stream_index == audioStream) cc = ac; // 发送到线程中去解码(将packet写入到解码队列当中去) ret = avcodec_send_packet(cc, packet); // 清理 int p = packet->pts; av_packet_unref(packet); if (ret != 0) { // LOGE("avcodec_send_packet failed."); continue; } for(;;) { // 从已解码成功的数据当中取出一个frame, 要注意send一次,receive不一定是一次 ret = avcodec_receive_frame(cc, frame); if (ret != 0) { break; } if (cc == vc) { frameCount++; } // LOGI("Receive a frame........."); } } // 关闭上下文 avformat_close_input(&ic); return env->NewStringUTF(hello.c_str());
另外:硬件解码器需要进行注册,即需要把Java虚拟机的环境传递给FFmpeg,因此还需要添加下列代码,否则解码器无法打开。
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm,void *res) { av_jni_set_java_vm(vm,0); return JNI_VERSION_1_4; }
av_jni_set_java_vm(vm,0); 包含在 <libavcodec/jni.h> 头文件当中,不要忘记包含该头文件。
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
2016-10-18 新浪微博客户端(7)-通过转换坐标系来调整首页下拉菜单的位置
2016-10-18 新浪微博客户端(6)-创建首页下拉菜单
2016-10-18 eclipse-统计代码行数