av_seek_frame获取当前Frame

濮阳征
2023-12-01

av_seek_frame对视频进行跳转,但该方法使用还是有些坑说明如下。

I/P/B帧

I frame:自身可以通过视频解压算法解压成一张单独的完整的图片。

P frame:需要参考其前面的一个I/P/B来生成一张完整的图片。

B frame:则要参考其前一个I或者P帧及其后面的一个P帧来生成一张完整的图片。

两个I frame之间形成一个GOP。

flag参数

#define AVSEEK_FLAG_BACKWARD 1 ///< seek backward,若你设置seek时间为1秒,但是只有0秒和2秒上才有I帧,则时间从0秒开始。

#define AVSEEK_FLAG_BYTE 2 ///<seeking based on position in bytes

#define AVSEEK_FLAG_ANY 4 ///< seek toany frame, even non-keyframes,若你设置seek时间为1秒,但是只有0秒和2秒上才有I帧,则时间从2秒开始。

#defineAVSEEK_FLAG_FRAME 8 ///< seeking based on frame number,若你设置seek时间为1秒,但是只有0秒和2秒上才有I帧,则时间从2秒开始。

说明

  • 视频是由一帧一帧图像造成的,所以Frame对应的时间是间断的,不是连续的,与指定的时间戳会有一定范围内的差值。
  • 根据上面的seek_flag可以看出来,一般指定跳转后,都会跳到关键帧上。但关键帧对应的时间未必和指定的时间相同,甚至是差距巨大;之后,就需要通过该关键帧来推断与指定时间接近的Frame。
  • 在推断Frame的时候,需要对其之前的数据都要进行解码,因为当前Frame是根据之前的I/P/B帧来形成完整的Frame;否则就会得到缺失的Frame,解码成图片会失败或者图片不完整。

程序示例

#include <stdio.h>
#include <cmath>
extern "C"
{
#include "libavutil/avutil.h"
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
}

#pragma warning(disable:4996)

static AVFormatContext *formatContext = NULL;
AVStream *inViStream = NULL;
static AVCodecContext *codecCtx = NULL;
static SwsContext *swsContext = NULL;
static char errors[200] = { 0 };
const char *outName = "out/image/img";

#define WORD uint16_t
#define DWORD uint32_t
#define LONG int32_t

#pragma pack(2)
typedef struct tagBITMAPFILEHEADER {
	WORD  bfType;
	DWORD bfSize;
	WORD  bfReserved1;
	WORD  bfReserved2;
	DWORD bfOffBits;
} BITMAPFILEHEADER, *PBITMAPFILEHEADER;


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;

