精选文章 ffmpeg:视频逐帧保存图片

ffmpeg:视频逐帧保存图片

作者:HFUTWXY 时间: 2019-11-06 11:31:23
HFUTWXY 2019-11-06 11:31:23

一、通用操作

打开视频文件、找到编码器,然后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)

勿删,copyright占位
分享文章到微博
分享文章到朋友圈

上一篇:[学习微服务-第3天] ServiceComb内置高性能网关服务--带图

下一篇:@JsonFormat与@DateTimeFormat注解的使用

CSDN

CSDN

中国开发者社区CSDN (Chinese Software Developer Network) 创立于1999年,致力为中国开发者提供知识传播、在线学习、职业发展等全生命周期服务。