05 FFmpeg4.4源码分析--解码

一、解码原理

视频解码实现的是将压缩域的视频数据解码为像素域的 YUV 数据。实现的过程,可以大致用如下图所示:

从图中可以看出,大致可以分为下面三个步骤:

  1. 首先要有待解码的压缩域的视频作为输入
  2. 其次根据视频视频的压缩格式获得对应解码器
  3. 最后通过解码器解码,输出像素域为YUV的解码后数据

二、解码流程

三、源码解析

1、av_register_all()

void av_register_all(void);

作用:

  该函数注册支持的所有的文件格式(容器)及其对应的CODEC,只需要调用一次。 

注意点:在ffmpeg4.0之后的版本后,已经被弃用,为了兼容之前版本,在ffmpeg4.0之后的版本中,还保留该函数,但其内部其实什么都没有做,所以也可以直接省略。但在ffmpeg5.0之后,该函数完全被弃用,即无法完成函数声明。


2、avformat_network_init()

int avformat_network_init(void)

作用:

 对网络库进行全局初始化。

注意点: 此函数仅用于解决旧 GnuTLS 或 OpenSSL 库的线程安全问题。如果 libavformat 链接到这些库的较新版本,或者您不使用它们,则无需调用此函数。否则,您需要在使用它们的任何其他线程启动之前调用此函数。


3、avformat_alloc_context()

AVFormatContext * avformat_alloc_context(void)

作用:

  使用默认参数分配并初始化一个AVFormatContext对象。

返回值:

  成功返回已分配且完成默认初始化的 AVFormatContext对象,否则返回NULL

注意:使用完毕后,必须使用函数avformat_free_context()函数进行释放

void avformat_free_context(AVFormatContext* s)	

 4、avformat_open_input()

int avformat_open_input(AVFormatContext **ps, const char *filename,
                        ff_const59 AVInputFormat *fmt, AVDictionary **options);

作用:

  • 如有必要,则为 AVFormatContext 分配内存。
  • 尝试猜测输入文件格式,输入文件的编解码器参数
  • 分配编解码器上下文、解复用上下文、I/O 上下文。

返回值:

  成功返回0,否则返回负数错误码

参数含义:

  • ps: 媒体相关的上下文结构信息,需要注意的是:若该函数调用失败,则其将被系统主动释放。
  • filename: 媒体文件名或URL.  
  • fmt:将要打开的媒体格式的操作结构,因为是读,所以是AVInputFormat结构.对应ffmpeg命令行中的 -f xxx段,若为NULL,则有系统自己探测获取其格式,否则,以此参数为准而不会探测文件的实际格式.  
  • options:一个AVFormatContext和 demuxer-private 选项的字典。该函数返回时,将被销毁,无需配置则设置为NULL。

5、avformat_find_stream_info()

int avformat_find_stream_info(AVFormatContext *ic,AVDictionary **options)

作用:

    读取媒体文件的数据包以获取媒体流信息。

返回值:

    大于等于0表示成功,否则则表示失败

参数含义 

  • ic:   媒体相关的上下文结构信息
  • options: 如果非 NULL,则为 ic.nb_streams 指向字典的长指针数组,其中第 i 个成员包含对应于第 i 个流的编解码器选项。 返回时,每个字典都将填充未找到的选项。

6、avcodec_find_decoder()

const AVCodec* avcodec_find_decoder(enum AVCodecID id)	

作用:

  查找 ID为id的已注册解码器

返回值:

  成功返回指定的解码器,失败则返回NULL

参数含义:

  • id:需要查找的解码器ID,其定义在codec_id.h文件中 

 7、avcodec_open2()

int avcodec_open2(AVCodecContext *avctx,const AVCodec *codec,
                    AVDictionary **options)	

 作用:

  初始化指定的编解码器

返回值:

  成功返回0,失败则返回负数

参数含义:

  • avctx:需要初始化的上下文信息
  • codec:要为avctx初始化的编解码器。 如果传入非 NULL值,则此值必须与avctx->codec值相同
  • options:一个AVCodecContext和 codec-private 选项的字典。返回时,此对象将填充未找到的选项。

 8、sws_getContext()