void saveBmp(AVFrame *avFrame, char *imgName)
{
	int w = avFrame->width;
	int h = avFrame->height;
	int size = avpicture_get_size(AV_PIX_FMT_BGR24, w, h);
	uint8_t *buffer = (uint8_t*)av_malloc(size * sizeof(uint8_t));
	AVFrame *frameRgb = av_frame_alloc();
	avpicture_fill((AVPicture*)frameRgb, buffer, AV_PIX_FMT_BGR24, w, h);
	sws_scale(swsContext, avFrame->data, avFrame->linesize, 0, h, frameRgb->data, frameRgb->linesize);

	//2 构造 BITMAPINFOHEADER
	BITMAPINFOHEADER header;
	header.biSize = sizeof(BITMAPINFOHEADER);
	header.biWidth = w;
	header.biHeight = h * (-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 = { 0, };
	//HANDLE hFile = NULL;
	DWORD dwTotalWriten = 0;
	//DWORD dwWriten;

	bmpFileHeader.bfType = 0x4d42; //'BM';
	bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + size;
	bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

	FILE *pFile = fopen(imgName, "wb");
	fwrite(&bmpFileHeader, sizeof(BITMAPFILEHEADER), 1, pFile);
	fwrite(&header, sizeof(BITMAPINFOHEADER), 1, pFile);
	fwrite(frameRgb->data[0], 1, size, pFile);
	fclose(pFile);
	av_freep(&frameRgb);
	av_free(frameRgb);
}

static int imgNum = 0;
static bool bFirst = true;
int decodeWriteFrame(AVPacket *avPacket, AVFrame* avFrame, int *frameCount, int last)
{
	int getFrame = 0;
	char buffer[200] = { 0 };
	//解码
	int len = avcodec_decode_video2(codecCtx, avFrame, &getFrame, avPacket);
	if (len < 0)
	{
		av_strerror(len, errors, 200);
		av_log(NULL, AV_LOG_WARNING, "avcodec_decode_video2 error: ret=%d, msg=%s, frame=%d\n", len, errors, frameCount);
		return len;
	}
	double curTime = av_q2d(inViStream->time_base)*avPacket->pts;
	//保存bmp
	if (getFrame)
	{
		printf("aaaa image count=%d, flag=%d, time=%f\n", *frameCount, avFrame->pict_type, curTime);
		fflush(stdout);
		snprintf(buffer, 200, "%s_%d.bmp", outName, *frameCount);
		saveBmp(avFrame, buffer);
		(*frameCount)++;
	}
	else
	{
		printf("aaaa error image count=%d, flag=%d, time=%f\n", *frameCount, avFrame->pict_type, curTime);
	}
	return 0;
}


int main(int argc, char* argv[])
{
	//
	const char* srcMedia = "media/glass5.mp4";
	float startTime = 0.41f;

	//
	av_log_set_level(AV_LOG_INFO);
	av_register_all();
	//获取输入上下文
	int ret = avformat_open_input(&formatContext, srcMedia, NULL, NULL);
	if (ret != 0)
	{
		av_strerror(ret, errors, 200);
		av_log(NULL, AV_LOG_WARNING, "avformat_open_input error: ret=%d, msg=%s\n", ret, errors);
		return -1;

	}
	ret = avformat_find_stream_info(formatContext, NULL);
	if (ret != 0)
	{
		av_strerror(ret, errors, 200);
		av_log(NULL, AV_LOG_WARNING, "avformat_find_stream_info error: ret=%d, msg=%s\n", ret, errors);
		return -1;
	}
	av_dump_format(formatContext, 0, srcMedia, 0);

	printf("===================================\n");
	int inViIndex = av_find_best_stream(formatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
	inViStream = formatContext->streams[inViIndex];
	double offset = 1.0f/av_q2d(inViStream->avg_frame_rate);
	printf("aaaa rate1=%f, rate2=%f\n", av_q2d(inViStream->avg_frame_rate), av_q2d(inViStream->r_frame_rate));
	//获取输入编解码器
	AVCodec *codec = avcodec_find_decoder(inViStream->codec->codec_id);
	codecCtx = avcodec_alloc_context3(NULL);
	//获取输入编解码器上下文
	ret = avcodec_parameters_to_context(codecCtx, inViStream->codecpar);
	ret = avcodec_open2(codecCtx, codec, NULL);
	//获取sws上下文
	swsContext = sws_getContext(codecCtx->width, codecCtx->height, codecCtx->pix_fmt,
		codecCtx->width, codecCtx->height, AV_PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL);
	AVPacket *avPacket = av_packet_alloc();
	//av_init_packet(&avPacket);
	int frameCount = 1;
	AVFrame *avFrame = av_frame_alloc();
	//ret = av_seek_frame(formatContext, -1, (startTime -0.1)*AV_TIME_BASE, AVSEEK_FLAG_BACKWARD);
	ret = av_seek_frame(formatContext, -1, startTime *AV_TIME_BASE, AVSEEK_FLAG_BACKWARD);
	//获取packet
	bool bFirst = true;
	while (av_read_frame(formatContext, avPacket) >= 0)
	{
		if (avPacket->stream_index == inViIndex)
		{
			double curTime = av_q2d(inViStream->time_base)*avPacket->pts;
			if (curTime > startTime + offset) {
				break;
			}
			if (abs(curTime - startTime) <= offset || bFirst) {
				//获取视频帧
				if (bFirst) {
					bFirst = false;
				}
				decodeWriteFrame(avPacket, avFrame, &frameCount, 0);
			}
		}
		if (avPacket) {
			av_packet_free(&avPacket);
			avPacket = NULL;
		}
		avPacket = av_packet_alloc();
	}
	if (avPacket) {
		av_packet_free(&avPacket);
		avPacket = NULL;
	}
	//
	sws_freeContext(swsContext);
	avcodec_free_context(&codecCtx);
	avformat_close_input(&formatContext);

	return 0;
}

 类似资料: