FFMPEG学习笔记之视频的编解码

FFMPEG视频编解码

1.视频解码流程

1.官方解码示例

流程:

1.查找解码器avcodec_find_decoder(enum AVCodecID)
2.初始化解析器av_parser_init(enum AVCodecID)
3.初始化解码上下文avcodec_alloc_context3(AVCodec*)
4.打开解码上下文avcodec_open2(AVCodecContext*, AVCodec*, NULL)
5.读取文件数据并解析数据,解析的过程就是将文件数据放入AVPacket中av_parser_parse2(AVCodecParserContext *s,
                     AVCodecContext *avctx,
                     uint8_t **poutbuf, int *poutbuf_size,
                     const uint8_t *buf, int buf_size,
                     int64_t pts, int64_t dts,
                     int64_t pos);
6.发送包给解码器avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt)
7.返回解码后的数据avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame)
8.解码后的数据写入文件

代码:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <libavcodec/avcodec.h>

#define INBUF_SIZE 4096

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 void decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt,
	const char *filename)
{
	char buf[1024];
	int ret;

	ret = avcodec_send_packet(dec_ctx, pkt);
	if (ret < 0) {
		fprintf(stderr, "Error sending a packet for decoding\n");
		exit(1);
	}

	while (ret >= 0) {
		ret = avcodec_receive_frame(dec_ctx, frame);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			return;
		else if (ret < 0) {
			fprintf(stderr, "Error during decoding\n");
			exit(1);
		}

		printf("saving frame %3d\n", dec_ctx->frame_number);
		fflush(stdout);

		/* the picture is allocated by the decoder. no need to
		   free it */
		snprintf(buf, sizeof(buf), "%s-%d", filename, dec_ctx->frame_number);
		pgm_save(frame->data[0], frame->linesize[0],
			frame->width, frame->height, buf);
	}
}

int main(int argc, char **argv)
{
	const char *filename, *outfilename;
	const AVCodec *codec;
	AVCodecParserContext *parser;
	AVCodecContext *c = NULL;
	FILE *f;
	AVFrame *frame;
	uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
	uint8_t *data;
	size_t   data_size;
	int ret;
	AVPacket *pkt;

	if (argc <= 2) {
		fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
		exit(0);
	}
	filename = argv[1];
	outfilename = argv[2];

	pkt = av_packet_alloc();
	if (!pkt)
		exit(1);

	/* 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);

	/* find the MPEG-1 video decoder */
	codec = avcodec_find_decoder(AV_CODEC_ID_MPEG1VIDEO);
	if (!codec) {
		fprintf(stderr, "Codec not found\n");
		exit(1);
	}

	parser = av_parser_init(codec->id);
	if (!parser) {
		fprintf(stderr, "parser not found\n");
		exit(1);
	}

	c = avcodec_alloc_context3(codec);
	if (!c) {
		fprintf(stderr, "Could not allocate video codec context\n");
		exit(1);
	}

	/* 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);
	}

	frame = av_frame_alloc();
	if (!frame) {
		fprintf(stderr, "Could not allocate video frame\n");
		exit(1);
	}

	while (!feof(f)) {
		/* read raw data from the input file */
		data_size = fread(inbuf, 1, INBUF_SIZE, f);
		if (!data_size)
			break;

		/* use the parser to split the data into frames */
		data = inbuf;
		while (data_size > 0) {
			ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size,
				data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
			if (ret < 0) {
				fprintf(stderr, "Error while parsing\n");
				exit(1);
			}
			data += ret;
			data_size -= ret;

			if (pkt->size)
				decode(c, frame, pkt, outfilename);
		}
	}

	/* flush the decoder */
	decode(c, frame, NULL, outfilename);

	fclose(f);

	av_parser_close(parser);
	avcodec_free_context(&c);
	av_frame_free(&frame);
	av_packet_free(&pkt);

	return 0;
}

