最近学习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
以上这些东西需要勤加练习查阅,才能熟练掌握,死记硬背效率太低。可以参考下图结合记忆。
声明了相应的结构体变量之后,开始进入视频文件解封装解码流程
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()
对于avformat_network_init()网络推流与打开网络流时都要在前面加上。
avformat_alloc_context就是分配一块AVFormatContext内存,关于相关结构体的初始化与销毁如下。
结构体 | 初始化 | 销毁 |
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,因此该函数的重要性不可忽视。
接下来就是取出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)。