【音视频】libavformat 容器格式处理

libavformat 是 FFmpeg 框架中的核心库之一,专门负责处理各种媒体容器格式的解复用(demuxing)和复用(muxing)操作。它为音视频数据的读取和写入提供了统一的API接口,是构建多媒体应用的基础组件。

1、libavformat概述

1.1、基本概念

libavformat(简称lavf)是FFmpeg的I/O和复用/解复用库,主要功能包括:

  • 解复用(Demuxing):将媒体文件分解为音频、视频、字幕等独立流
  • 复用(Muxing):将多个媒体流合并为指定容器格式的文件
  • I/O模块:支持文件、TCP、HTTP等多种数据源访问协议

1.2、容器格式基础

容器格式(Container Format),也称为封装格式,是一种将音频、视频、字幕等多媒体数据以及元数据打包成单一文件的标准格式。

常见容器格式

格式扩展名特点主要用途
MP4.mp4广泛支持,适合流媒体网络视频、移动设备
MKV.mkv开放标准,功能丰富高清视频存储
AVI.avi历史悠久,兼容性好传统视频编辑
MOV.movApple标准,编辑友好专业视频制作
WebM.webm开源,Web优化网页视频播放
TS.ts传输流,适合广播数字电视、直播

2、容器格式结构分析

2.1、通用结构模型

容器文件结构:
+------------------+
|   文件头/Header   | 格式标识、基本信息
+------------------+
|   元数据/Metadata | 时长、码率、编码信息等
+------------------+
|   索引/Index     | 关键帧位置、时间戳映射
+------------------+
|   数据流/Streams  | 实际的音视频数据
+------------------+
|   尾部/Footer    | 附加信息(可选)
+------------------+

2.2、具体格式结构

2.2.1、MP4结构(ISO Base Media File Format)

MP4文件基于Box结构:
+--------+------------------+
| ftyp   | 文件类型标识     |
+--------+------------------+
| moov   | 元数据容器       |
|  - mvhd| 电影头部         |
|  - trak| 轨道信息         |
|  - udta| 用户数据         |
+--------+------------------+
| mdat   | 媒体数据         |
+--------+------------------+
| free   | 空闲空间(可选) |
+--------+------------------+

2.2.2、MKV结构(Matroska)

MKV使用EBML结构:
+--------+------------------+
| EBML   | EBML头部         |
+--------+------------------+
| Segment| 主段             |
|  - Seek| 定位信息         |
|  - Info| 段信息           |
|  - Tracks|轨道信息       |
|  - Clusters|数据簇      |
|  - Cues| 索引点           |
+--------+------------------+

2.2.3、AVI结构

AVI使用RIFF结构:
+--------+------------------+
| RIFF   | RIFF头部         |
|  - AVI | AVI列表          |
|    - hdrl| 头部列表       |
|    - movi| 数据列表       |
|    - idx1| 索引(可选)   |
+--------+------------------+

3、libavformat核心API

3.1、主要数据结构

3.1.1、AVFormatContext

typedef struct AVFormatContext {
    const AVClass *av_class;        // 日志和选项
    struct AVInputFormat *iformat;// 输入格式
    struct AVOutputFormat *oformat;// 输出格式
    void *priv_data;                // 私有数据
    AVIOContext *pb;               // I/O上下文
    int ctx_flags;                 // 上下文标志
    
    unsignedint nb_streams;       // 流数量
    AVStream **streams;            // 流数组
    
    char *filename;                // 文件名
    int64_t start_time;           // 起始时间
    int64_t duration;             // 时长
    int64_t bit_rate;             // 比特率
    
    AVDictionary *metadata;       // 元数据
    
    // ... 其他字段
} AVFormatContext;

3.1.2、AVStream

typedef struct AVStream {
    int index;                     // 流索引
    int id;                       // 流ID
    AVCodecContext *codec;        // 编解码器上下文
    void *priv_data;              // 私有数据
    AVRational time_base;         // 时间基
    int64_t start_time;          // 起始时间
    int64_t duration;            // 时长
    int64_t nb_frames;           // 帧数量
    AVDisposition disposition;   // 流属性
    AVDictionary *metadata;      // 流元数据
    AVRational avg_frame_rate;   // 平均帧率
    AVRational r_frame_rate;     // 实际帧率
    
    // ... 其他字段
} AVStream;

