ffmpeg源码分析(三)结构体之AVFormatContext

AVFormatContext是API中直接接触到的结构体,位于avformat.h中,结构体描述了一个多媒体文件或流的构成和基本信息。,是FFmpeg中最为基本的一个结构体。贯穿了ffmpeg使用的整个流程。

可以说是ffmpeg中最顶端的一个结构体。

头文件

github.com

举个例子(转封装格式,doc/examples/remux.c)

int main(int argc, char **argv)
{
    const AVOutputFormat *ofmt = NULL;
    AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
    AVPacket *pkt = NULL;
    const char *in_filename, *out_filename;
    int ret, i;
    int stream_index = 0;
    int *stream_mapping = NULL;
    int stream_mapping_size = 0;

    // 检查命令行参数是否足够
    if (argc < 3) {
        printf("usage: %s input output\n"
               "API example program to remux a media file with libavformat and libavcodec.\n"
               "The output format is guessed according to the file extension.\n"
               "\n", argv[0]);
        return 1;
    }

    // 获取输入和输出文件名
    in_filename  = argv[1];
    out_filename = argv[2];

    // 分配一个AVPacket结构体
    pkt = av_packet_alloc();
    if (!pkt) {
        fprintf(stderr, "Could not allocate AVPacket\n");
        return 1;
    }

    // 打开输入文件并读取其头信息
    if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
        fprintf(stderr, "Could not open input file '%s'", in_filename);
        goto end;
    }

    // 查找流信息,此时可能会预解析一些数据
    if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
        fprintf(stderr, "Failed to retrieve input stream information");
        goto end;
    }

    // 打印输入文件的格式信息
    av_dump_format(ifmt_ctx, 0, in_filename, 0);

    // 分配输出格式上下文
    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
    if (!ofmt_ctx) {
        fprintf(stderr, "Could not create output context\n");
        ret = AVERROR_UNKNOWN;
        goto end;
    }

    // 定义流映射数组
    stream_mapping_size = ifmt_ctx->nb_streams;
    stream_mapping = av_calloc(stream_mapping_size, sizeof(*stream_mapping));
    if (!stream_mapping) {
        ret = AVERROR(ENOMEM);
        goto end;
    }

    ofmt = ofmt_ctx->oformat;

    // 为每个输入流创建相应的输出流
    for (i = 0; i < ifmt_ctx->nb_streams; i++) {
        AVStream *out_stream;
        AVStream *in_stream = ifmt_ctx->streams[i];
        AVCodecParameters *in_codecpar = in_stream->codecpar;

        // 仅处理音频、视频和字幕流
        if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
            in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
            in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
            stream_mapping[i] = -1;
            continue;
        }

        // 更新流映射,保证在输出流中,是顺序递增的。
        stream_mapping[i] = stream_index++;

        // 创建输出流
        out_stream = avformat_new_stream(ofmt_ctx, NULL);
        if (!out_stream) {
            fprintf(stderr, "Failed allocating output stream\n");
            ret = AVERROR_UNKNOWN;
            goto end;
        }

        // 复制编解码器参数
        ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
        if (ret < 0) {
            fprintf(stderr, "Failed to copy codec parameters\n");
            goto end;
        }

        out_stream->codecpar->codec_tag = 0;
    }

    // 打印输出文件的格式信息
    av_dump_format(ofmt_ctx, 0, out_filename, 1);

    // 打开文件
    if (!(ofmt->flags & AVFMT_NOFILE)) {
        ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
        if (ret < 0) {
            fprintf(stderr, "Could not open output file '%s'", out_filename);
            goto end;
        }
    }
    //写入封装格式头
    ret = avformat_write_header(ofmt_ctx, NULL);
    if (ret < 0) {
        fprintf(stderr, "Error occurred when opening output file\n");
        goto end;
    }

    // 拷贝数据包
    while (1) {
        AVStream *in_stream, *out_stream;

        // 从输入文件读取一帧数据
        ret = av_read_frame(ifmt_ctx, pkt);
        if (ret < 0)
            break;

        in_stream  = ifmt_ctx->streams[pkt->stream_index];

        // 检查stream_mapping
        if (pkt->stream_index >= stream_mapping_size ||
            stream_mapping[pkt->stream_index] < 0) {
            av_packet_unref(pkt);
            continue;
        }

        // 更新packet的流索引,并重新缩放时间戳
        pkt->stream_index = stream_mapping[pkt->stream_index];
        out_stream = ofmt_ctx->streams[pkt->stream_index];
        log_packet(ifmt_ctx, pkt, "in");
        
        /* 重新缩放时间戳 */
        av_packet_rescale_ts(pkt, in_stream->time_base, out_stream->time_base);
        pkt->pos = -1;
        log_packet(ofmt_ctx, pkt, "out");

        // 将数据包写入输出文件
        ret = av_interleaved_write_frame(ofmt_ctx, pkt);
        if (ret < 0) {
            fprintf(stderr, "Error muxing packet\n");
            break;
        }

        // 释放数据包引用
        av_packet_unref(pkt);
    }

    // 写入输出文件的尾
    av_write_trailer(ofmt_ctx);

