从零到一学FFmpeg:AVCodecContext 结构体详析与实战

文章目录


前言

AVCodecContext是FFmpeg库中的一个核心结构体,它代表了编解码器的上下文信息,是进行音频或视频编解码操作时必不可少的组件。

AVCodecContext存储了编解码器的配置参数、状态信息以及与编解码过程相关的资源。

无论是在编码还是解码过程中,都需要一个相应的AVCodecContext实例来管理和控制编解码过程。


提示:以下是本篇文章正文内容,下面案例可供参考

一、结构体原型

bash 复制代码
typedef struct AVCodecContext {
    const AVClass *av_class;
    AVMediaType codec_type; //编解码器处理的媒体类型,如AVMEDIA_TYPE_VIDEO或AVMEDIA_TYPE_AUDIO
    const AVCodec *codec; // 指向当前编解码器的指针
    AVCodecID codec_id; // 编解码器的ID,例如CODEC_ID_H264
    int width, height; // 视频的情况下,宽和高
    AVRational time_base; // 时间基,用于计算时间戳
    int bit_rate; // 编码时的目标比特率
    /* 更多编解码相关的参数... */
    AVFrame *frame; // 可选的,用于存储原始数据的帧
    AVFrame *coded_frame; // 编码后的帧,解码时使用
    /* 更多功能性、控制和状态字段... */
} AVCodecContext;

参数说明

*const AVClass av_class:
同AVFormatContext中的av_class,用于日志记录和运行时类型信息。

AVMediaType codec_type:
指定编解码器处理的媒体类型,如音频或视频。

*const AVCodec codec:
指向已初始化的编解码器结构体,这个编解码器用于执行实际的编解码操作。

AVCodecID codec_id:
编解码器的唯一标识符,例如对于H.264视频编码器,其值为AV_CODEC_ID_H264。

int width, height:
视频编解码时,指定帧的宽度和高度。

AVRational time_base:
定义时间戳的单位,例如1/1000表示每毫秒一个时间单位。

int bit_rate:
编码时的目标比特率,单位通常是比特每秒。

**AVFrame frame, coded_frame:
frame通常用于存放原始数据,coded_frame用于存放编码或解码后的数据。在编码时,原始数据放入frame,编码后数据转移到coded_frame;解码时相反。

返回值

如果ts_a小于ts_b,返回负数。
如果ts_a等于ts_b,返回0。
如果ts_a大于ts_b,返回正数

二、使用场景

1、编码流程:

1、查找并分配编解码器 :使用avcodec_find_encoder(codec_id)查找编解码器,然后通过avcodec_alloc_context3(codec)分配并初始化AVCodecContext。
2、配置编解码器上下文 :设置AVCodecContext的参数,如分辨率、比特率、时间基等。
3、打开编解码器 :调用avcodec_open2(context, codec, NULL)来初始化编解码器。
4、编码循环:准备数据帧,调用avcodec_send_frame送入编码器,再通过avcodec_receive_packet获取编码后的数据包。

2、解码流程:

1、查找并分配解码器 :与编码流程相似,使用avcodec_find_decoder(codec_id)和avcodec_alloc_context3(codec)。
2、配置解码器上下文 :根据输入流的参数配置AVCodecContext。
3、打开解码器 :调用avcodec_open2。
4、解码循环:接收数据包通过avcodec_send_packet送入解码器,然后使用avcodec_receive_frame获取解码后的帧。

三、编码使用实例

这个例子将展示如何使用AVCodecContext和其他相关结构体来编码一帧原始图像数据为H.264视频流。

1、首先,我们初始化FFmpeg库,找到H.264编码器,并为编码器创建和配置AVCodecContext。

2、接着,我们创建输出文件的AVFormatContext,添加一个视频流,并打开输出文件。

3、之后,我们为一帧原始图像数据创建AVFrame结构体,并简单地填充一些测试数据。

4、利用avcodec_encode_video2()函数尝试编码这一帧数据,得到的编码数据包通过av_interleaved_write_frame()写入输出文件。

5、最后,我们完成编码后清理资源,关闭编码器和输出文件。

c 复制代码
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>