3.2、解复用(Demuxing)流程

3.2.1、基本步骤

// 1. 注册所有格式和编解码器
av_register_all();

// 2. 打开输入文件
AVFormatContext *fmt_ctx = NULL;
if (avformat_open_input(&fmt_ctx, input_filename, NULL, NULL) < 0) {
    fprintf(stderr, "无法打开输入文件\n");
    return-1;
}

// 3. 读取流信息
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
    fprintf(stderr, "无法找到流信息\n");
    return-1;
}

// 4. 打印详细信息
av_dump_format(fmt_ctx, 0, input_filename, 0);

// 5. 查找视频流
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;
    }
}

// 6. 读取数据包
AVPacket pkt;
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
    if (pkt.stream_index == video_stream_index) {
        // 处理视频数据包
        process_video_packet(&pkt);
    }
    av_packet_unref(&pkt);
}

// 7. 关闭输入
avformat_close_input(&fmt_ctx);

3.2.2、完整解复用示例

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <stdio.h>

int demux_video(const char *input_filename) {
    AVFormatContext *fmt_ctx = NULL;
    AVPacket pkt;
    int ret;
    
    // 初始化libavformat并注册所有格式
    av_register_all();
    
    // 打开输入文件
    if ((ret = avformat_open_input(&fmt_ctx, input_filename, NULL, NULL)) < 0) {
        char errbuf[1024];
        av_strerror(ret, errbuf, sizeof(errbuf));
        fprintf(stderr, "无法打开文件 '%s': %s\n", input_filename, errbuf);
        return ret;
    }
    
    // 获取流信息
    if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0) {
        fprintf(stderr, "无法获取流信息\n");
        goto end;
    }
    
    // 打印输入格式信息
    printf("输入格式: %s\n", fmt_ctx->iformat->name);
    printf("时长: %" PRId64 " 秒\n", fmt_ctx->duration / AV_TIME_BASE);
    printf("比特率: %" PRId64 " bps\n", fmt_ctx->bit_rate);
    printf("流数量: %d\n", fmt_ctx->nb_streams);
    
    // 遍历所有流
    for (int i = 0; i < fmt_ctx->nb_streams; i++) {
        AVStream *stream = fmt_ctx->streams[i];
        AVCodecParameters *codecpar = stream->codecpar;
        
        printf("\n流 #%d:\n", i);
        printf("  类型: %s\n", av_get_media_type_string(codecpar->codec_type));
        printf("  编解码器: %s\n", avcodec_get_name(codecpar->codec_id));
        printf("  时间基: %d/%d\n", stream->time_base.num, stream->time_base.den);
        
        if (codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            printf("  分辨率: %dx%d\n", codecpar->width, codecpar->height);
            printf("  帧率: %.2f fps\n", av_q2d(stream->avg_frame_rate));
            printf("  像素格式: %s\n", av_get_pix_fmt_name(codecpar->format));
        } elseif (codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            printf("  采样率: %d Hz\n", codecpar->sample_rate);
            printf("  声道数: %d\n", codecpar->channels);
            printf("  采样格式: %s\n", av_get_sample_fmt_name(codecpar->format));
        }
        
        // 打印元数据
        AVDictionaryEntry *entry = NULL;
        while ((entry = av_dict_get(stream->metadata, "", entry, AV_DICT_IGNORE_SUFFIX))) {
            printf("  元数据: %s = %s\n", entry->key, entry->value);
        }
    }
    
    // 读取并处理数据包
    int packet_count = 0;
    while ((ret = av_read_frame(fmt_ctx, &pkt)) >= 0 && packet_count < 100) {
        AVStream *stream = fmt_ctx->streams[pkt.stream_index];
        
        printf("\n数据包 #%d:\n", packet_count);
        printf("  流索引: %d\n", pkt.stream_index);
        printf("  大小: %d 字节\n", pkt.size);
        printf("  PTS: %" PRId64 "\n", pkt.pts);
        printf("  DTS: %" PRId64 "\n", pkt.dts);
        printf("  时长: %" PRId64 "\n", pkt.duration);
        printf("  关键帧: %s\n", (pkt.flags & AV_PKT_FLAG_KEY) ? "是" : "否");
        
        // 这里可以添加实际的数据处理逻辑
        // process_packet_data(&pkt, stream);
        
        av_packet_unref(&pkt);
        packet_count++;
    }
    
