一、通用操作
打开视频文件、找到编码器,然后while循环读取每一帧,使用完后释放资源
av_register_all();
AVFormatContext *av_format_context = avformat_alloc_context();
int open_input_result = avformat_open_input(&av_format_context, "C:/Users/Public/Videos/Sample Videos/xxx.mp4", nullptr, nullptr);
if (open_input_result != 0) {
return 0;
}
int find_stream_result = avformat_find_stream_info(av_format_context, nullptr);
if (find_stream_result < 0) {
return 0;
}
//1. find video stream
int av_stream_index = -1;
for (int i = 0; i < av_format_context->nb_streams; ++i) {
if (av_format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
av_stream_index = i;
break;
}
}
//2.get codec_context by video stream
if (av_stream_index == -1) {
return 0;
}
AVCodecContext *av_codec_context = av_format_context->streams[av_stream_index]->codec;
//find codec
AVCodec *av_codec = avcodec_find_decoder(av_codec_context->codec_id);
//open
int av_open_codec_result = avcodec_open2(av_codec_context, av_codec, nullptr);
if (av_open_codec_result != 0) {
return 0;
}
AVPacket *packet = av_packet_alloc();
AVFrame *av_frame_in = av_frame_alloc();
int decode_result = 0;
int frame_count = 0;
while (av_read_frame(av_format_context, packet) >= 0)
{
if (packet->stream_index == av_stream_index) {
int frame_finished = 0;
avcodec_decode_video2(av_codec_context, av_frame_in, &frame_finished, packet);
if (frame_finished)
{
char buf[512];
snprintf(buf, sizeof(buf), "%stemp-%d.bmp", "C:/Users/Public/Videos/Sample Videos/images/", ++frame_count);
saveBMP(img_convert_ctx, av_frame_in, buf);
}
}
av_packet_unref(packet);
}
av_packet_free(&packet);
av_frame_free(&av_frame_in);
avcodec_close(av_codec_context);
avformat_close_input(&av_format_context);
二、创建一个图片sws上下文,这里使用的是AV_PIX_FMT_BGR24
AVCodecContext *out_context = avcodec_alloc_context3(nullptr);
// 将视频流的编解码参数直接拷贝到输出上下文环境中
if ((avcodec_parameters_to_context(out_context, av_format_context->streams[av_stream_index]->codecpar)) < 0) {
fprintf(stderr, "Failed to copy %s codec parameters to decoder context\n",
av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
return -1;
}
SwsContext *img_convert_ctx = sws_getContext(out_context->width, out_context->height,
out_context->pix_fmt,
out_context->width, out_context->height,
AV_PIX_FMT_BGR24,
SWS_BICUBIC, nullptr, nullptr, nullptr);
三、到目前为止都很简单,下面才开始真正的把AVFrame 转成图片并且保存下来
封装了一个方法saveBMP(),第一个参数是图片context,第二个参数是图片数据,第三个参数是图片保存路径
//1 先进行转换, YUV420=>RGB24:
sws_scale(img_convert_ctx, frame->data, frame->linesize, 0, height, frame_rgb->data, frame_rgb->linesize);
//2 构造 BITMAPINFOHEADER
BITMAPINFOHEADER header;
header.biSize = sizeof(BITMAPINFOHEADER);
header.biWidth = width;
header.biHeight = height * -1;
header.biBitCount = 24;
header.biCompression = 0;
header.biSizeImage = 0;
header.biClrImportant = 0;
header.biClrUsed = 0;
header.biXPelsPerMeter = 0;
header.biYPelsPerMeter = 0;
header.biPlanes = 1;
//3 构造文件头
BITMAPFILEHEADER bmpFileHeader;
//HANDLE hFile = NULL;
DWORD dwTotalWriten = 0;
DWORD dwWriten;
bmpFileHeader.bfType = 0x4d42; //'BMP';
bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + num_bytes;
bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
FILE* pf = fopen(filename, "wb");
fwrite(&bmpFileHeader, sizeof(BITMAPFILEHEADER), 1, pf);
fwrite(&header, sizeof(BITMAPINFOHEADER), 1, pf);
fwrite(frame_rgb->data[0], 1, num_bytes, pf);
fclose(pf);
//释放资源
av_freep(&frame_rgb[0]);
av_free(frame_rgb);
这里涉及到bmp文件的格式
这是从网上copy的一段话:一个bmp图片最多由4大部分组成:BITMAPFILEHEADER结构体,BITMAPINFOHEADER结构体,RGBQUAD结构体(这个结构体可以有,也可以没有),DIB数据区。其中DIB意思就是Device-Independent Bitmap(设备无关位图)
所以我们定义两个结构体:
BITMAPFILEHEADER的第1个属性是bfType(2字节),这里恒定等于0x4D42。由于内存中的数据排列高位在左,低位在右,所以内存中从左往右看就显示成(42 4D),所以在winhex中头两个 字节显示为(42 4D)就是这样形成的,以后的数据都是这个特点,不再作重复说明。
// 位图文件头(bitmap-file header)包含了图像类型、图像大小、图像数据存放地址和两个保留未使用的字段
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1 = 0;
WORD bfReserved2 = 0;
DWORD bfOffBits;
} BITMAPFILEHEADER, *PBITMAPFILEHEADER;
// 位图信息头(bitmap-information header)包含了位图信息头的大小、图像的宽高、图像的色深、压缩说明图像数据的大小和其他一些参数。
typedef struct tagBITMAPINFOHEADER {
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
四、最后bmp文件在解析的时候,会先读取头信息,在读取数据信息。所以我们再写bmp文件时,设置一下字节对齐为2
#pragma pack(2)