最简单的基于FFmpeg的解码器-纯净版(不包含libavformat)

2024-02-20 14:32

本文主要是介绍最简单的基于FFmpeg的解码器-纯净版(不包含libavformat),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

这个播放器调用了FFmpeg中的libavformatlibavcodec两个库完成了视频解码工作。但是这不是一个“纯净”的解码器。

该解码器中libavformat完成封装格式的解析,而libavcodec完成解码工作。

一个“纯净”的解码器,理论上说只需要使用libavcodec就足够了,并不需要使用libavformat。本文记录的解码器就是这样的一个“纯净”的解码器,它仅仅通过调用libavcodecH.264/HEVC等格式的压缩视频码流解码成为YUV数据。

流程图

本文记录的纯净版本的基于FFmpeg的解码器的函数调用流程图如下图所示。需要注意的是,此解码器的输入必须是只包含视频编码数据“裸流”(例如H.264HEVC码流文件),而不能是包含封装格式的媒体数据(例如AVIMKVMP4)。
在这里插入图片描述
流程图中关键函数的作用如下所列:
avcodec_register_all():注册所有的编解码器。
avcodec_find_decoder():查找解码器。
avcodec_alloc_context3():为AVCodecContext分配内存。
avcodec_open2():打开解码器。
avcodec_decode_video2():解码一帧数据。

有两个平时“不太常见”的函数:
av_parser_init():初始化AVCodecParserContext
av_parser_parse2():解析获得一个Packet

两个存储数据的结构体如下所列:
AVFrame:存储一帧解码后的像素数据
AVPacket:存储一帧(一般情况下)压缩编码数据

AVCodecParser

AVCodecParser用于解析输入的数据流并把它分成一帧一帧的压缩编码数据。
比较形象的说法就是把长长的一段连续的数据“切割”成一段段的数据。他的核心函数是av_parser_parse2()。它的定义如下所示。

/*** Parse a packet.** @param s             parser context.* @param avctx         codec context.* @param poutbuf       set to pointer to parsed buffer or NULL if not yet finished.* @param poutbuf_size  set to size of parsed buffer or zero if not yet finished.* @param buf           input buffer.* @param buf_size      input length, to signal EOF, this should be 0 (so that the last frame can be output).* @param pts           input presentation timestamp.* @param dts           input decoding timestamp.* @param pos           input byte position in stream.* @return the number of bytes of the input bitstream used.** Example:* @code*   while(in_len){*       len = av_parser_parse2(myparser, AVCodecContext, &data, &size,*                                        in_data, in_len,*                                        pts, dts, pos);*       in_data += len;*       in_len  -= len;**       if(size)*          decode_frame(data, size);*   }* @endcode*/
int av_parser_parse2(AVCodecParserContext *s,AVCodecContext *avctx,uint8_t **poutbuf, int *poutbuf_size,const uint8_t *buf, int buf_size,int64_t pts, int64_t dts,int64_t pos);

其中poutbuf指向解析后输出的压缩编码数据帧buf指向输入的压缩编码数据
如果函数执行完后输出数据为空(poutbuf_size0),则代表解析还没有完成,还需要再次调用av_parser_parse2()解析一部分数据才可以得到解析后的数据帧。
当函数执行完后输出数据不为空的时候,代表解析完成,可以将poutbuf中的这帧数据取出来做后续处理。

对比

简单记录一下这个只使用libavcodec的“纯净版”视频解码器和使用libavcodec+libavformat的视频解码器的不同。

(1) 下列与libavformat相关的函数在“纯净版”视频解码器中都不存在。

av_register_all():注册所有的编解码器,复用/解复用器等等组件。其中调用了avcodec_register_all()注册所有编解码器相关的组件

avformat_alloc_context()创建AVFormatContext结构体

avformat_open_input()打开一个输入流(文件或者网络地址)。其中会调用avformat_new_stream()创建AVStream结构体

avformat_new_stream()中会调用avcodec_alloc_context3()创建AVCodecContext结构体

avformat_find_stream_info()获取媒体的信息

av_read_frame()获取媒体的一帧压缩编码数据。其中调用了av_parser_parse2()

(2) 新增了如下几个函数。

avcodec_register_all()只注册编解码器有关的组件。比如说编码器、解码器、比特流滤镜等,但是不注册复用/解复用器这些和编解码器无关的组件。
avcodec_alloc_context3()创建AVCodecContext结构体
av_parser_init()初始化AVCodecParserContext结构体
av_parser_parse2():使用AVCodecParser从输入的数据流中分离出一帧一帧的压缩编码数据

(3) 程序的流程发生了变化。

在“libavcodec+libavformat”的视频解码器中,使用avformat_open_input()avformat_find_stream_info()就可以解析出输入视频的信息(例如视频的宽、高)并且赋值给相关的结构体。
因此我们在初始化的时候就可以通过读取相应的字段获取到这些信息。

