从零到一学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;
}
相关推荐
逢生博客42 分钟前
Ubuntu 安装 gvm 管理 Go 语言开发环境
linux·ubuntu·golang·gvm
多吃蔬菜!!!2 小时前
vscode 搭建C/C++开发环境搭建(linux)
linux·c语言·c++
李李李li2 小时前
Ubuntu 22.04 安装tensorrt
linux·tensorrt
phoenix09814 小时前
Linux入门DAY29
linux·运维
入秋4 小时前
Linux服务器安装部署 Nginx、Redis、PostgreSQL、Docker
linux·前端
Mr. Cao code5 小时前
使用Tomcat Clustering和Redis Session Manager实现Session共享
java·linux·运维·redis·缓存·tomcat
zcz16071278215 小时前
Linux 网络命令大全
linux·运维·网络
the sun345 小时前
Reactor设计模式及其在epoll中的应用
linux·运维·服务器·c++
喜欢你,还有大家5 小时前
Linux笔记7——shell编程基础-1
linux·运维·笔记