end:
    // 释放资源
    av_packet_free(&pkt);
    avformat_close_input(&ifmt_ctx);

    if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
        avio_closep(&ofmt_ctx->pb);

    avformat_free_context(ofmt_ctx);
    av_freep(&stream_mapping);

    if (ret < 0 && ret != AVERROR_EOF) {
        fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
        return 1;
    }

    return 0;
}

关键字段

  1. iformat、oformatiformat / oformat:输入和输出的封装格式,决定了如何处理流。

  2. AVIOContext:I/O上下文。这个在上一篇文章介绍过,

  • 解复用:可以由用户在avformat_open_input()之前设置(然后
    用户必须手动关闭它),或者由avformat_open_input()设置。
  • 复用:在avformat_write_header()之前由用户设置。调用者必须
    负责关闭/释放I/O上下文。
    如果iformat/oformat.flags中设置了AVFMT_NOFILE标志,则不要设置这个字段。在这种情况下,(解)复用器将以其他方式处理(libdevice中的muxers和demuxers都是AVFMT_NOFILE类型的,因为他的输入和输出一般都是特定的设备确定的,所以在这种情况下,请将该字段置为空)
  1. nb_streams、streams:流数量和流的列表,管理多媒体流。新流通过avformat_new_stream()创建

  2. url:输入或输出URL。与旧的filename字段不同,该字段没有长度限制

    • 解复用:由avformat_open_input()设置,如果avformat_open_input()的url参数为NULL,则初始化为一个空字符串。

    • 复用:可以在调用avformat_write_header()之前由调用者设置为可由av_free()释放的字符串。如果在avformat_init_output()中首先调用它,则设置为空字符

  3. flag,修改(解)复用器行为的标志。AVFMT_FLAG_*的组合。在avformat_open_input()/avformat_write_header()之前由用户设置。

#define AVFMT_FLAG_GENPTS       0x0001 ///< 生成丢失的pts,即使需要解析未来的帧。
#define AVFMT_FLAG_IGNIDX       0x0002 ///< 忽略索引。
#define AVFMT_FLAG_NONBLOCK     0x0004 ///< 从输入中读取数据时不阻塞。
#define AVFMT_FLAG_IGNDTS       0x0008 ///< 忽略同时包含DTS和PTS的帧上的DTS
#define AVFMT_FLAG_NOFILLIN     0x0010 ///< 不从其他值推断任何值,只返回容器中存储的值
#define AVFMT_FLAG_NOPARSE      0x0020 ///< 不使用AVParsers,还必须设置AVFMT_FLAG_NOFILLIN,因为填充代码是在帧上工作,不解析->没有帧。如果禁用解析来查找帧边界,那么寻找帧也不起作用
#define AVFMT_FLAG_NOBUFFER     0x0040 ///< 在可能的情况下不缓冲帧
#define AVFMT_FLAG_CUSTOM_IO    0x0080 ///< 调用方已提供自定义AVIOContext,不要avio_close()它。
#define AVFMT_FLAG_DISCARD_CORRUPT  0x0100 ///< 丢弃标记为损坏的帧
#define AVFMT_FLAG_FLUSH_PACKETS    0x0200 ///< 每个包后刷新AVIOContext。
/**
 * 在复用时,尽量避免向输出写入任何随机/不稳定的数据。这包括任何随机ID、实时时间戳/日期、复用器版本等。
 *
 * 这个标志主要用于测试。
 */