在“纯净”的解码器则不能这样,由于没有上述的函数,所以不能在初始化的时候获得视频的参数。“纯净”的解码器中,可以通过avcodec_decode_video2()获得这些信息。因此我们只有在成功解码第一帧之后,才能通过读取相应的字段获取到这些信息。

源代码

/*** 最简单的基于FFmpeg的视频解码器(纯净版)* Simplest FFmpeg Decoder Pure** 雷霄骅 Lei Xiaohua* leixiaohua1020@126.com* 中国传媒大学/数字电视技术* Communication University of China / Digital TV Technology* http://blog.csdn.net/leixiaohua1020*** 本程序实现了视频码流(支持HEVC,H.264,MPEG2等)解码为YUV数据。* 它仅仅使用了libavcodec(而没有使用libavformat)。* 是最简单的FFmpeg视频解码方面的教程。* 通过学习本例子可以了解FFmpeg的解码流程。* This software is a simplest decoder based on FFmpeg.* It decode bitstreams to YUV pixel data.* It just use libavcodec (do not contains libavformat).* Suitable for beginner of FFmpeg.*/#include <stdio.h>#define __STDC_CONSTANT_MACROS#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#ifdef __cplusplus
};
#endif
#endif//test different codec
#define TEST_H264  1
#define TEST_HEVC  0int main(int argc, char* argv[])
{AVCodec *pCodec;AVCodecContext *pCodecCtx= NULL;AVCodecParserContext *pCodecParserCtx=NULL;FILE *fp_in;FILE *fp_out;AVFrame	*pFrame;const int in_buffer_size=4096;uint8_t in_buffer[in_buffer_size + FF_INPUT_BUFFER_PADDING_SIZE]={0};uint8_t *cur_ptr;int cur_size;AVPacket packet;int ret, got_picture;int y_size;#if TEST_HEVCenum AVCodecID codec_id=AV_CODEC_ID_HEVC;char filepath_in[]="bigbuckbunny_480x272.hevc";
#elif TEST_H264AVCodecID codec_id=AV_CODEC_ID_H264;char filepath_in[]="bigbuckbunny_480x272.h264";
#elseAVCodecID codec_id=AV_CODEC_ID_MPEG2VIDEO;char filepath_in[]="bigbuckbunny_480x272.m2v";
#endifchar filepath_out[]="bigbuckbunny_480x272.yuv";int first_time=1;//av_log_set_level(AV_LOG_DEBUG);avcodec_register_all();pCodec = avcodec_find_decoder(codec_id);if (!pCodec) {printf("Codec not found\n");return -1;}pCodecCtx = avcodec_alloc_context3(pCodec);if (!pCodecCtx){printf("Could not allocate video codec context\n");return -1;}pCodecParserCtx=av_parser_init(codec_id);if (!pCodecParserCtx){printf("Could not allocate video parser context\n");return -1;}//if(pCodec->capabilities&CODEC_CAP_TRUNCATED)//    pCodecCtx->flags|= CODEC_FLAG_TRUNCATED; if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {printf("Could not open codec\n");return -1;}//Input Filefp_in = fopen(filepath_in, "rb");if (!fp_in) {printf("Could not open input stream\n");return -1;}//Output Filefp_out = fopen(filepath_out, "wb");if (!fp_out) {printf("Could not open output YUV file\n");return -1;}pFrame = av_frame_alloc();av_init_packet(&packet);while (1) {cur_size = fread(in_buffer, 1, in_buffer_size, fp_in);if (cur_size == 0)break;cur_ptr=in_buffer;while (cur_size>0){int len = av_parser_parse2(pCodecParserCtx, pCodecCtx,&packet.data, &packet.size,cur_ptr , cur_size ,AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);cur_ptr += len;cur_size -= len;if(packet.size==0)continue;//Some Info from AVCodecParserContextprintf("[Packet]Size:%6d\t",packet.size);switch(pCodecParserCtx->pict_type){case AV_PICTURE_TYPE_I: printf("Type:I\t");break;case AV_PICTURE_TYPE_P: printf("Type:P\t");break;case AV_PICTURE_TYPE_B: printf("Type:B\t");break;default: printf("Type:Other\t");break;}printf("Number:%4d\n",pCodecParserCtx->output_picture_number);ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);if (ret < 0) {printf("Decode Error.\n");return ret;}if (got_picture) {if(first_time){printf("\nCodec Full Name:%s\n",pCodecCtx->codec->long_name);printf("width:%d\nheight:%d\n\n",pCodecCtx->width,pCodecCtx->height);first_time=0;}//Y, U, Vfor(int i=0;i<pFrame->height;i++){fwrite(pFrame->data[0]+pFrame->linesize[0]*i,1,pFrame->width,fp_out);}for(int i=0;i<pFrame->height/2;i++){fwrite(pFrame->data[1]+pFrame->linesize[1]*i,1,pFrame->width/2,fp_out);}for(int i=0;i<pFrame->height/2;i++){fwrite(pFrame->data[2]+pFrame->linesize[2]*i,1,pFrame->width/2,fp_out);}printf("Succeed to decode 1 frame!\n");}}}//Flush Decoderpacket.data = NULL;packet.size = 0;while(1){ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);if (ret < 0) {printf("Decode Error.\n");return ret;}if (!got_picture){break;}else {//Y, U, Vfor(int i=0;i<pFrame->height;i++){fwrite(pFrame->data[0]+pFrame->linesize[0]*i,1,pFrame->width,fp_out);}for(int i=0;i<pFrame->height/2;i++){fwrite(pFrame->data[1]+pFrame->linesize[1]*i,1,pFrame->width/2,fp_out);}for(int i=0;i<pFrame->height/2;i++){fwrite(pFrame->data[2]+pFrame->linesize[2]*i,1,pFrame->width/2,fp_out);}printf("Flush Decoder: Succeed to decode 1 frame!\n");}}fclose(fp_in);fclose(fp_out);av_parser_close(pCodecParserCtx);av_frame_free(&pFrame);avcodec_close(pCodecCtx);av_free(pCodecCtx);return 0;
}

