FFmpeg编程FFmpeg中级开发
Posted 贺二公子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FFmpeg编程FFmpeg中级开发相关的知识,希望对你有一定的参考价值。
原味地址:https://www.cnblogs.com/ssyfj/p/14722272.html
文章目录
一:H264解码处理
(一)解码步骤
1.引入解码头文件
#include <libavcodec/avcodec.h>
2.常用数据结构
- AVCodec编码器结构体:所使用的编码器类型,(H264/H265,音频/视频)
- AVCodecContext编码器上下文:串联各个API,形成API链条,每个API都需要我们把上下文作为参数传入该API。内部也保存了编解码器信息,可以供其调用
- AVFrame解码后的帧:未压缩的帧(未编码)
3.结构体内存的分配和释放
4.解码步骤
- avcodec_find_decoder通过id查找解码器,当然也可以通过名字by_name查找到编解码器(如下面的编码);两种方式各有好处
- avcodec_decode_video2编解码一般使用外部编解码库,比如libx264;
注意:avcodec_decode_video2与后面的avcodec_decode_audio4函数解码:是指从packet中解析出来一帧一帧数据,并不涉及数据格式的转换,如果要进行格式的转换,需要设置重采样方法等等!!
(二)编程实战(YUV视频流转RGB图像)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavutil/log.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h> //ibswscale是一个主要用于处理图片像素数据的类库。可以完成图片像素格式的转换,图片的拉伸等工作
#define WORD uint16_t
#define DWORD uint32_t
#define LONG int32_t
//https://www.cnblogs.com/lzlsky/archive/2012/08/16/2641698.html
typedef struct tagBITMAPFILEHEADER
WORD bfType; //位图类别,根据不同的操作,系统而不同,在Windows中,此字段的值总为‘BM’
DWORD bfSize; //BMP图像文件的大小
WORD bfReserved1; //保留,为0
WORD bfReserved2; //保留,为0
DWORD bfOffBits; //BMP图像数据的地址
BITMAPFILEHEADER, *PBITMAPFILEHEADER;
typedef struct tagBITMAPINFOHEADER
DWORD biSize; //包含的是这个结构体的大小(包括颜色表)
LONG biWidth; //是图片的长
LONG biHeight; //是图片的宽
WORD biPlanes; //是目标绘图设备包含的层数,必须设置为1
WORD biBitCount; //是图像的位数,例如24位,8位等
DWORD biCompression; //压缩方式,0表示不压缩,1表示RLE8压缩,2表示RLE4压缩,3表示每个像素值由指定的掩码决定
DWORD biSizeImage; //BMP图像数据大小,必须是4的倍数,图像数据大小不是4的倍数时用0填充补足
LONG biXPelsPerMeter;//水平分辨率,单位像素/m
LONG biYPelsPerMeter;//垂直分辨率,单位像素/m
DWORD biClrUsed; //BMP图像使用的颜色,0表示使用全部颜色,对于256色位图来说,此值为100h=256
DWORD biClrImportant; //重要的颜色数,此值为0时所有颜色都重要,对于使用调色板的BMP图像来说,当显卡不能够显示所有颜色时,此值将辅助驱动程序显示颜色
BITMAPINFOHEADER, *PBITMAPINFOHEADER;
void saveBMP(struct SwsContext* img_convert_cxt,AVFrame* frame,char* filename)
//1.先进行转换,YUV420=>RGB24
int w = frame->width;
int h = frame->height;
int numBytes = avpicture_get_size(AV_PIX_FMT_RGB24,w,h); //计算字节数量
uint8_t* buffer = (uint8_t*)av_malloc(numBytes*sizeof(uint8_t)); //分配空间
AVFrame* pFrameRGB = av_frame_alloc();
//avpicture_fill函数将ptr指向的数据填充到picture内,但并没有拷贝,只是将picture结构内的data指针指向了ptr的数据。
avpicture_fill((AVPicture*)pFrameRGB,buffer,AV_PIX_FMT_RGB24,w,h);
//真正用来做转换的函数:https://www.cnblogs.com/yongdaimi/p/10715830.html
sws_scale(img_convert_cxt,frame->data,frame->linesize, //当前处理区域的每个通道数据指针,每个通道行字节数
0,h, //参数int srcSliceY, int srcSliceH,定义在输入图像上处理区域,srcSliceY是起始位置,srcSliceH是处理多少行。如果srcSliceY=0,srcSliceH=height,表示一次性处理完整个图像。
pFrameRGB->data,pFrameRGB->linesize); //定义输出图像信息(输出的每个通道数据指针,每个通道行字节数)
//上面将数据转换完成,下面初始化结构体,写入BMP图片数据,到文件中去
//---构造BITMAPINFOHEADER信息首部(第二部分,先有文件头,再有信息头,再之后是数据)
BITMAPINFOHEADER header;
header.biSize = sizeof(BITMAPINFOHEADER);
header.biWidth = w;
/*
如果该值是一个正数,说明Btimap是Bottom up DIB,起始点是左下角,也就是从图像的最下面一行扫描,位图数组中得到的第一行数据实际是图形的最下面的一行。图像是倒向的;
如果该值是一个负数,则说明图像是TopDown DIB,起始点是左上角,图像从最上面一行扫描,图像正向的。
大多数的BMP文件都是倒向的位图,也就是时,高度值是一个正数。(注:当高度值是一个负数时(正向图像),图像将不能被压缩(也就是说biCompression成员将不能是BI_RLE8或BI_RLE4)
*/
header.biHeight = h*(-1); //biHeight字段的正负号指定DIB图像的绘制方向,负数表示为正向,不被压缩
header.biPlanes = 1; //必须为1
header.biBitCount = 24; //RBG位深24
header.biCompression = 0; //不压缩
header.biSizeImage = 0; //其中 biSizeImage 如果不为 0 这代表位图中实际的像素数据字节数;同时如果为0,位图像素数据的字节数也可以通过 biWidth biHeight biBitCount 计算得到。
header.biXPelsPerMeter = 0; //设置分辨率 像素/米
header.biYPelsPerMeter = 0;
header.biClrUsed = 0; //使用全部颜色
header.biClrImportant = 0; //全都重要
//---BITMAPFILEHEADER文件头(第一部分)
BITMAPFILEHEADER bmpFileHeader;
bmpFileHeader.bfType = 0x4d42; //"BM"
bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+numBytes;
bmpFileHeader.bfReserved1 = 0;
bmpFileHeader.bfReserved2 = 0;
bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);
FILE* fp = fopen(filename,"wb");
//由于linux上4字节对齐,而信息头大小为54字节,第一部分14字节,第二部分40字节,所以会将第一部分补齐为16自己,直接用sizeof,打开图片时就会遇到premature end-of-file encountered错误
//下面的方式可以防止对齐,避免对齐导致出错
fwrite(&bmpFileHeader,8,1,fp); //先把bfType、bfSize、bfReserved1写入
fwrite(&bmpFileHeader.bfReserved2,sizeof(bmpFileHeader.bfReserved2),1,fp);
fwrite(&bmpFileHeader.bfOffBits,sizeof(bmpFileHeader.bfOffBits),1,fp);
fwrite(&header,sizeof(BITMAPINFOHEADER),1,fp);
//-----------进行色彩矫正,RGB-->GBR才能变为原本的色彩
for(int i=0;i<numBytes-3;i+=3)
uint8_t temp = pFrameRGB->data[0][i];
pFrameRGB->data[0][i] = pFrameRGB->data[0][i+1];
pFrameRGB->data[0][i+1] = temp;
temp = pFrameRGB->data[0][i+1];
pFrameRGB->data[0][i+1] = pFrameRGB->data[0][i+2];
pFrameRGB->data[0][i+2] = temp;
fwrite(pFrameRGB->data[0],1,numBytes,fp); //对于RGB只需要一个数组,YUV需要3个
fclose(fp);
//释放资源
av_freep(&pFrameRGB[0]);
av_freep(pFrameRGB);
int decode_write_frame(const char* out_filename,AVCodecContext* avctx,struct SwsContext* img_convert_cxt,
AVFrame* frame,int* frame_count,AVPacket* packet,int last)
int len,got_frame;
char buf[1024];
//进行解码操作-----------------
//作用是解码一帧视频数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame。
len = avcodec_decode_video2(avctx,frame,&got_frame,packet); //got_frame该值为0表明没有图像可以解码,否则表明有图像可以解码;
if(len<0)
av_log(NULL,AV_LOG_ERROR,"Fail to decode video frame %d\\n",*frame_count);
return len;
if(got_frame) //一个包中可能有1个或者多个帧,一般视频包中包含1帧;这里我们只获取1帧,进行处理,其他的依旧保留在packet结构体中,后面进行修改
if(last)
av_log(NULL,AV_LOG_INFO,"----Get last frame"); //一般最后一帧可能会做特殊处理
av_log(NULL,AV_LOG_INFO,"Get frame count %3d,Saving frame %3d\\n",got_frame,*frame_count);
snprintf(buf,sizeof(buf),"%s-%d.bmp",out_filename,*frame_count); //图像命名
//保存图像
saveBMP(img_convert_cxt,frame,buf);
(*frame_count)++;
//avcodec_decode_video2只获取了包中的一帧,然而包中可能还有其他帧,所以这里进行处理,进行结构体数据修改
if(packet->data)
packet->size -= len; //大小减去1帧大小
packet->data += len; //数据下移,跳过已经处理的数据
return 0;
void decode_video(char* in_filename,char* out_filename)
int ret,cnt=500; //cnt长视频输出数量
AVFormatContext* fmt_cxt = NULL;
AVCodec* codec = NULL;
AVCodecContext* c = NULL;
struct SwsContext* img_convert_cxt = NULL; //图像处理上下文
int stream_idx;
AVStream* st = NULL;
int frameCnt = 0; //记录解码的帧数量
AVFrame* frame = NULL;
AVPacket packet;
ret = avformat_open_input(&fmt_cxt,in_filename,NULL,NULL);
if(ret<0)
av_log(NULL,AV_LOG_ERROR,"Can`t open input file:%s\\n",in_filename);
return;
ret = avformat_find_stream_info(fmt_cxt,0); //获取输入流的详细信息
if(ret<0)
av_log(NULL,AV_LOG_ERROR,"Could not find stream information\\n");
goto __AVFORMAT;
//媒体文件句柄 / 流类型 / 请求的流编号(-1则自动去找) / 相关流索引号(比如音频对应的视频流索引号),不指定则-1 / 如果非空,则返回所选流的解码器(指针获取) / flag当前未定义
ret = av_find_best_stream(fmt_cxt,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0);
if(ret<0)
av_log(NULL,AV_LOG_ERROR,"Can`t find the best stream\\n");
goto __AVFORMAT;
//获取流并打印流的信息
stream_idx = ret;
st = fmt_cxt->streams[stream_idx];
av_dump_format(fmt_cxt,stream_idx,in_filename,0);
//查找解码器-----------------
codec = avcodec_find_decoder(st->codecpar->codec_id); //根据id查找解码器,id信息存放在输入文件视频流中
if(!codec)
av_log(NULL,AV_LOG_ERROR,"Can`t find the decoder[%s] for input file:%s\\n",av_get_media_type_string(AVMEDIA_TYPE_VIDEO),in_filename);
goto __AVFORMAT;
//打开解码器之前,先分配上下文内存-----------------
c = avcodec_alloc_context3(NULL);
if(!c)
av_log(NULL,AV_LOG_ERROR,"Failed to copy %s codec parameters to decoder context\\n",av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
goto __AVFORMAT;
//将解码器的参数进行设置:将输入流的参数直接拷贝即可-----------------
ret = avcodec_parameters_to_context(c,st->codecpar);
if(ret<0)
av_log(NULL,AV_LOG_ERROR,"Cannot initialize the conversion context\\n");
goto __ACCODEC;
//创建图片转换上下文(在上面参数拷贝后面)-----------------
//源图像宽、高、像素格式;目标图像宽、高、像素格式;以及图像拉伸使用的算法
img_convert_cxt = sws_getContext(c->width,c->height,c->pix_fmt,
c->width,c->height,AV_PIX_FMT_BGR24,
SWS_BICUBIC,NULL,NULL,NULL); //后面为源、目的图像过滤器,和参数
if(img_convert_cxt==NULL)
av_log(NULL,AV_LOG_ERROR,"Can`t open the codec\\n");
goto __ACCODEC;
//打开解码器-----------------
ret = avcodec_open2(c,codec,NULL);
if(ret<0)
av_log(NULL,AV_LOG_ERROR,"Can`t open the codec\\n");
goto __SWSCXT;
frame = av_frame_alloc();
if(!frame)
av_log(NULL,AV_LOG_ERROR,"Can`t alloc the frame for video\\n");
goto __SWSCXT;
//开始读取数据
av_init_packet(&packet); //初始化数据包
while(av_read_frame(fmt_cxt,&packet)>=0&&(--cnt)>=0)
if(packet.stream_index == stream_idx)
if(decode_write_frame(out_filename,c,img_convert_cxt,
frame,&frameCnt,&packet,0)<0)
av_log(NULL,AV_LOG_ERROR,"Failed to decode frame in decode_write_frame\\n");
goto __PACKET;
av_packet_unref(&packet); //注意:减少引用不会释放空间,因为本函数还在引用这个packet结构体;
//所以我们在解码函数中剩余的其他帧数据,还是保留在packet中的,后面根据av_read_frame继续向内部添加数据
//处理packet中剩余的帧
packet.data = NULL;
packet.size = 0;
decode_write_frame(out_filename,c,img_convert_cxt,
frame,&frameCnt,&packet,1);
//下面开始处理在堆上创建的内存空间
__PACKET:
av_frame_free(&frame);
av_packet_unref(&packet); //减少引用,使得自己释放空间
__SWSCXT:
sws_freeContext(img_convert_cxt);
__ACCODEC:
avcodec_free_context(&c);
__AVFORMAT:
avformat_close_input(&fmt_cxt);
return;
int main(int argc,char* argv[])
av_register_all();
av_log_set_level(AV_LOG_DEBUG);
if(argc < 3)
av_log(NULL,AV_LOG_ERROR,"The Count of Parameter must be than 2\\n");
return -1;
decode_video(argv[1],argv[2]);
return 0;
gcc 02_ffmpeg_dec.c -o fd -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice -lswscale
./fd gfxm.mp4 gfxm
二:H264编码处理
(一)编码流程
- avcodec_open2在解码和编码过程不同(在设置参数上):
- 解码:因为输入文件中输入流本身已经设置好了参数,解码时只需要把这些参数拷贝即可,不需要手动设置
- 编码:需要我们手动设置,比如分辨率…
(二)编码实战:(YUV编码为H264)FFmpeg学习(五)H264结构
#include <stdio.h>
#include <libavutil/log.h>
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#define V_WIDTH 640
#define V_HEIGHT 480
AVFormatContext* open_dev()
char* devicename = "/dev/video0"; //设备文件描述符
char errors[1024];
int ret;
AVFormatContext* fmt_ctx=NULL; //格式上下文获取-----av_read_frame获取packet
AVDictionary* options=NULL;
AVInputFormat *iformat=NULL;
AVPacket packet; //包结构
//获取输入(采集)格式
iformat = av_find_input_format("video4linux2"); //驱动,用来录制视频
//设置参数 ffmpeg -f video4linux2 -pixel_format yuyv422 -video_size 640*480 -framerate 15 -i /dev/video0 out.yuv
av_dict_set(&options,"video_size","640*480",0);
av_dict_set(&options,"framerate","30",0);
av_dict_set(&optionsFFMPEG:两个单声道到一个立体声文件,其持续时间是这两个单声道文件中最长的
FFmpegffmpeg 命令查询二 ( 比特流过滤器 | 可用协议 | 过滤器 | 像素格式 | 标准声道布局 | 音频采样格式 | 颜色名称 )