现在正在学FFMPEG相关知识,写此文的目的就是便于自己对一些函数接口的理解,也希望对同样学习FFMPEG的人有一些帮助。代码不是自己写的,主要是在原代码的基础上增加了一些注释。
代码中涉及到的AVFrame中的linesize知识,可以参考下此篇博文介绍:http://blog.csdn.net/h514434485/article/details/51788666
#include
#include
#define INBUF_SIZE 4096
FILE *pFin = NULL;
FILE *pFout = NULL;
AVCodec *pCodec = NULL;
AVCodecContext *pCodecContext = NULL;
//用于解析码流,生存可以供解码器解码的包
AVCodecParserContext *pCodecParserCtx=NULL;
AVFrame *frame=NULL;
AVPacket pkt;
static int open_decoder()
{
//注册编解码器所有的组件
avcodec_register_all();
//初始化avpacket
av_init_packet(&pkt);
pCodec=avcodec_find_decoder(AV_CODEC_ID_H264);
if ( !pCodec )
{
printf("Error: find codec failed\n");
return -1;
}
pCodecContext = avcodec_alloc_context3(pCodec);
if ( !pCodecContext )
{
printf("Error: alloc codecCtx failed\n");
return -1;
}
//读取码流时,可能不是按一个完整的包来读取的,所以要判断下
/*
通知解码器,我们能够处理截断的流。
为什么会有截断流?因为视频流中的数据是被分割放入包中的,因为每个视频帧的数据大小是可变的
那么两帧之间的边界就不一定刚好是包的边界,这里通知解码器,我们可以处理截断流。
*/
if (pCodec->capabilities & AV_CODEC_CAP_TRUNCATED)
{
//截断的方式来读取,流帧边界可以在包中
pCodecContext->flags |= AV_CODEC_CAP_TRUNCATED;
}
//根据h264格式初始化解析器
pCodecParserCtx= av_parser_init(AV_CODEC_ID_H264);
if ( !pCodecParserCtx )
{
printf("Error:alloc parser failed\n");
return -1;
}
//打开解码器
if ( avcodec_open2(pCodecContext,pCodec,NULL)<0 )
{
printf("Error: Opening codec failed");
return -1;
}
//分配frame对象
frame=av_frame_alloc();
if ( !frame )
{
printf("Error: Alloc frame failed");
return -1;
}
return 0;
}
static int open_input_output_file(char **argv)
{
const char *inputFileName=argv[1];
const char *outputFileName=argv[2];
pFin=fopen(inputFileName,"rb+");
if ( !pFin )
{
printf("Error: open input file failed\n");
return -1;
}
pFout=fopen(outputFileName,"wb+");
if ( !pFin )
{
printf("Error: open output file failed\n");
return -1;
}
return 0;
}
static void write_out_yuv_frame(AVFrame *frame)
{
unsigned char **pBuf=frame->data;//frame所保存的像素的地址
int *pStride = frame->linesize;//保存一帧的位宽,一行究竟有多少像素
int color_idx;
int idx;
//YUV有三个颜色分量,分三步来实现
for ( color_idx = 0 ; color_idx < 3 ; color_idx++ )
{
int nWidth = color_idx==0?frame->width:frame->width/2;
int nHeight = color_idx==0?frame->height:frame->height/2;
for ( idx = 0 ; idx < nHeight ; idx++ )
{
fwrite(pBuf[color_idx],1,nWidth,pFout);
pBuf[color_idx]+=pStride[color_idx];
}
fflush(pFout);
}
}
static void Close()
{
fclose(pFin);
fclose(pFout);
av_parser_close(pCodecParserCtx);
avcodec_close(pCodecContext);
av_free(pCodecContext);
av_frame_free(&frame);
}
int main(int argc, char **argv)
{
/*
从输入文件中读取码流数据,保存到内存缓存中,AV_INPUT_BUFFER_PADDING_SIZE=32
*/
char inbuf[INBUF_SIZE+AV_INPUT_BUFFER_PADDING_SIZE]={0};
int ret;
if(open_input_output_file(argv)<0)
{
printf("Error:open file fail\n");
return -1;
}
if ( open_decoder()<0 )
{
printf("Error: open decoder fail\n");
return -1;
}
printf("Open file and decoder succeed!\n");
//一次读入到我们定义的缓存的长度
int uDataSize=0;
//缓存中被解析出数据的长度,
int len;
//got_frame表示是否解码出一个完整像素的帧
int got_frame;
unsigned char *pDataPtr=NULL;
while ( 1 )
{
uDataSize = fread(inbuf, 1,INBUF_SIZE, pFin);
if ( uDataSize==0)
{
//读取文件失败或读取到文件末尾
break;
}
pDataPtr=inbuf;
//从码流文件中解析出packet包
while ( uDataSize > 0)
{
len=av_parser_parse2(pCodecParserCtx,pCodecContext,&pkt.data,&pkt.size,
pDataPtr,uDataSize,AV_NOPTS_VALUE,AV_NOPTS_VALUE,AV_NOPTS_VALUE);
pDataPtr+=len;
uDataSize-=len;
if ( pkt.size==0 )
{
//表示一个包还没解析完成
continue;
}
//成功解析出一个packet的码流
printf("Parse 1 packet.\n");
//调用解码器,对包里面的码流进行解码
ret=avcodec_decode_video2(pCodecContext,frame,&got_frame,&pkt);
if ( ret < 0 )
{
printf("Error:decode failed\n");
return -1;
}
if(got_frame)
{
printf("Decoded 1 frame ok!Width x Height:(%d x %d)\n",frame->width,frame->height);
write_out_yuv_frame(frame);
}
}
}
//解码器中仍然可能存在尚未输出的数据
pkt.data=NULL;
pkt.size=0;
while ( 1 )
{
ret=avcodec_decode_video2(pCodecContext,frame,&got_frame,&pkt);
if ( ret<0 )
{
printf("Error:decode failed\n");
return -1;
}
if ( got_frame )
{
printf("flush decoder:Decoded 1 frame ok!Width x Height:(%d x %d)\n",frame->width,frame->height);
write_out_yuv_frame(frame);
}
else
{
break;
}
}
Close();
return 0;
}
#include
#include
#include
#include
#include
const char *inputFilename=NULL; const char *outputFilename=NULL; FILE *pFin=NULL,*pFout=NULL; int frameWidth=0,frameHeight=0; int bitRate =0,frameToEncode=0; AVCodec *codec = NULL;//编解码器 AVCodecContext *codecCtx=NULL;//编码器的上下文 AVFrame *frame=NULL; AVPacket pkt; //编码之后的码流 static int read_yuv_data(int color); static int parse_input_paramaters(int argc, char **argv) { //inputFilename = argv[1]; //outputFilename = argv[2]; inputFilename = "./ds_480x272.yuv"; outputFilename = "./ds1.h264"; pFin=fopen(inputFilename,"rb+"); if ( !pFin ) { return -1; } pFout=fopen(outputFilename,"wb+"); if ( !pFin ) { return -1; } /*获取视频宽高*/ //frameWidth=atoi(argv[3]); //frameHeight=atoi(argv[4]); //bitRate=atoi(argv[5]); frameWidth=480; frameHeight=272; bitRate=400000; /*要编码的帧数*/ //frameToEncode=atoi(argv[6]); frameToEncode=100; return 1; } int main(int argc, char **argv) { int got_packet=0; int frameIdx; int color_0; int color_1; int color_2; int y_size; int ret; int framecnt=0; if ( parse_input_paramaters(argc,argv) > 0 ) { printf("Input file:%s\nOutput file:%s\n",inputFilename,outputFilename); printf("Frame resolution:(%d x %d)\nBit rate:%d\nFrame num to code:%d\n",frameWidth,frameHeight,bitRate,frameToEncode); } else { printf("Error: command line put error!\n"); } /*注册编解码器组件*/ avcodec_register_all(); //查找AVCodec编解码器 codec=avcodec_find_encoder(AV_CODEC_ID_H264); if ( !codec ) { //查找失败 return -1; } //分配AVCodecContext实列 codecCtx=avcodec_alloc_context3( codec); if ( !codecCtx ) { //查找失败 return -11; } //设置编码器参数 codecCtx->width = frameWidth; codecCtx->height = frameHeight; codecCtx->bit_rate = bitRate; //codecCtx->bit_rate = 400000; AVRational r = {1,25}; codecCtx->time_base = r; //codecCtx->gop_size = 12; //连续画面组大小 codecCtx->gop_size = 10; //连续画面组大小 //两个非B帧之间允许出现多少个B帧数 //设置0表示不使用B帧 //B帧越多,图片越小 codecCtx->max_b_frames = 1; codecCtx->pix_fmt = AV_PIX_FMT_YUV420P; //设置私有参数 av_opt_set(codecCtx->priv_data,"preset","slow",0); //打开编码器 if(avcodec_open2(codecCtx, codec, NULL)< 0) { return -11; } //分配AVFrame以及像素存储空间 frame=av_frame_alloc(); if ( !frame ) { return -11; } frame->width = codecCtx->width; frame->height = codecCtx->height; frame->format = codecCtx->pix_fmt; //分配avframe包含的像素空间 /* 分配像素的存储空间,内存以16字节对齐 */ if(av_image_alloc(frame->data,frame->linesize,frame->width,frame->height,codecCtx->pix_fmt,16) < 0) { printf("ERROR:avimag alloc fail!\n"); return -11; } y_size = codecCtx->width * codecCtx->height; /*编码过程*/ for ( frameIdx = 0 ; frameIdx < frameToEncode; frameIdx++ ) { //初始化以后avpacket就可以装载编码之后的码流 av_init_packet(&pkt); pkt.data = NULL; pkt.size = 0; //读取数据到AVFrame //将三个颜色分量读取到AVFrame中 color_0=read_yuv_data(0); color_1=read_yuv_data(1); color_2=read_yuv_data(2); printf("Color0 size:%d,Colort1:%d,Color2:%d\n",color_0,color_1,color_2); //这一帧被显示的时间 frame->pts = frameIdx; printf("The frame pts:%d\n",frameIdx); //编码 //将AVFrame编码成avpack //got_packet表示是否完成编码出一个完整的数据包 if ( avcodec_encode_video2(codecCtx, &pkt, frame, &got_packet) < 0) { printf("Error: encoding failed\n"); return -2; } printf("Got packet flag:%d\n",got_packet); if ( got_packet ) { //表示编码成一个完整的包 printf("Write packet of frame %d,size = %d\n",frameIdx,pkt.size); fwrite(pkt.data, 1, pkt.size, pFout); av_packet_unref(&pkt); } } //有时候编码器中还保留未输出的编码数据,下面就是将编码器中可能存在的数据进行输出 for ( got_packet = 1 ; got_packet ; ) { //第三个参数设置为NULL就不会从frame中读取数据,而是编码自身内部的缓存数据 if ( avcodec_encode_video2(codecCtx, &pkt, NULL, &got_packet) < 0) { printf("Error: encoding failed\n"); return -2; } printf("Read data from cache got_packet:%d,line:%d\n",got_packet,__LINE__); if ( got_packet ) { //表示编码成一个完整的包 printf("Write cached packet size = %d\n",pkt.size); fwrite(pkt.data, 1, pkt.size, pFout); av_packet_unref(&pkt); } } //收尾 fclose(pFin); fclose(pFout); avcodec_close(codecCtx); av_free(codecCtx); /*释放frame中保存的像素*/ av_freep(&frame->data[0]); /*释放frame本身*/ av_frame_free(&frame); return 0; } static int read_yuv_data(int color) { //color = 0 表示Y分量 //color = 1 表示U分量 //color = 2 表示V分量 int row_idx; int color_height = color == 0 ? frameHeight:frameHeight/2; int color_width = color == 0 ? frameWidth:frameWidth/2; //颜色空间大小 int color_size = color_height*color_width; //各分量图像缓存的宽度,一般linesize是大于图像width的 int color_stride = frame->linesize[color]; //往frame里面读取数据 if ( color_width == color_stride ) { //表示颜色分量在AVFrame中是连续存放 fread(frame->data[color], color_size, 1, pFin); } else { //表示颜色分量不是连续存放,内存空间的边缘有填充的数据 //数据不能直接读取了,要按行,一行一行的读取 for ( row_idx = 0 ; row_idx < color_height ; row_idx++ ) { fread(frame->data[color]+row_idx*color_stride, color_width, 1, pFin); } } return color_size; }