end:
    // 清理
    if (fmt_ctx) {
        avformat_close_input(&fmt_ctx);
    }
    
    return ret < 0 ? ret : 0;
}

3.3、复用(Muxing)流程

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>

int mux_streams(const char *output_filename) {
    AVFormatContext *out_fmt_ctx = NULL;
    AVStream *video_stream = NULL, *audio_stream = NULL;
    AVCodecContext *video_codec_ctx = NULL, *audio_codec_ctx = NULL;
    const AVCodec *video_codec = NULL, *audio_codec = NULL;
    int ret;
    
    // 分配输出格式上下文
    avformat_alloc_output_context2(&out_fmt_ctx, NULL, NULL, output_filename);
    if (!out_fmt_ctx) {
        fprintf(stderr, "无法分配输出格式上下文\n");
        return-1;
    }
    
    // 查找视频编解码器(H.264)
    video_codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!video_codec) {
        fprintf(stderr, "找不到H.264编解码器\n");
        goto end;
    }
    
    // 创建视频流
    video_stream = avformat_new_stream(out_fmt_ctx, video_codec);
    if (!video_stream) {
        fprintf(stderr, "无法创建视频流\n");
        goto end;
    }
    
    // 设置视频流参数
    video_codec_ctx = avcodec_alloc_context3(video_codec);
    if (!video_codec_ctx) {
        fprintf(stderr, "无法分配视频编解码器上下文\n");
        goto end;
    }
    
    video_codec_ctx->width = 1920;
    video_codec_ctx->height = 1080;
    video_codec_ctx->time_base = (AVRational){1, 30}; // 30fps
    video_codec_ctx->framerate = (AVRational){30, 1};
    video_codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
    video_codec_ctx->bit_rate = 4000000; // 4Mbps
    
    // 打开视频编解码器
    if ((ret = avcodec_open2(video_codec_ctx, video_codec, NULL)) < 0) {
        char errbuf[1024];
        av_strerror(ret, errbuf, sizeof(errbuf));
        fprintf(stderr, "无法打开视频编解码器: %s\n", errbuf);
        goto end;
    }
    
    // 将编解码器参数复制到流
    ret = avcodec_parameters_from_context(video_stream->codecpar, video_codec_ctx);
    if (ret < 0) {
        fprintf(stderr, "无法复制视频编解码器参数\n");
        goto end;
    }
    
    // 查找音频编解码器(AAC)
    audio_codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
    if (!audio_codec) {
        fprintf(stderr, "找不到AAC编解码器\n");
        goto end;
    }
    
    // 创建音频流
    audio_stream = avformat_new_stream(out_fmt_ctx, audio_codec);
    if (!audio_stream) {
        fprintf(stderr, "无法创建音频流\n");
        goto end;
    }
    
    // 设置音频流参数
    audio_codec_ctx = avcodec_alloc_context3(audio_codec);
    if (!audio_codec_ctx) {
        fprintf(stderr, "无法分配音频编解码器上下文\n");
        goto end;
    }
    
    audio_codec_ctx->sample_rate = 48000;
    audio_codec_ctx->channels = 2;
    audio_codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO;
    audio_codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
    audio_codec_ctx->bit_rate = 128000; // 128kbps
    audio_codec_ctx->time_base = (AVRational){1, 48000};
    
    // 打开音频编解码器
    if ((ret = avcodec_open2(audio_codec_ctx, audio_codec, NULL)) < 0) {
        char errbuf[1024];
        av_strerror(ret, errbuf, sizeof(errbuf));
        fprintf(stderr, "无法打开音频编解码器: %s\n", errbuf);
        goto end;
    }
    
    // 将编解码器参数复制到流
    ret = avcodec_parameters_from_context(audio_stream->codecpar, audio_codec_ctx);
    if (ret < 0) {
        fprintf(stderr, "无法复制音频编解码器参数\n");
        goto end;
    }
    
    // 打印输出格式信息
    av_dump_format(out_fmt_ctx, 0, output_filename, 1);
    
    // 打开输出文件
    if (!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
        ret = avio_open(&out_fmt_ctx->pb, output_filename, AVIO_FLAG_WRITE);
        if (ret < 0) {
            char errbuf[1024];
            av_strerror(ret, errbuf, sizeof(errbuf));
            fprintf(stderr, "无法打开输出文件: %s\n", errbuf);
            goto end;
        }
    }
    
    // 写入文件头
    ret = avformat_write_header(out_fmt_ctx, NULL);
    if (ret < 0) {
        char errbuf[1024];
        av_strerror(ret, errbuf, sizeof(errbuf));
        fprintf(stderr, "无法写入文件头: %s\n", errbuf);
        goto end;
    }
    
    // 这里应该添加实际的音视频编码和写入逻辑
    // 为演示目的,我们写入一些空的数据包
    AVPacket pkt;
    av_init_packet(&pkt);
    pkt.data = NULL;
    pkt.size = 0;
    
    // 写入视频帧(假设30帧,1秒视频)
    for (int i = 0; i < 30; i++) {
        pkt.stream_index = video_stream->index;
        pkt.pts = i;
        pkt.dts = i;
        pkt.duration = 1;
        pkt.pos = -1;
        
        if (i % 30 == 0) { // 每秒一个关键帧
            pkt.flags |= AV_PKT_FLAG_KEY;
        }
        
        ret = av_interleaved_write_frame(out_fmt_ctx, &pkt);
        if (ret < 0) {
            fprintf(stderr, "写入视频帧失败\n");
            goto end;
        }
    }
    
    // 写入音频帧(假设48000Hz,1024样本/帧,约46帧/秒)
    for (int i = 0; i < 46; i++) {
        pkt.stream_index = audio_stream->index;
        pkt.pts = i * 1024;
        pkt.dts = i * 1024;
        pkt.duration = 1024;
        pkt.pos = -1;
        pkt.flags |= AV_PKT_FLAG_KEY;
        
        ret = av_interleaved_write_frame(out_fmt_ctx, &pkt);
        if (ret < 0) {
            fprintf(stderr, "写入音频帧失败\n");
            goto end;
        }
    }
    
    // 写入文件尾
    av_write_trailer(out_fmt_ctx);
    
    printf("复用完成,输出文件: %s\n", output_filename);
    
