由于工作需要,所以基本了解了一下视频的解码流程。
参考教程为:
1、王纲的《跟我一起学FFmpeg》系列
2、雷霄骅雷神的博客
原理部分暂时没有整理,后期可以补充一下知识。
ffmepg的api使用方面
1、打开一个输入流
2、设置解码器
3、读取每一个包,并获取到一帧的数据
4、交给解码器解码
下面的代码就是,读取一个视频或者文件,将其中的一帧图片保存为BGR24格式的文件,该文件加上BMP文件头即可使用图片浏览器打开。需要注意的是,这样会将图片上下翻转。所以图片会倒着显示。
#include
#include
#include
#include
#include
#include
#include
#define myprintf(x) printf(x)
unsigned char *filecontent;
unsigned char *content;
static int parse_cmd(int argc, char * argv[])
{
int ret = 0;
if (argc < 2)
return -1;
return ret;
}
static void show_help()
{
printf("Input error!\n");
printf("Usage: ./decodec-and-display-ffmpeg \n");
}
void add_header()
{
filecontent = (unsigned char*) malloc (0x5eec36);
content = filecontent + 0x36;
FILE *file = fopen("./1920*1080.bmp", "rb");
if (file < 0)
{
myprintf("file can not open!\n");
return;
}
printf("1920*1080.bmp open success!\n");
fread(filecontent, 1, 0x36, file);
printf("finish reading 1920*1080.bmp header!\n");
fclose(file);
file = fopen("./fpsave.bgr24", "rb");
fread(content, 1, 0x5eec00, file);
myprintf("finish reading ./fpsave.bgr24!\n");
fclose(file);
file = fopen("./fpsave-bgr24.bmp", "wb+");
fwrite(filecontent, 1, 0x5eec36, file);
fclose(file);
myprintf("Add header finish!\n");
}
int main(int argc, char *argv[])
{
int ret, got_picture;
int i, videoindex = -1; //这两个变量,主要是从流中找到视频流的编号,而非音频流
char filepath[128]; //输入文件路径
int dst_bytes_num; //一幅解码后的BGR24格式图片存储所需的内存
AVFormatContext *pFormatCtx; //上下文结构体,包含了所有的信息
AVCodecContext *pCodecCtx; //解码器用到的上下文结构体
AVCodec *pCodec; //解码器实例
AVFrame *pFrame,*pFrameBGR; //存放帧和BGR数据的结构体
AVPacket *avpacket = NULL; //这个结构体很关键,其中包含了ffmpeg从输入流中得到的裸流的数据和大小
FILE *fpSave; //先将解码后的数据保存到这个文件中,以便检验
uint8_t *out_buffer; //解码后的帧数据缓存
struct SwsContext *img_convert_ctx; //格式转换需要用到的结构体,暂时不对视频进行格式转换
/* 0、简单判断输入否合法 */
ret = parse_cmd(argc, argv);
if (ret < 0)
{
show_help();
return 0;
}
else
{
printf("%s\n", argv[1]);
}
/* 1、初始化ffmpeg */
av_register_all();
/* 2、打开输入流 */
/* 2.1打开网络流或文件流 */
pFormatCtx = avformat_alloc_context(); //分配一个输入流的上下文结构体,用以存储相关的数据
ret = avformat_open_input(&pFormatCtx, argv[1], NULL, NULL); //以设定的方式(参数)打开输入流,并与pFormatCtx结构体关联,但是此时未填充
if(ret)
{
printf("Couldn't open input stream.\n");
return -1;
}
/* 2.2获取流信息,并保存在上下文pFormatCtx中 */
ret = avformat_find_stream_info(pFormatCtx, NULL); //通过该函数,填充pFormatCtx结构体,如编码方式,流(音频、视频)的个数,宽高,帧率等
if(ret)
{
printf("Couldn't find stream information.\n");
return -1;
}
/* 2.3打印出输入文件信息 */
printf("---------------- File Information ---------------\n");
av_dump_format(pFormatCtx, 0, argv[1], 0);
printf("-------------------------------------------------\n");
/* 2.4从流中,找出视频流编号 */
videoindex=-1;
for( i = 0; i < pFormatCtx->nb_streams; i++)
{
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) //我们不关心音频AVMEDIA_TYPE_AUDIO,所以找出来视频流AVMEDIA_TYPE_VIDEO的编号,通常为0
{
videoindex=i;
//printf("videoindex = %d\n", videoindex);
break;
}
}
if(-1 == videoindex)
{
printf("Didn't find a video stream.\n");
return -1;
}
/* 3、解码输入流,获取到一帧的数据 */
/* 3.1根据编码类型查找解码器 */
pCodecCtx = pFormatCtx->streams[videoindex]->codec; //获取到视频的编码类型,对于MP4,pFormatCtx->streams[videoindex]->codec->codec_id = 13,即AV_CODEC_ID_MPEG4
//printf("pCodecCtx = pFormatCtx->streams[videoindex]->codec->codec_id = %d \n", pFormatCtx->streams[videoindex]->codec->codec_id);
pCodec = avcodec_find_decoder(pCodecCtx->codec_id); //用于查找FFmpeg的AV_CODEC_ID_MPEG4格式对应的解码器
if(pCodec==NULL)
{
printf("Codec not found.\n");
return -1;
}
/* 3.2打开解码器 */
if(avcodec_open2(pCodecCtx, pCodec,NULL)<0)
{
printf("Could not open codec.\n");
return -1;
}
/* 3.2为解码工作,分配存储空间 */
//为一帧编码的数据分配内存空间,这个函数只是分配AVFrame结构体,但data指向的内存并没有分配,需要我们指定,这个内存的大小就是一张特定格式图像所需的大小。
pFrame=av_frame_alloc();
//为一帧编解码后的BGR24数据分配一个结构体,这个函数只是分配AVFrame结构体,但data指向的内存并没有分配,需要我们指定,这个内存的大小就是一张特定格式图像所需的大小。
pFrameBGR=av_frame_alloc();
//分配的out_buffer大小是宽*高*3(RGB24)对于测试视频320*240*3=230400Byte,该buffer只是一个缓存
dst_bytes_num = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height);
out_buffer=(uint8_t *)av_malloc(dst_bytes_num);
if (out_buffer)
{
printf("out_buffer alloc success!\n");
printf("address of out_buffer = %p\n", out_buffer);
}
else
{
printf("out_buffer alloc failed!\n");
return 0;
}
//前面的av_frame_alloc函数,只是为这个AVFrame结构体分配了内存,而该类型的指针指向的内存还没分配。这里把av_malloc得到的内存和AVFrame关联起来,当然,其还会设置AVFrame的其他成员
//该函数会初始化pFrameBGR->linesize
ret = av_image_fill_arrays(pFrameBGR->data, pFrameBGR->linesize, out_buffer, AV_PIX_FMT_BGR24,pCodecCtx->width, pCodecCtx->height,1);
//经过测试,pFrameBGR->linesize[4]的初始值均为0,经过上面的初始化后,分别被赋值;pFrameBGR->data[4]为4个指针,初值均为NULL,后续被分配到内存
//如果参数是YUV420P,pFrameBGR->linesize[0] = width, pFrameBGR->linesize[1] = width / 2, pFrameBGR->linesize[2] = width / 2
//如果参数是BGR24,pFrameBGR->linesize[0] = width * 3, pFrameBGR->linesize[1] = 0, pFrameBGR->linesize[2] = 0
//printf("pFrameBGR->linesize[0] = %d, pFrameBGR->linesize[1] = %d, pFrameBGR->linesize[2] = %d, pFrameBGR->linesize[3] = %d\naddress of pFrameBGR->data[0] = %p, address of pFrameBGR->data[1] = %p, address of pFrameBGR->data[2] = %p\n", pFrameBGR->linesize[0], pFrameBGR->linesize[1], pFrameBGR->linesize[2], pFrameBGR->linesize[3], pFrameBGR->data[0], pFrameBGR->data[1], pFrameBGR->data[2]);
//为AVPacket结构体分配缓存
avpacket=(AVPacket *)av_malloc(sizeof(AVPacket));
//对转换进行设置,这里要设置转换源的大小、格式和转换目标的大小、格式,以及转换所用的算法
//img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL);
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL);
if (img_convert_ctx == NULL)
{
printf("sws_getContext failed!\n ");
return -1;
}
if ((fpSave = fopen("./fpsave.bgr24", "wb+")) == NULL) //data数据保存的文件名
return 0;
int cnt = 0;
int cnt_decode = 0;
while(av_read_frame(pFormatCtx, avpacket)>=0)
{
if(avpacket->stream_index==videoindex)
{
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, avpacket); //真正的解码工作,将压缩的数据解码为yuv420p格式
if(ret < 0)
{
printf("解码错误\n");
return -1;
}
else
{
cnt_decode++;
}
if(got_picture)
{
printf("解码一帧所需的次数cnt_decode = %d\n", cnt_decode);
printf("解码成功\n");
sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameBGR->data, pFrameBGR->linesize);
printf("YUV420P->BGR24格式转换完成\n");
//y_size=pCodecCtx->width*pCodecCtx->height*3;
if (cnt == 0)
fwrite(pFrameBGR->data[0], 1, dst_bytes_num, fpSave); //BGR24
//fwrite(pFrameBGR->data[0], 1, y_size, fpSave); //Y
//fwrite(pFrameBGR->data[1], 1, y_size/4,fpSave); //U
//fwrite(pFrameBGR->data[2], 1, y_size/4,fpSave); //V
printf("Succeed to decode 1 frame!\n");
printf("avpacket->stream_index = %d\n", avpacket->stream_index);
printf("avpacket->size = %d, cnt = %d\n", avpacket->size, cnt);
//fwrite(avpacket->data,1,avpacket->size,fpSave);//写数据到文件中
cnt++;
if (cnt == 1)
break;
}
}
av_free_packet(avpacket);
}
fclose(fpSave);
add_header();
av_frame_free(&pFrameBGR);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
return 0;
}