av_seek_frame对视频进行跳转,但该方法使用还是有些坑说明如下。
I frame:自身可以通过视频解压算法解压成一张单独的完整的图片。
P frame:需要参考其前面的一个I/P/B来生成一张完整的图片。
B frame:则要参考其前一个I或者P帧及其后面的一个P帧来生成一张完整的图片。
两个I frame之间形成一个GOP。
#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;
}