end:
    // 清理
    if (video_codec_ctx) {
        avcodec_close(video_codec_ctx);
        avcodec_free_context(&video_codec_ctx);
    }
    if (audio_codec_ctx) {
        avcodec_close(audio_codec_ctx);
        avcodec_free_context(&audio_codec_ctx);
    }
    if (out_fmt_ctx) {
        if (!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
            avio_closep(&out_fmt_ctx->pb);
        }
        avformat_free_context(out_fmt_ctx);
    }
    
    return ret < 0 ? ret : 0;
}

4、高级特性

4.1、格式探测

int probe_format(const char *filename) {
    AVProbeData probe_data = {0};
    uint8_t buf[4096];
    FILE *f = fopen(filename, "rb");
    if (!f) return-1;
    
    // 读取文件头
    probe_data.buf_size = fread(buf, 1, sizeof(buf), f);
    probe_data.filename = filename;
    probe_data.buf = buf;
    fclose(f);
    
    // 探测格式
    AVInputFormat *fmt = av_probe_input_format(&probe_data, 1);
    if (fmt) {
        printf("探测到的格式: %s\n", fmt->name);
        printf("格式描述: %s\n", fmt->long_name);
        printf("扩展名: %s\n", fmt->extensions ? fmt->extensions : "无");
        printf("MIME类型: %s\n", fmt->mime_type ? fmt->mime_type : "无");
        
        // 检查格式能力
        printf("支持的功能:\n");
        if (fmt->flags & AVFMT_NOFILE) {
            printf("  - 不需要文件I/O\n");
        }
        if (fmt->flags & AVFMT_NEEDNUMBER) {
            printf("  - 需要序列号\n");
        }
        if (fmt->flags & AVFMT_SHOW_IDS) {
            printf("  - 显示流ID\n");
        }
        if (fmt->flags & AVFMT_GLOBALHEADER) {
            printf("  - 需要全局头部\n");
        }
        if (fmt->flags & AVFMT_NOTIMESTAMPS) {
            printf("  - 无时间戳\n");
        }
        if (fmt->flags & AVFMT_GENERIC_INDEX) {
            printf("  - 通用索引\n");
        }
        
        return0;
    }
    
    printf("无法识别格式\n");
    return-1;
}

