最近学习openCV的时候,感觉opencv对视频的处理能力不是很强,才开始接触ffmpeg。


  • ffmpeg是用C语言写的,里面都是结构体与库函数,没有类,所以在C++文件中添加头文件是一般会这样写:
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
};

这样做是因为C++的编译器会对程序中符号进行修饰,这个过程在编译器中叫符号修饰(Name Decoration)或者符号改编(Name Mangling)。我们知道C++是能够兼容C的,如果我们有了一个C语言的头文件和其对应的库,在C++中如何使用它呢?在include该头文件的时候当然要加入extern "C",否则按照C++的符号进行符号修饰,那么在库中就会找不到该符号了。

另外,C语言不支持extern "C"语法,如果我们想写一个头文件,同时支持被C和C++引用,该怎么办?可以使用C++的宏 "__cplusplus"来判断是不是C++编译器。

#ifdef __cplusplus
extern "C" {
#endif

// 正式定义。。。

#ifdef __cplusplus
}
#endif

  • 接下来创建一些可能用到的变量,启用ffmpeg的api
AVFormatContext	*pFormatCtx;   //封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息。
int	i,videoindex;		        //
AVCodecContext	*pCodecCtx;		//编码器上下文结构体,保存了视频(音频)编码相关信息。
AVCodec			*pCodec;		//每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体。
AVFrame	*pFrame,*pFrameYUV;		//存储一帧解码后像素(采样)数据。
uint8_t *out_buffer;			//
AVPacket *packet;				//存储一帧压缩编码数据。

对于上述的几种结构体,对于一个完整的ffmpeg程序是必不可少的下面我们简略介绍一下:

AVFormatContext
封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息
iformat:输入视频的AVInputFormat
nb_streams :输入视频的AVStream个数
streams :输入视频的AVStream[]数组
duration :输入视频的时长(以微秒为单位)
bit_rate :输入视频的码率
AVInputFormat
每种封装格式(例如FLV, MKV, MP4, AVI)对应一个该结构体
name:封装格式名称
long_name:封装格式的长名称
extensions:封装格式的扩展名
id:封装格式ID
一些封装格式处理的接口函数
AVStream
视频文件中每个视频(音频)流对应一个该结构体
id:序号
codec:该流对应的AVCodecContext
time_base:该流的时基
r_frame_rate: 该流的帧率
AVCodecContext
编码器上下文结构体,保存了视频(音频)编解码相关信息
codec:编解码器的AVCodec
width, height:图像的宽高(只针对视频)
pix_fmt:像素格式(只针对视频)
sample_rate:采样率(只针对音频)
channels:声道数(只针对音频)
sample_fmt:采样格式(只针对音频)
AVCodec
每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体
name:编解码器名称
long_name:编解码器长名称
type:编解码器类型
id:编解码器ID
一些编解码的接口函数
AVPacket
存储一帧压缩编码数据
pts:显示时间戳
dts :解码时间戳
data :压缩编码数据
size :压缩编码数据大小
stream_index :所属的AVStream
AVFrame
存储一帧解码后像素(采样)数据
data:解码后的图像像素数据(音频采样数据)
linesize:对视频来说是图像中一行像素的大小;对音频来说是整个音频帧的大小
width, height:图像的宽高(只针对视频)
key_frame:是否为关键帧(只针对视频)
pict_type:帧类型(只针对视频)。例如I,P,B

以上这些东西需要勤加练习查阅,才能熟练掌握,死记硬背效率太低。可以参考下图结合记忆。

ffmpeg_python 和ffmpeg的区别 ffmpeg和opencv是什么关系_数据

 

声明了相应的结构体变量之后,开始进入视频文件解封装解码流程

av_register_all();						//注册所有文件格式和编解码库
	avformat_network_init();				//打开网络视频流
	pFormatCtx = avformat_alloc_context();	//分配内存

	if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){     // 读取文件头部把信息保存到AVFormatContext结构体
		
		printf("Couldn't open input stream.\n");
		return -1;
	}
	if(avformat_find_stream_info(pFormatCtx,NULL)<0){	//为pFormatCtx->streams填充上正确的信息
		printf("Couldn't find stream information.\n");
		return -1;
	}

关于av_register_all()函数参考下图:

详情参考:ffmpeg 源代码简单分析 : av_register_all()

ffmpeg_python 和ffmpeg的区别 ffmpeg和opencv是什么关系_ffmpeg_02

对于avformat_network_init()网络推流与打开网络流时都要在前面加上。

avformat_alloc_context就是分配一块AVFormatContext内存,关于相关结构体的初始化与销毁如下。

ffmpeg_python 和ffmpeg的区别 ffmpeg和opencv是什么关系_编解码器_03

 

结构体

初始化

销毁

AVFormatContext

avformat_alloc_contetxt()

avformat_free_context()

AVIOContext

avio_alloc_context()

 

AVStream

avformat_new_stream()

 

AVCodecContext

avcodec_alloc_context3()

 

AVFrame

av_frame_alloc();

av_image_fill_arrays()

av_frame_free()

AVPacket

av_init_packet();

av_new_packet()

av_free_packet()

AVFormatContext	*pFormatCtx;            //封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息。
	int		i, videoindex;			//
	AVCodecContext	*pCodecCtx;				//编码器上下文结构体,保存了视频(音频)编码相关信息。
	AVCodec			*pCodec;				//每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体。
	AVFrame	*pFrame,*pFrameYUV;				//存储一帧解码后像素(采样)数据。
	uint8_t *out_buffer;					//
	AVPacket *packet;						//存储一帧压缩编码数据。
    pFrame=av_frame_alloc();
	pFrameYUV=av_frame_alloc();
	out_buffer=(uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));//获取编码信息对于YUV420格式的内存大小
	avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
	packet=(AVPacket *)av_malloc(sizeof(AVPacket));
	//Output Info-----------------------------
	printf("--------------- File Information ----------------\n");
	av_dump_format(pFormatCtx,0,filepath,0);
	printf("-------------------------------------------------\n");

avformat_open_input()函数:FFMPEG打开媒体的的过程开始于avformat_open_input,因此该函数的重要性不可忽视。

ffmpeg_python 和ffmpeg的区别 ffmpeg和opencv是什么关系_数据_04

接下来就是取出pFormatCtx中的帧数据了

frame_cnt=0;
	FILE *fout = fopen("output.h264", "ab+");
	FILE *fout_yuv = fopen("output.yuv", "ab+");
	while(av_read_frame(pFormatCtx, packet)>=0){			//从输入文件读取一帧压缩数据
		if(packet->stream_index==videoindex){
			//ffmpeg中的avcodec_decode_video2()的作用是解码一帧视频数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame
			ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
			fwrite(pFrame, 1, sizeof(AVFrame), fout);
			if(ret < 0){
				printf("Decode Error.\n");
				return -1;
			}
			if(got_picture){
				sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, 
					pFrameYUV->data, pFrameYUV->linesize);
				printf("Decoded frame index: %d\n",frame_cnt);
				/*
				 * 在此处添加输出YUV的代码
				 * 取自于pFrameYUV,使用fwrite()
				 */
				fwrite(pFrameYUV, 1, sizeof(AVFrame), fout_yuv);
				frame_cnt++;
			}
		}
		av_free_packet(packet);
	}

ffmpeg中的av_read_frame()的作用是读取码流中的音频若干帧或者视频一帧。例如,解码视频的时候,每解码一个视频帧,需要先调用 av_read_frame()获得一帧视频的压缩数据,然后才能对该数据进行解码(例如H.264中一帧压缩数据通常对应一个NAL)。