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