4.2、元数据处理

int handle_metadata(AVFormatContext *fmt_ctx) {
    // 处理全局元数据
    AVDictionaryEntry *entry = NULL;
    printf("全局元数据:\n");
    while ((entry = av_dict_get(fmt_ctx->metadata, "", entry, AV_DICT_IGNORE_SUFFIX))) {
        printf("  %s = %s\n", entry->key, entry->value);
    }
    
    // 处理每个流的元数据
    for (int i = 0; i < fmt_ctx->nb_streams; i++) {
        AVStream *stream = fmt_ctx->streams[i];
        printf("\n流 #%d 元数据:\n", i);
        
        entry = NULL;
        while ((entry = av_dict_get(stream->metadata, "", entry, AV_DICT_IGNORE_SUFFIX))) {
            printf("  %s = %s\n", entry->key, entry->value);
        }
        
        // 添加新的元数据
        av_dict_set(&stream->metadata, "encoder", "libavformat_demo", 0);
    }
    
    // 设置输出元数据
    av_dict_set(&fmt_ctx->metadata, "title", "Demo Video", 0);
    av_dict_set(&fmt_ctx->metadata, "author", "FFmpeg Developer", 0);
    av_dict_set(&fmt_ctx->metadata, "creation_time", "2024-01-01T00:00:00Z", 0);
    
    return0;
}

4.3、错误处理和恢复

int robust_demuxing(const char *input_filename) {
    AVFormatContext *fmt_ctx = NULL;
    AVPacket pkt;
    int ret, error_count = 0;
    constint MAX_ERRORS = 10;
    
    // 设置错误标志
    AVDictionary *opts = NULL;
    av_dict_set(&opts, "err_detect", "crccheck+bitstream+buffer", 0);
    
    // 打开输入文件
    ret = avformat_open_input(&fmt_ctx, input_filename, NULL, &opts);
    av_dict_free(&opts);
    
    if (ret < 0) {
        char errbuf[1024];
        av_strerror(ret, errbuf, sizeof(errbuf));
        fprintf(stderr, "打开文件失败: %s\n", errbuf);
        return ret;
    }
    
    // 读取数据包,带错误恢复
    while (error_count < MAX_ERRORS) {
        ret = av_read_frame(fmt_ctx, &pkt);
        
        if (ret == AVERROR_EOF) {
            printf("到达文件末尾\n");
            break;
        } elseif (ret == AVERROR(EAGAIN)) {
            // 需要更多数据
            continue;
        } elseif (ret < 0) {
            char errbuf[1024];
            av_strerror(ret, errbuf, sizeof(errbuf));
            fprintf(stderr, "读取数据包错误: %s\n", errbuf);
            
            error_count++;
            if (error_count >= MAX_ERRORS) {
                fprintf(stderr, "错误次数过多,停止处理\n");
                break;
            }
            
            // 尝试跳过错误部分
            continue;
        }
        
        // 成功读取数据包,进行处理
        printf("成功读取数据包: stream=%d, size=%d, pts=%"PRId64"\n",
               pkt.stream_index, pkt.size, pkt.pts);
        
        // 处理数据包...
        
        av_packet_unref(&pkt);
    }
    
    avformat_close_input(&fmt_ctx);
    return error_count < MAX_ERRORS ? 0 : -1;
}