#define AVFMT_FLAG_BITEXACT         0x0400
#if FF_API_LAVF_MP4A_LATM
#define AVFMT_FLAG_MP4A_LATM    0x8000 ///< 已弃用,不起作用。
#endif
#define AVFMT_FLAG_SORT_DTS    0x10000 ///< 尝试按DTS交叉输出包(使用此标志可能会减慢解复用)
#define AVFMT_FLAG_PRIV_OPT    0x20000 ///< 延迟编解码器打开,以启用私有选项(一旦所有代码都被转换,这可以成为默认设置)
#if FF_API_LAVF_KEEPSIDE_FLAG
#define AVFMT_FLAG_KEEP_SIDE_DATA 0x40000 ///< 已弃用,不起作用。
#endif
#define AVFMT_FLAG_FAST_SEEK   0x80000 ///< 启用快速但不准确的寻址
#define AVFMT_FLAG_SHORTEST   0x100000 ///< 当最短流停止时停止复用。
#define AVFMT_FLAG_AUTO_BSF   0x200000 ///< 根据复用器请求添加比特流过滤器

  1. probesize、max_analyze_duration,控制读取数据以确定格式的大小和时间,影响解复用性能。

  2. nb_chapterschapters :AVChapter数量以及AVChapter数组。当复用时,AVChapter通常写入文件头,因此nb_chapters通常在调用write_header时初始化。某些复用器(例如mov和mkv)也可以在尾部写入章节。要在尾部写入章节,必须在调用write_header时将nb_chapters设置为零,并在write_trailer时设置为非零。

  3. metadata,适用于整个文件的元数据。解复用:由libavformat在avformat_open_input()中设置
    , 复用:可以在avformat_write_header()之前由用户设置,由libavformat在avformat_free_context()中释放。

  4. start_time_realtime,实时世界时间中流的起始时间,以自1970年1月1日Unix时代以来的微秒为单位。也就是流中的pts=0是在这个实际世界时间捕获的。复用:由用户在avformat_write_header()之前设置。如果设置为0或AV_NOPTS_VALUE,将使用当前的时间。解复用:由libavformat设置。如果未知,则为AV_NOPTS_VALUE。请注意,该值可能在接收到一定数量的帧之后变得已知。

  5. fps_probe_size,用于avformat_find_stream_info()中确定帧率的帧数。仅用于解复用,由用户在avformat_find_stream_info()之前设置。

  6. interrupt_callback,I/O层的自定义中断回调。 解复用:由用户在avformat_open_input()之前设置。复用:由用户在avformat_write_header()之前设置(主要用于AVFMT_NOFILE格式)。如果它用于打开文件,也应该传递给avio_open2()。

  7. debug, 用于启用调试的标志。 FF_FDEBUG_TS: 0x0001 复用:未使用,解复用:由用户设置。

  8. max_interleave_delta,用于交错的最大缓冲持续时间。为了确保所有的流正确地交错,av_interleaved_write_frame()将等到至少为每个流排队了一个包后才实际写入任何包到输出文件。比如收到的流是包含音视频的,那ffmpeg会等音视频都到了才开始写,当一些流“稀疏”(即连续包之间存在大的间隔)时,这可能导致过多的缓冲。这个字段指定了排队的包中的第一个包和最后一个包的时间戳之间的最大差异,超过这个差异,libavformat将输出一个包,无论它是否已为所有流排队了包。仅用于封装,由用户在avformat_write_header()之前设置。

  9. event_flags,用于用户检测文件上发生的事件的标志。比如包数据读取过程元数据更新了, 标志必须由用户在事件处理后清除。

#define AVSTREAM_EVENT_FLAG_METADATA_UPDATED 0x0001 ///< The call resulted in updated metadata.
  1. max_ts_probe,在等待第一个时间戳时最多要读取的数据包数。仅用于解码

  2. avoid_negative_ts,避免在封装时出现负时间戳,仅在av_interleaved_write_frame中有效

#define AVFMT_AVOID_NEG_TS_AUTO -1 ///< Enabled when required by target format
#define AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE 1 ///< Shift timestamps so they are non negative
#define AVFMT_AVOID_NEG_TS_MAKE_ZERO 2 ///< Shift timestamps so that they start at 0
  1. use_wallclock_as_timestamps 强制使用壁钟时间戳作为数据包的pts/dts。这在存在B帧时会导致未定义的结果。只能自解码时使用。

  2. duration_estimation_method,可以通过】多种方式估算持续时间字段,而这个字段可以用于知道持续时间是如何估算的。定义如

enum AVDurationEstimationMethod {
  AVFMT_DURATION_FROM_PTS,    ///< Duration accurately estimated from PTSes
  AVFMT_DURATION_FROM_STREAM, ///< Duration estimated from a stream with a known duration
  AVFMT_DURATION_FROM_BITRATE ///< Duration estimated from bitrate (less accurate)
};
  1. skip_initial_bytes ,在打开流时跳过的初始字节。

  2. correct_ts_overflow ,修正单个时间戳溢出。

  3. flush_packets,在每个数据包后刷新I/O上下文。在编码时由用户设置。

  4. format_probesize,用于识别格式的最大读取字节数。在解码时由用户设置。

  5. codec_whitelist,逗号分隔的允许解码器列表。如果为NULL,则允许所有解码器。解码时由用户设置

  6. internal,用于libavformat内部使用的不透明字段。不得由调用者以任何方式访问。

  7. io_repositioned,IO重新定位标志。 这由avformat设置,当底层IO上下文读指针被重新定位时,例如在进行基于字节的寻址时。解封装器可以使用该标志来检测此类更改。

  8. metadata_header_padding,写入元数据头部时要写入的填充字节数。封装:通过av_format_set_metadata_header_padding由用户设置。

  9. control_message_cb,用于设备与应用程序通信的回调。

  10. output_ts_offset,输出时间戳偏移,以微秒为单位。封装时由用户设置。

  11. io_open 每当封装器或解封装器需要打开一个IO流(通常是从avformat_open_input()用于解封装器,但对于某些格式也可能发生在其他时间),它会调用此回调以获取IO上下文。另外某些封装器和解封装器嵌套,即它们会打开一个或多个额外的内部格式上下文。因此,传递给此回调的AVFormatContext指针可能与面向调用者的指针不同。但是,它将具有相同的“opaque”字段。

  12. io_close 用于关闭由AVFormatContext.io_open()打开的流 的回调。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值