
#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;
}