5、性能优化

5.1、内存管理优化

typedef struct {
    AVFormatContext *fmt_ctx;
    AVPacket *packet_buffer;
    int buffer_size;
    int buffer_index;
} OptimizedDemuxer;

OptimizedDemuxer* create_optimized_demuxer(const char *filename) {
    OptimizedDemuxer *demuxer = calloc(1, sizeof(OptimizedDemuxer));
    if (!demuxer) returnNULL;
    
    // 预分配包缓冲区
    demuxer->buffer_size = 100;
    demuxer->packet_buffer = calloc(demuxer->buffer_size, sizeof(AVPacket));
    if (!demuxer->packet_buffer) {
        free(demuxer);
        returnNULL;
    }
    
    // 预分配格式上下文
    demuxer->fmt_ctx = avformat_alloc_context();
    if (!demuxer->fmt_ctx) {
        free(demuxer->packet_buffer);
        free(demuxer);
        returnNULL;
    }
    
    // 设置自定义I/O缓冲区(可选)
    uint8_t *io_buffer = av_malloc(32768); // 32KB缓冲区
    demuxer->fmt_ctx->pb = avio_alloc_context(
        io_buffer, 32768, 0, NULL, NULL, NULL, NULL
    );
    
    return demuxer;
}

5.2、多线程处理

#include <pthread.h>

typedefstruct {
    AVFormatContext *fmt_ctx;
    int stream_index;
    AVPacket *packets;
    int packet_count;
    pthread_mutex_t mutex;
} ThreadSafeDemuxer;

void* demux_thread(void *arg) {
    ThreadSafeDemuxer *demuxer = (ThreadSafeDemuxer*)arg;
    AVPacket pkt;
    
    while (1) {
        pthread_mutex_lock(&demuxer->mutex);
        int ret = av_read_frame(demuxer->fmt_ctx, &pkt);
        pthread_mutex_unlock(&demuxer->mutex);
        
        if (ret < 0) break;
        
        if (pkt.stream_index == demuxer->stream_index) {
            // 处理特定流的数据包
            process_packet(&pkt);
        }
        
        av_packet_unref(&pkt);
    }
    
    returnNULL;
}

int start_multithreaded_demuxing(AVFormatContext *fmt_ctx, int num_threads) {
    pthread_t *threads = calloc(num_threads, sizeof(pthread_t));
    ThreadSafeDemuxer *demuxers = calloc(num_threads, sizeof(ThreadSafeDemuxer));
    
    for (int i = 0; i < num_threads; i++) {
        demuxers[i].fmt_ctx = fmt_ctx;
        demuxers[i].stream_index = i % fmt_ctx->nb_streams;
        pthread_mutex_init(&demuxers[i].mutex, NULL);
        
        pthread_create(&threads[i], NULL, demux_thread, &demuxers[i]);
    }
    
    // 等待所有线程完成
    for (int i = 0; i < num_threads; i++) {
        pthread_join(threads[i], NULL);
        pthread_mutex_destroy(&demuxers[i].mutex);
    }
    
    free(threads);
    free(demuxers);
    return0;
}

6、实际应用案例

6.1、媒体文件分析器

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/dict.h>
#include <stdio.h>

typedefstruct {
    constchar *filename;
    constchar *format_name;
    int64_t duration;
    int64_t bit_rate;
    int nb_streams;
    struct {
        int index;
        enum AVMediaType type;
        constchar *codec_name;
        int width, height;
        int sample_rate;
        int channels;
        int64_t bit_rate;
    } streams[32];
} MediaInfo;

int analyze_media_file(const char *filename, MediaInfo *info) {
    AVFormatContext *fmt_ctx = NULL;
    int ret;
    
    // 初始化
    memset(info, 0, sizeof(MediaInfo));
    info->filename = filename;
    
    // 打开文件
    if ((ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL)) < 0) {
        return ret;
    }
    
    // 获取流信息
    if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0) {
        avformat_close_input(&fmt_ctx);

学习和提升音视频开发技术,推荐你加入我们的知识星球:【关键帧的音视频开发圈】

【音视频】libavformat 容器格式处理

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论