这篇关于最简单的基于FFmpeg的解码器-纯净版(不包含libavformat)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/728534

相关文章

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

redis群集简单部署过程

《redis群集简单部署过程》文章介绍了Redis,一个高性能的键值存储系统,其支持多种数据结构和命令,它还讨论了Redis的服务器端架构、数据存储和获取、协议和命令、高可用性方案、缓存机制以及监控和... 目录Redis介绍1. 基本概念2. 服务器端3. 存储和获取数据4. 协议和命令5. 高可用性6.

JAVA调用Deepseek的api完成基本对话简单代码示例

《JAVA调用Deepseek的api完成基本对话简单代码示例》:本文主要介绍JAVA调用Deepseek的api完成基本对话的相关资料,文中详细讲解了如何获取DeepSeekAPI密钥、添加H... 获取API密钥首先,从DeepSeek平台获取API密钥,用于身份验证。添加HTTP客户端依赖使用Jav

利用Python编写一个简单的聊天机器人

《利用Python编写一个简单的聊天机器人》这篇文章主要为大家详细介绍了如何利用Python编写一个简单的聊天机器人,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 使用 python 编写一个简单的聊天机器人可以从最基础的逻辑开始,然后逐步加入更复杂的功能。这里我们将先实现一个简单的

使用IntelliJ IDEA创建简单的Java Web项目完整步骤

《使用IntelliJIDEA创建简单的JavaWeb项目完整步骤》:本文主要介绍如何使用IntelliJIDEA创建一个简单的JavaWeb项目,实现登录、注册和查看用户列表功能,使用Se... 目录前置准备项目功能实现步骤1. 创建项目2. 配置 Tomcat3. 项目文件结构4. 创建数据库和表5.

使用PyQt5编写一个简单的取色器

《使用PyQt5编写一个简单的取色器》:本文主要介绍PyQt5搭建的一个取色器,一共写了两款应用,一款使用快捷键捕获鼠标附近图像的RGB和16进制颜色编码,一款跟随鼠标刷新图像的RGB和16... 目录取色器1取色器2PyQt5搭建的一个取色器,一共写了两款应用,一款使用快捷键捕获鼠标附近图像的RGB和16

四种简单方法 轻松进入电脑主板 BIOS 或 UEFI 固件设置

《四种简单方法轻松进入电脑主板BIOS或UEFI固件设置》设置BIOS/UEFI是计算机维护和管理中的一项重要任务,它允许用户配置计算机的启动选项、硬件设置和其他关键参数,该怎么进入呢?下面... 随着计算机技术的发展,大多数主流 PC 和笔记本已经从传统 BIOS 转向了 UEFI 固件。很多时候,我们也

基于Qt开发一个简单的OFD阅读器

《基于Qt开发一个简单的OFD阅读器》这篇文章主要为大家详细介绍了如何使用Qt框架开发一个功能强大且性能优异的OFD阅读器,文中的示例代码讲解详细,有需要的小伙伴可以参考一下... 目录摘要引言一、OFD文件格式解析二、文档结构解析三、页面渲染四、用户交互五、性能优化六、示例代码七、未来发展方向八、结论摘要

MyBatis框架实现一个简单的数据查询操作

《MyBatis框架实现一个简单的数据查询操作》本文介绍了MyBatis框架下进行数据查询操作的详细步骤,括创建实体类、编写SQL标签、配置Mapper、开启驼峰命名映射以及执行SQL语句等,感兴趣的... 基于在前面几章我们已经学习了对MyBATis进行环境配置,并利用SqlSessionFactory核

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个