函数详细解析:

  • AVCodec *avcodec_find_decoder(enum AVCodecID id);

    • 函数功能:查找具有匹配的编解码器ID的注册解码器。
    • 参数说明:编码器id,在avcodec.h中声明了,具体参照博客FFMPEG编解码相关数据结构(这里有个疑问,如何判断不同格式的文件对应的编码器id呢,希望有大佬能够指教一下)
    • 相关函数:AVCodec *avcodec_find_decoder_by_name(const char *name);(查找具有指定名称的解码器。)
  • AVCodecParserContext *av_parser_init(int codec_id);

    • 函数功能:根据解码器id来初始化解析器上下文
    • 参数说明:和上面函数的参数一样,是AVCodeID枚举类型
    • 返回值:初始化后的解析器上下文
  • AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);

    • 函数功能:分配AVCodecContext并将其字段设置为默认值。使用avcodec_free_context()释放结构。
    • 参数说明:编码器,如果非空,分配私有数据并初始化默认值;对于给定的编解码器,如果使用不同的解码器调用avcodec_open2()是非法的。
    • 返回值:初始化后的编解码上下文
  • int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

    • 函数功能:初始化AVCodecContext以使用给定的AVCodec。在使用之前

      必须使用avcodec_alloc_context3()分配上下文。

    • 参数说明:avctx----初始化的编码上下文

      ​ codec----指定编解码器

      ​ options----设置一些参数

    • 返回值:成功返回0,否则返回小于0

  • int av_parser_parse2(AVCodecParserContext *s,AVCodecContext *avctx,uint8_t **poutbuf, int *poutbuf_size,const uint8_t *buf, int buf_size,int64_t pts, int64_t dts,
    int64_t pos);

    • 函数功能:解析一个包
    • 参数说明:解析器上下文,编解码上下文,输出数据,输出数据大小,输入数据,输入数据大小,pts,dts,流中位置
    • 返回值:输入比特流中使用过的字节数
  • int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);

    • 函数功能:向编码器发送包
    • 参数说明:编解码上下文,包
    • 返回值:成功返回0,否则返回小于0
  • int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

    • 函数功能:返回解码后的数据,存放在frame数据中
    • 参数说明:编解码上下文,帧数据
    • 返回值:成功返回0,否则返回小于0

2.视频编码流程

1.官方视频编码示例

流程:

1.查找编码器avcodec_find_encoder_by_name()或者avcodec_find_encoder()
2.初始化编码器上下文avcodec_alloc_context3()
3.设置编码器上下文的参数(bit_rate,width,height,time_base,frame_rate,gop_size,max_b_frames,pix_fmt)
4.打开编码器avcodec_open2()
5.分配包空间av_packet_alloc()和分配帧空间av_frame_alloc()
6.设置帧数据的参数,主要是三个(format,width,height)
7.获取帧缓冲av_frame_get_buffer()
8.设置帧为可写av_frame_make_writable()
9.给帧数据赋值并设置pts
10.发送帧数据avcodec_send_frame()
11.接收包数据avcodec_receive_packet()
12.写入文件中

详细代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <libavcodec/avcodec.h>

#include <libavutil/opt.h>
#include <libavutil/imgutils.h>

static void encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt,
                   FILE *outfile)
{
    int ret;

    /* send the frame to the encoder */
    if (frame)
        printf("Send frame %3"PRId64"\n", frame->pts);

    ret = avcodec_send_frame(enc_ctx, frame);
    if (ret < 0) {
        fprintf(stderr, "Error sending a frame for encoding\n");
        exit(1);
    }

    while (ret >= 0) {
        ret = avcodec_receive_packet(enc_ctx, pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
            fprintf(stderr, "Error during encoding\n");
            exit(1);
        }

        printf("Write packet %3"PRId64" (size=%5d)\n", pkt->pts, pkt->size);
        fwrite(pkt->data, 1, pkt->size, outfile);
        av_packet_unref(pkt);
    }
}

