参考链接
官网
https://ffmpeg.org/doxygen/3.4/decode_video_8c-example.html
https://blog.csdn.net/u014162133/article/details/81131339
核心流程及API
//处理输入
/* register all formats and codecs */
av_register_all();
/* open input file, and allocate format context */
avformat_open_input
/* retrieve stream information */
avformat_find_stream_info
av_init_packet
av_find_best_stream
//处理输出
/* find decoder for the stream */
avcodec_find_decoder
// allocate video codec context ( avcodeccontext)
avcodec_alloc_context3
/* Copy codec parameters from input stream to output codec context */
avcodec_parameters_to_context
/* open it */
avcodec_open2
//Allocate and return an SwsContext to picture scaling or conversion
sws_getContext
av_frame_alloc()
av_read_frame
//自定义函数 解码并输出bmp图片
decode_write_frame
{
//输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame
avcodec_decode_video2
//把图片名称存放在buf中
snprintf
//自定义函数,将yuv的帧转化为RGB并输出
saveBMP
{
//计算这个格式的图片,需要多少字节来存储
avpicture_get_size
//申请空间来存放图片数据。包含源数据和目标数据
av_malloc
//为已经分配的空间的结构体AVPicture挂上一段用于保存数据的空间
avpicture_fill
//将yuv转化为RGB
sws_scale
构造BMP文件
// 构造 BITMAPINFOHEADER位图信息头
// 构造文件头
//打开输出文件
//将头和RGB帧写入
}
}
decode_video.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#define INBUF_SIZE 4096
#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(struct SwsContext *img_convert_ctx, AVFrame *frame, char *filename)
{
//1 先进行转换, YUV420=>RGB24:
int w = frame->width;
int h = frame->height;
//alloc buf to avframe struct
AVFrame *pFrameRGB = av_frame_alloc();
//计算这个格式的图片,需要多少字节来存储
int numBytes=avpicture_get_size(AV_PIX_FMT_BGR24, w, h);
//申请空间来存放图片数据。包含源数据和目标数据
uint8_t *buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
/* buffer is going to be written to rawvideo file, no alignment */
/*
if (av_image_alloc(pFrameRGB->data, pFrameRGB->linesize,
w, h, AV_PIX_FMT_BGR24, pix_fmt, 1) < 0) {
fprintf(stderr, "Could not allocate destination image\n");
exit(1);
}
*/
//前面的av_frame_alloc函数,只是为这个AVFrame结构体分配了内存,而该类型的指针指向的内存还没分配
//把av_malloc得到的内存和AVFrame关联起来
avpicture_fill((AVPicture *)pFrameRGB, buffer, AV_PIX_FMT_BGR24, w, h);
// FFmpeg定义了一个结构体SwsContext,它记录进行图像格式转换时,源图像和目标图像的格式、大小分别是什么。然后用sws_scale函数直接转换即可。
sws_scale(img_convert_ctx, frame->data, frame->linesize,
0, h, pFrameRGB->data, pFrameRGB->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)+ numBytes;
bmpFileHeader.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);
FILE* pf = fopen(filename, "wb");
fwrite(&bmpFileHeader, sizeof(BITMAPFILEHEADER), 1, pf);
fwrite(&header, sizeof(BITMAPINFOHEADER), 1, pf);
fwrite(pFrameRGB->data[0], 1, numBytes, pf);
fclose(pf);
//释放资源
//av_free(buffer);
av_freep(&pFrameRGB[0]);
av_free(pFrameRGB);
}
//this function not use
static void pgm_save(unsigned char *buf, int wrap, int xsize, int ysize,
char *filename)
{
FILE *f;
int i;
f = fopen(filename,"w");
fprintf(f, "P5\n%d %d\n%d\n", xsize, ysize, 255);
for (i = 0; i < ysize; i++)
fwrite(buf + i * wrap, 1, xsize, f);
fclose(f);
}
static int decode_write_frame(const char *outfilename, AVCodecContext *avctx,
struct SwsContext *img_convert_ctx, AVFrame *frame, int *frame_count, AVPacket *pkt, int last)
{
int len, got_frame;
char buf[1024];
//输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame
len = avcodec_decode_video2(avctx, frame, &got_frame, pkt);
if (len < 0) {
fprintf(stderr, "Error while decoding frame %d\n", *frame_count);
return len;
}
if (got_frame) {
printf("Saving %sframe %3d\n", last ? "last " : "", *frame_count);
//冲刷缓存区
fflush(stdout);
//snprintf将format格式化成字符串,并将字符串复制到 buf 中
/* the picture is allocated by the decoder, no need to free it */
snprintf(buf, sizeof(buf), "%s-%d.bmp", outfilename, *frame_count);
/*
pgm_save(frame->data[0], frame->linesize[0],
frame->width, frame->height, buf);
*/
saveBMP(img_convert_ctx, frame, buf);
(*frame_count)++;
}
if (pkt->data) {
pkt->size -= len;
pkt->data += len;
}
return 0;
}
int main(int argc, char **argv)
{
int ret;
FILE *f;
const char *filename, *outfilename;
AVFormatContext *fmt_ctx = NULL;
const AVCodec *codec;
AVCodecContext *c= NULL;
AVStream *st = NULL;
int stream_index;
int frame_count;
AVFrame *frame;
struct SwsContext *img_convert_ctx;
//uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
AVPacket avpkt;
if (argc <= 2) {
fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
exit(0);
}
filename = argv[1];
outfilename = argv[2];
/* register all formats and codecs */
av_register_all();
/* open input file, and allocate format context */
if (avformat_open_input(&fmt_ctx, filename, NULL, NULL) < 0) {
fprintf(stderr, "Could not open source file %s\n", filename);
exit(1);
}
/* retrieve stream information */
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
fprintf(stderr, "Could not find stream information\n");
exit(1);
}
/* dump input information to stderr */
av_dump_format(fmt_ctx, 0, filename, 0);
av_init_packet(&avpkt);
/* set end of buffer to 0 (this ensures that no overreading happens for damaged MPEG streams) */
//memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);
//
ret = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (ret < 0) {
fprintf(stderr, "Could not find %s stream in input file '%s'\n",
av_get_media_type_string(AVMEDIA_TYPE_VIDEO), filename);
return ret;
}
stream_index = ret;
//put the best stream to a avstream struct
st = fmt_ctx->streams[stream_index];
/* find decoder for the stream */
codec = avcodec_find_decoder(st->codecpar->codec_id);
if (!codec)
{
fprintf(stderr, "Failed to find %s codec\n",av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
return AVERROR(EINVAL);
}
/* find the MPEG-1 video decoder */
/*
codec = avcodec_find_decoder(AV_CODEC_ID_MPEG1VIDEO);
if (!codec) {
fprintf(stderr, "Codec not found\n");
exit(1);
}
*/
c = avcodec_alloc_context3(NULL);
if (!c) {
fprintf(stderr, "Could not allocate video codec context\n");
exit(1);
}
/* Copy codec parameters from input stream to output codec context */
if ((ret = avcodec_parameters_to_context(c, st->codecpar)) < 0)
{
fprintf(stderr, "Failed to copy %s codec parameters to decoder context\n",
av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
return ret;
}
/*
if (codec->capabilities & AV_CODEC_CAP_TRUNCATED)
c->flags |= AV_CODEC_FLAG_TRUNCATED; // we do not send complete frames
*/
/* For some codecs, such as msmpeg4 and mpeg4, width and height
MUST be initialized there because this information is not
available in the bitstream. */
/* open it */
if (avcodec_open2(c, codec, NULL) < 0)
{
fprintf(stderr, "Could not open codec\n");
exit(1);
}
/*
f = fopen(filename, "rb");
if (!f) {
fprintf(stderr, "Could not open %s\n", filename);
exit(1);
}
*/
//设置转换源的大小、格式和转换目标的大小、格式
//Allocate and return an SwsContext to picture scaling or conversion
img_convert_ctx = sws_getContext(c->width, c->height,
c->pix_fmt,
c->width, c->height,
AV_PIX_FMT_RGB24,
SWS_BICUBIC, NULL, NULL, NULL);
if (img_convert_ctx == NULL)
{
fprintf(stderr, "Cannot initialize the conversion context\n");
exit(1);
}
frame = av_frame_alloc();
if (!frame) {
fprintf(stderr, "Could not allocate video frame\n");
exit(1);
}
frame_count = 0;
while (av_read_frame(fmt_ctx, &avpkt) >= 0) {
/*
avpkt.size = fread(inbuf, 1, INBUF_SIZE, f);
if (avpkt.size == 0)
break;
*/
/* NOTE1: some codecs are stream based (mpegvideo, mpegaudio)
and this is the only method to use them because you cannot
know the compressed data size before analysing it.
BUT some other codecs (msmpeg4, mpeg4) are inherently frame
based, so you must call them with all the data for one
frame exactly. You must also initialize 'width' and
'height' before initializing them. */
/* NOTE2: some codecs allow the raw parameters (frame size,
sample rate) to be changed at any frame. We handle this, so
you should also take care of it */
/* here, we use a stream based decoder (mpeg1video), so we
feed decoder and see if it could decode a frame */
//avpkt.data = inbuf;
//while (avpkt.size > 0)
if(avpkt.stream_index == stream_index)
{
if (decode_write_frame(outfilename, c, img_convert_ctx, frame, &frame_count, &avpkt, 0) < 0)
exit(1);
}
av_packet_unref(&avpkt);
}
/* Some codecs, such as MPEG, transmit the I- and P-frame with a
latency of one frame. You must do the following to have a
chance to get the last frame of the video. */
avpkt.data = NULL;
avpkt.size = 0;
decode_write_frame(outfilename, c, img_convert_ctx, frame, &frame_count, &avpkt, 1);
fclose(f);
avformat_close_input(&fmt_ctx);
sws_freeContext(img_convert_ctx);
avcodec_free_context(&c);
av_frame_free(&frame);
return 0;
}
视频分解为图片并保存在当前路径下
./decode_video test.mp4 ./
核心结构体使用流程