int main() {
    const char *out_filename = "output.h264";
    const AVCodecID codec_id = AV_CODEC_ID_H264;

    // 初始化FFmpeg库
    av_register_all();
    avcodec_register_all();

    // 找到编码器
    AVCodec *codec = avcodec_find_encoder(codec_id);
    if (!codec) {
        printf("Codec not found\n");
        return -1;
    }

    // 创建并初始化编码上下文
    AVCodecContext *c = avcodec_alloc_context3(codec);
    if (!c) {
        printf("Could not allocate video codec context\n");
        return -1;
    }

    // 设置编码参数
    c->bit_rate = 400000; // 比特率
    c->width = 640;       // 宽度
    c->height = 480;      // 高度
    c->time_base = (AVRational){1, 25}; // 25帧每秒
    c->gop_size = 12;     // I帧间隔
    c->max_b_frames = 1;  // 最大B帧数
    c->pix_fmt = AV_PIX_FMT_YUV420P; // 像素格式

    // 打开编码器
    if (avcodec_open2(c, codec, NULL) < 0) {
        printf("Could not open codec\n");
        return -1;
    }

    // 创建输出文件及格式上下文
    AVFormatContext *fmt_ctx = NULL;
    if (avformat_alloc_output_context2(&fmt_ctx, NULL, NULL, out_filename) < 0) {
        printf("Could not create output context\n");
        return -1;
    }

    // 添加视频流到输出上下文
    AVStream *stream = avformat_new_stream(fmt_ctx, codec);
    if (!stream) {
        printf("Could not create new stream\n");
        return -1;
    }
    avcodec_parameters_from_context(stream->codecpar, c);

    // 打开输出文件
    if (avio_open(&fmt_ctx->pb, out_filename, AVIO_FLAG_WRITE) < 0) {
        printf("Could not open output file\n");
        return -1;
    }

    // 写头部信息
    avformat_write_header(fmt_ctx, NULL);

    // 准备一帧原始图像数据(这里简化处理,实际应用中可能来自摄像头、文件等)
    AVFrame *frame = av_frame_alloc();
    frame->format = c->pix_fmt;
    frame->width = c->width;
    frame->height = c->height;
    av_frame_get_buffer(frame, 0);

    // 填充帧数据(此处仅为演示,实际应用中应根据真实图像数据填充)
    uint8_t *image_data = frame->data[0];
    size_t image_size = c->width * c->height;
    memset(image_data, 128, image_size); // 简单地填充灰阶数据

    // 编码循环(简化版,实际应用中需要循环处理多帧)
    AVPacket pkt = {0};
    av_init_packet(&pkt);
    pkt.data = NULL; // packet data will be allocated by the encoder
    pkt.size = 0;

    // 将帧送入编码器
    int got_packet = 0;
    if (avcodec_encode_video2(c, &pkt, frame, &got_packet) < 0) {
        printf("Error encoding frame\n");
        return -1;
    }

    // 如果有编码好的数据包,则写入输出文件
    if (got_packet) {
        av_packet_rescale_ts(&pkt, c->time_base, stream->time_base);
        pkt.stream_index = stream->index;
        if (av_interleaved_write_frame(fmt_ctx, &pkt) < 0) {
            printf("Error while writing output packet\n");
        }
    }

    // 写尾部信息并清理
    av_write_trailer(fmt_ctx);
    avcodec_close(c);
    avformat_free_context(fmt_ctx);
    av_frame_free(&frame);

    return 0;
}

四、解码使用实例

这个例子将会使用FFmpeg库来打开一个视频文件,找到解码器,初始化AVCodecContext,并进行基本的解码流程。

1、首先,通过av_register_all()和avformat_network_init()注册FFmpeg的所有组件并初始化网络支持。

2、使用avformat_open_input()打开视频文件,并通过avformat_find_stream_info()获取流信息。

3、查找视频流,并根据视频流的编解码器ID查找解码器。

4、为找到的解码器创建AVCodecContext实例,并使用avcodec_parameters_to_context()从流的AVCodecParameters复制参数到解码器上下文中。

5、通过avcodec_open2()打开解码器,准备进行解码操作。

c 复制代码
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>

int main(int argc, char **argv) {
    const char *filename = "example.mp4"; // 待解码的视频文件名
    if (argc > 1) filename = argv[1];

    av_register_all(); // 注册所有组件
    avformat_network_init(); // 初始化网络组件,如果视频来源于网络
    
    AVFormatContext *fmt_ctx = NULL;
    if (avformat_open_input(&fmt_ctx, filename, NULL, NULL) != 0) {
        printf("Could not open input file.\n");
        return -1;
    }

    if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
        printf("Failed to retrieve stream information.\n");
        return -1;
    }
    
    AVCodecContext *dec_ctx = NULL;
    AVCodec *dec = NULL;
    int video_stream_index = -1;
    for (int i = 0; i < fmt_ctx->nb_streams; i++) {
        if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_index = i;
            break;
        }
    }
    if (video_stream_index == -1) {
        printf("Didn't find a video stream.\n");
        return -1;
    }

    dec = avcodec_find_decoder(fmt_ctx->streams[video_stream_index]->codecpar->codec_id);
    if (!dec) {
        printf("Decoder not found.\n");
        return -1;
    }
    
    dec_ctx = avcodec_alloc_context3(dec);
    if (avcodec_parameters_to_context(dec_ctx, fmt_ctx->streams[video_stream_index]->codecpar) < 0) {
        printf("Failed to copy codec parameters to decoder context.\n");
        return -1;
    }
    
    if (avcodec_open2(dec_ctx, dec, NULL) < 0) {
        printf("Failed to open codec.\n");
        return -1;
    }
    
    // 这里开始解码循环,实际应用中你需要处理AVPacket和AVFrame
    // 由于解码循环较为复杂,这里省略以保持示例简洁

    // 清理
    avcodec_free_context(&dec_ctx);
    avformat_close_input(&fmt_ctx);
    
    return 0;
}
相关推荐
我要成为C++领域大神14 分钟前
【高性能服务器】select模型
linux·服务器·c语言·开发语言·网络·tcp·io多路复用
bitcsljl33 分钟前
Linux系统中卸载GitLab
linux·运维·gitlab
oDrake1 小时前
Openstack制作Rhel9,使用IOS镜像制作
linux·openstack·虚拟化·rhel-9.3
辣香牛肉面1 小时前
Linux下“/proc”目录的作用
java·linux·服务器
千殃sama1 小时前
Linux高并发服务器开发(十一)UDP通信和本地socket通信
linux·服务器·网络·笔记·学习·udp
Danileaf_Guo1 小时前
CentOS 7停服之后该怎么安装软件呢?
linux·运维·服务器·centos
小宏运维有点菜2 小时前
Prometheus
linux·运维·prometheus·监控
张文君2 小时前
树莓派根目录满了可以使用外部存储吗
linux·ubuntu
m0_747124532 小时前
将QT移植到IMX6ULL开发板
linux·单片机·qt·imx6ull
起个别名2 小时前
必须掌握的Linux的九大命令
linux·服务器·网络