int main(int argc, char **argv)
{
    const char *filename, *codec_name;
    const AVCodec *codec;
    AVCodecContext *c= NULL;
    int i, ret, x, y;
    FILE *f;
    AVFrame *frame;
    AVPacket *pkt;
    uint8_t endcode[] = { 0, 0, 1, 0xb7 };

    if (argc <= 2) {
        fprintf(stderr, "Usage: %s <output file> <codec name>\n", argv[0]);
        exit(0);
    }
    filename = argv[1];
    codec_name = argv[2];

    /* find the mpeg1video encoder */
    codec = avcodec_find_encoder_by_name(codec_name);
    if (!codec) {
        fprintf(stderr, "Codec '%s' not found\n", codec_name);
        exit(1);
    }

    c = avcodec_alloc_context3(codec);
    if (!c) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }

    pkt = av_packet_alloc();
    if (!pkt)
        exit(1);

    /* put sample parameters */
    c->bit_rate = 400000;
    /* resolution must be a multiple of two */
    c->width = 352;
    c->height = 288;
    /* frames per second */
    c->time_base = (AVRational){1, 25};
    c->framerate = (AVRational){25, 1};

    /* emit one intra frame every ten frames
     * check frame pict_type before passing frame
     * to encoder, if frame->pict_type is AV_PICTURE_TYPE_I
     * then gop_size is ignored and the output of encoder
     * will always be I frame irrespective to gop_size
     */
    c->gop_size = 10;
    c->max_b_frames = 1;
    c->pix_fmt = AV_PIX_FMT_YUV420P;

    if (codec->id == AV_CODEC_ID_H264)
        av_opt_set(c->priv_data, "preset", "slow", 0);

    /* open it */
    ret = avcodec_open2(c, codec, NULL);
    if (ret < 0) {
        fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret));
        exit(1);
    }

    f = fopen(filename, "wb");
    if (!f) {
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }

    frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }
    frame->format = c->pix_fmt;
    frame->width  = c->width;
    frame->height = c->height;

    ret = av_frame_get_buffer(frame, 32);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate the video frame data\n");
        exit(1);
    }

    /* encode 1 second of video */
    for (i = 0; i < 25; i++) {
        fflush(stdout);

        /* make sure the frame data is writable */
        ret = av_frame_make_writable(frame);
        if (ret < 0)
            exit(1);

        /* prepare a dummy image */
        /* Y */
        for (y = 0; y < c->height; y++) {
            for (x = 0; x < c->width; x++) {
                frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;
            }
        }

        /* Cb and Cr */
        for (y = 0; y < c->height/2; y++) {
            for (x = 0; x < c->width/2; x++) {
                frame->data[1][y * frame->linesize[1] + x] = 128 + y + i * 2;
                frame->data[2][y * frame->linesize[2] + x] = 64 + x + i * 5;
            }
        }

        frame->pts = i;

        /* encode the image */
        encode(c, frame, pkt, f);
    }

    /* flush the encoder */
    encode(c, NULL, pkt, f);

    /* add sequence end code to have a real MPEG file */
    fwrite(endcode, 1, sizeof(endcode), f);
    fclose(f);

    avcodec_free_context(&c);
    av_frame_free(&frame);
    av_packet_free(&pkt);

    return 0;
}

函数详细说明:

因为有部分函数是和上面解码的过程中使用的函数是一样的,所以这里仅仅说明一下几个新的函数。

  • avcodec_find_encoder_by_name():
    • 函数功能:根据传入的编码器名称字符串来查找到对应的编码器
    • 参数:传入的编码器名称
    • 返回值:对应的编码器指针
    • 相关函数为avcodec_find_encoder,功能也是找到对应的编码器,但是传入的是编码器id,返回值相同
  • av_packet_alloc()和av_frame_alloc()
    • 函数功能:分配包/帧的内存空间
    • 参数:无
    • 返回值:包/帧对象指针
  • av_frame_get_buffer():
    • 函数功能:分配AVFrame的data缓冲区
  • av_frame_make_writable():
    • 函数功能:将帧设置为可写
    • 参数:需要设置的帧
    • 返回值:设置成功返回0.否则返回小于0
  • avcodec_send_frame():
    • 函数功能:将帧数据发送到编码器中
    • 参数说明:编码器上下文,帧
    • 返回值:成功返回0,否则返回小于0
  • avcodec_receive_packet():
    • 函数功能:包数据接收编码后的数据
    • 参数说明:编码器上下文,包
    • 返回值:成功返回0,否则返回小于0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值