FFmpeg 学习之路,个人通过多方资料阅读而得出的,做个记录,也分享给其他人,降低不必要的社会劳动力。
操作系统:window 7 x64
使用IDE :Qt Creator 4.2.1
使用语言:纯C语言
FFmpeg版本:3.3.1
请务必下载源码,进行跟读
版权所有:_OE_, 转载请注明出处:http://blog.csdn.net/csnd_ayo
还没整理好,有需要的可以留邮箱。
对于 ffmpeg
在这里我就不做介绍了,可以自行百度。在这里主要对源码进行逐一解释。
这是根据源码的执行流程画的执行架构图,点击可查看大图
该架构图对应的讲解视频 [点击前往]
我们的代码将根据这张架构图的路线进行编写。
而我们这篇文章就是对每个调用过程做一个较为详细的讲解。
// typedef struct AVFormatContext {
// struct AVInputFormat *iformat;
// struct AVOutputFormat *oformat;
// AVIOContext *pb;
// unsigned int nb_streams;
// AVStream **streams;
// char filename[1024]; /**< input or output filename */
// ....
// } AVFormatContext;
// AVFormatContext在FFMpeg里是一个非常重要的的结构,是其它输入、输出相关信息的一个容器
// 以上只列出了其中的部分成员
// 作为输入容器时 struct AVInputFormat *iformat; 不能为空, 其中包含了输入文件的音视频流信息,程序从输入容器从读出音视频包进行解码处理
// 作为输出容器时 struct AVOutputFormat *oformat; 不能为空, 程序把编码好的音视频包写入到输出容器中
// AVIOContext *pb: I/O上下文,通过对该变量赋值可以改变输入源或输出目的
// unsigned int nb_streams; 音视频流数量
// AVStream **streams; 音视频流
// 视频流的格式内容
AVFormatContext *pFormatCtx;
/// 解码器上下文
AVCodecContext *pCodecCtx;
/// 解码器
AVCodec *pCodec;
/// 结构性视频数据
AVFrame *pFrame, *pFrameRGB;
/// 视频数据包 (1帧)
AVPacket *packet;
uint8_t *out_buffer;
struct SwsContext *sws_ctx;
int videoStream, numBytes;
int ret, frameFinished;
/// 内部调用 avcodec_register_all()
/// avcodec_register_all()注册了和编解码器有关的组件:硬件加速器,
/// 解码器,编码器,Parser,Bitstream Filter。
/// av_register_all()除了调用avcodec_register_all()之外,
/// 还注册了复用器,解复用器,协议处理器。
av_register_all();
// 这里为 AVFormatContext 分配了一个内存空间
pFormatCtx = avformat_alloc_context();
/**
* @brief : 该函数用于打开多媒体并且获得一些相关的信息
* 保存到我们给的AVFormatContext结构
*
* @param : ps 函数调用成功之后处理过的AVFormatContext结构体。
*
* @param : filename 打开的视音频流的文件路径。
*
* @param : fmt 强制指定AVFormatContext中AVInputFormat的。
* 这个参数一般情况下可以设置为NULL,
* 这样FFmpeg可以自动检测AVInputFormat。
*
* @param : options 附加的一些选项,一般情况下可以设置为NULL。
*
* @return: 函数执行成功的话,其返回值大于等于0
*
* @note : 1、打开文件 2、初始化并推测了视频文件的类型
* 3、读取文件的头部信息,填充了文件上下文数据.
*
* @blog : http://blog.csdn.net/leixiaohua1020/article/details/44064715
*/
if (avformat_open_input(&pFormatCtx, file_path, NULL, NULL) != 0) {
printf("can't open the file. \n");
return -1;
}
/**
* @brief : 该函数可以检查在文件中的流的信息,读取一部分视音频数据并且获得一些相关的信息。
* @param : ic 视频文件的句柄,在这里指的就是 AVFormatContext 视频上下文
* @param : options 其他附加的参数属性
* @return: 函数正常执行后返回值大于等于0。
* @note : 1.查找解码器
* 2.打开解码器
* 3.读取完整的一帧压缩编码的数据
* 4.解码一些压缩编码数据
* @blog : http://blog.csdn.net/leixiaohua1020/article/details/44084321
*/
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
printf("Could't find stream infomation.\n");
return -1;
}
/// 找到第一个视频流
/// 循环查找视频中包含的流信息,直到找到视频类型的流
/// 便将其记录下来 保存到videoStream变量中
for (unsigned int i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type
== AVMEDIA_TYPE_VIDEO) {
videoStream = i;
}
}
/// 查找解码器
/// @blog : http://blog.csdn.net/leixiaohua1020/article/details/44084557
// 通过code ID查找一个已经注册的音视频解码器
// 查找解码器之前,必须先调用av_register_all注册所有支持的解码器
// 查找成功返回解码器指针,否则返回NULL
// 音视频解码器保存在一个链表中,查找过程中,函数从头到尾遍历链表,通过比较解码器的ID来查找
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
/**
* @brief : 该函数用于打开并初始化一个视音频编解码器的AVCodecContext。
*
* @param : avctx 需要初始化的AVCodecContext。
* @param : codec 输入的AVCodec
* @param : options 一些选项。例如使用libx264编码的时候,
* “preset”,“tune”等都可以通过该参数设置。
*
* @return: 返回0则正确
*
* @blog : http://blog.csdn.net/leixiaohua1020/article/details/44117891
*/
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
printf("Could not open codec.\n");
return -1;
}
// 因为我们准备输出保存24位RGB色的PPM文件,我们必需把帧的格式从原来的转换为RGB。
// FFMPEG将为我们做这些转换。
// 在大多数项目中(包括我们的这个)我们都想把原始的帧转换成一个特定的格式。
// 让我们先为转换来申请一帧的内存
pFrame = av_frame_alloc();
pFrameRGB = av_frame_alloc();
/**
* @brief : 图像转换,格式转换 像素转换 图像大小转换
*
* @param : srcW 源图像的宽
* @param : srcH 源图像的高
* @param : srcFormat 源图像的像素格式
* @param : dstW 目标图像的宽
* @param : dstH 目标图像的高
* @param : dstFormat 目标图像的像素格式
* @param : flags 设定图像拉伸使用的算法
* @return: 成功执行的话返回生成的SwsContext,否则返回NULL。
*
* @blog : http://blog.csdn.net/leixiaohua1020/article/details/44305697
*/
sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height,AV_PIX_FMT_RGB24,
SWS_FAST_BILINEAR, NULL, NULL, NULL);
/**
* @brief : 获得这个图像占用空间的大小
*
* @param : pix_fmt 像素格式
* @param : width 图片宽度
* @param : height 图片高度
*
* @return: 返回图像的大小,在错误的情况下返回负值
*
* @note : 即使我们申请了一帧的内存,当转换的时候,我们仍然需要一个地方来放置原始的数据。
* 我们使用avpicture_get_size来获得我们需要的大小,然后手工申请内存空间:
*/
numBytes = avpicture_get_size(AV_PIX_FMT_RGB24,
pCodecCtx->width, pCodecCtx->height);
/**
* @brief : 填充图像到指定存储区
*
* @param : picture 要填写的图像数据
* @param : ptr 存储图像数据的缓冲区
* @param : pix_fmt 图像的像素格式
* @param : width 图像的宽度
* @param : height 图像的高度
*
* @return: 源图像所需要的字节大小,失败则返回负数
*
* @note : 现在我们使用avpicture_fill来把帧和我们新申请的内存来结合。
* 关于AVPicture的结成:AVPicture结构体是AVFrame结构体的子集
* AVFrame结构体的开始部分与AVPicture结构体是一样的。
*
*/
avpicture_fill((AVPicture *) pFrameRGB, out_buffer, AV_PIX_FMT_RGB24,
pCodecCtx->width, pCodecCtx->height);
/**
* @brief : 打印有关输入或输出格式的详细信息,如持续时间、
* 比特率、流、容器、程序、元数据、侧数据、编解码器和时基。
*
* @param ic 需要分析的上下文指针
* @param index 流索引
* @param url 要打印的文件路径(这个参数只是为了格式化输出,没有实质意义)
* @param is_output 选择指定的上下文是否为输入(0)或输出(1)
*/
av_dump_format(pFormatCtx, 0, file_path, 0); //输出视频信息
/**
* @brief : 读取码流中的音频若干帧或者视频一帧
*
* @param : s 输入的 AVFormatContext 视频上下文
* @param : pkt 输出 AVPacket
*
* @note : 解码视频的时候,每解码一个视频帧,
* 需要先调用 av_read_frame()获得一帧视频的
* 压缩数据,然后才能对该数据进行解码
*
* @blog : http://blog.csdn.net/leixiaohua1020/article/details/12678577
*/
if (av_read_frame(pFormatCtx, packet) < 0) {
break; // 这里认为视频读取完了
}
/**
* @brief : 解码一帧视频数据
*
* @param : avctx 解码器上下文
* @param : picture 输出一个解码后的结构体AVFrame
* @param : got_picture_ptr 没得到数据他是0 得到数据他是非0。
* @param : avpkt 解析的包
*
* @return: 错误的情况下返回负值,否则返回0或字节数
*
* @blog : http://blog.csdn.net/leixiaohua1020/article/details/12679719
*/
ret = avcodec_decode_video2(pCodecCtx, pFrame,
&frameFinished, packet);
/**
* @brief : 处理图像数据,用于转换像素的函数.根据窗口有无拉伸对图像进行转换
*
* @param : c 经过 sws_getContext() 创建的上下文
* @param : srcSlice 解码后的数据源
* @param : srcStride 源图片的数据步长
* @param : srcSliceY
* @param : srcSliceH 源切片的高度,即切片中的行数
* @param : dst 包含目标图像平面指针的数组
* @param : dstStride 目标图片的数据步长
*
* @return: 输出片的高度
*
* @blog : http://blog.csdn.net/leixiaohua1020/article/details/44346687
*/
sws_scale(sws_ctx,pFrame->data, pFrame->linesize,
0, pCodecCtx->height,
pFrameRGB->data, pFrameRGB->linesize);
///现在我们需要做的是让SaveFrameToPPM函数能把RGB信息定稿到一个PPM格式的文件中。
///我们将生成一个简单的PPM格式文件,请相信,它是可以工作的。
void SaveFrameToPPM(AVFrame *pFrame, int width, int height,int index) {
FILE *pFile;
char szFilename[32];
int y;
// Open file
sprintf(szFilename, "frame%d.ppm", index);
pFile=fopen(szFilename, "wb");
if(pFile==NULL)
return;
// Write header
fprintf(pFile, "P6\n%d %d\n255\n", width, height);
// Write pixel data
for(y=0; ydata[0]+y*pFrame->linesize[0], 1, width*3, pFile);
// Close file
fclose(pFile);
}
这里的图片因为格式特殊,而且保存的比较简练,常规的图片查看器是无法查看图片的。
在此,文章的总结大多源自 雷霄骅
的博客,感谢博士的分析于分享。博士,一路走好。