struct SwsContext* sws_getContext(int srcW,int srcH,enum AVPixelFormat 	srcFormat,
                                  int dstW,int dstH,enum AVPixelFormat 	dstFormat,
                                  int flags,SwsFilter* srcFilter,SwsFilter* dstFilter,
                                  const double* param)		

作用:

  分配并返回一个SwsContext。您需要它来使用sws_scale()执行缩放/转换操作。

返回值:

  成功返回 一个SwsContext指针,失败则返回NULL

参数含义:

  • srcW:源图像的宽度
  • srcH:源图像的高度
  • srcFormat:源图像格式
  • dstW:目标图像的宽度
  • dstH:目标图像的高度
  • dstFormat:目标图像格式
  • flags:指定用于重新缩放的算法和选项
  • param:调整使用的缩放器的额外参数

9、av_read_frame

int av_read_frame(AVFormatContext *s,AVPacket *pkt )	

 作用:

   读取码流中的音频若干帧或者视频一帧

返回值:

  成功返回0,失败则返回小于0的值,且pkt为NULL

参数含义:

  • s:输入的AVFormatContext

  • pkt:输出的AVPacket


 

三、示例代码:

#include <stdio.h>
#define __STDC_CONSTANT_MACROS

//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
};

int main()
{
	// 注册支持的所有的文件格式(容器)及其对应的CODEC,只需要调用一次
	av_register_all();    

	// 加载socket库以及网络加密协议相关的库,为后续使用网络相关提供支持
	avformat_network_init();   

	// 用来申请AVFormatContext类变量并初始化默认参数
	AVFormatContext* pFormatCtx = avformat_alloc_context();   

	// RTSP地址
	char filepath[] = "rtsp://admin:xjyc2020@172.23.240.11:554/Streaming/Channels/201";

	//打开网络流或文件流
	if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) {
		printf("Couldn't open input stream.\n");
		return -1;
	}

	//读取一部分视音频数据并且获得一些相关的信息
	if (avformat_find_stream_info(pFormatCtx, NULL) < 0){
		printf("Couldn't find stream information.\n");
		return -1;
	}

	int videoindex = -1;
	int i = -1;
	for (i = 0; i < pFormatCtx->nb_streams; i++) {
		if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)     //在多个数据流中找到视频流 video stream(类型为AVMEDIA_TYPE_VIDEO)
		{
			videoindex = i;
			break;
		}
	}

	if (videoindex == -1) {
		printf("Didn't find a video stream.\n");
		return -1;
	}

	AVCodecContext* pCodecCtx = pFormatCtx->streams[videoindex]->codec;

	//查找video stream 相对应的解码器
	AVCodec* pCodec = avcodec_find_decoder(pCodecCtx->codec_id);    
	if (pCodec == NULL) {
		printf("Codec not found.\n");
		return -1;
	}

	//打开解码器
	if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)  {
		printf("Could not open codec.\n");
		return -1;
	}

	AVFrame* pFrame = av_frame_alloc();    //为解码帧分配内存
	AVFrame* pFrameYUV = av_frame_alloc();
	uint8_t* out_buffer = (uint8_t*)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
	avpicture_fill((AVPicture*)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);

	//Output Info---输出一些文件(RTSP)信息
	printf("---------------- File Information ---------------\n");
	av_dump_format(pFormatCtx, 0, filepath, 0);
	printf("-------------------------------------------------\n");

	struct SwsContext* img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
		pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, 4, NULL, NULL, NULL);

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

	FILE* fpSave;
	//h264保存的文件名
	if ((fpSave = fopen("video.h264", "ab")) == NULL) {
		return 0;
	}
		
	for (;;) {
		if (av_read_frame(pFormatCtx, packet) >= 0)  //从流中读取读取数据到Packet中
		{
			if (packet->stream_index == videoindex)
			{
				fwrite(packet->data, 1, packet->size, fpSave);//写数据到文件中
			}
			av_free_packet(packet);
		}
	}


	av_frame_free(&pFrameYUV);
	av_frame_free(&pFrame);
	avcodec_close(pCodecCtx);     //需要关闭avformat_open_input打开的输入流,avcodec_open2打开的CODEC
	avformat_close_input(&pFormatCtx);

	return 0;
}

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Chiang木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值