ffmpeg avio使用示例

源代码

ffmpeg 自定以输入输出avio从文件中读再写到另一个文件中的源码,ffmpeg 3.4

gcc ./test.c -o test -lpthread -lavformat -lavcodec -lavutil -lz

cpp 复制代码
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <stdio.h>
#include <stdlib.h>

#define BUFFER_SIZE (32 * 1024)

// 自定义上下文
typedef struct {
    FILE *input_file;
    FILE *output_file;
} CustomIOContext;

// ========================================
// read_packet 回调:从输入文件读取数据
// ========================================
int read_packet(void *opaque, uint8_t *buf, int buf_size)
{
    CustomIOContext *ctx = (CustomIOContext *)opaque;
    size_t ret = fread(buf, 1, buf_size, ctx->input_file);
    
    if (ret == 0 && feof(ctx->input_file))
        return AVERROR_EOF;
    return ret; // 返回实际读取字节数
}

// ========================================
// write_packet 回调:将数据写入输出文件
// ========================================
int write_packet(void *opaque, uint8_t *buf, int buf_size)
{
    CustomIOContext *ctx = (CustomIOContext *)opaque;
    size_t written = fwrite(buf, 1, buf_size, ctx->output_file);
    
    if (written != buf_size)
        return AVERROR(EIO); // 写入失败
    return written; // 返回成功写入字节数
}

// ========================================
// seek_input: 输入文件的 seek 回调
// ========================================
int64_t seek_input(void *opaque, int64_t offset, int whence)
{
    CustomIOContext *ctx = (CustomIOContext *)opaque;

    // 处理特殊请求:获取文件大小
    if (whence == AVSEEK_SIZE) {
        long cur = ftell(ctx->input_file);
        if (cur == -1) return AVERROR(errno);

        if (fseek(ctx->input_file, 0, SEEK_END) < 0)
            return AVERROR(errno);

        long size = ftell(ctx->input_file);
        fseek(ctx->input_file, cur, SEEK_SET);  // 恢复原位置
        printf("seek input get file size %d\n", size);
        return size;
    }

    // 转换 FFmpeg 的 whence 到标准 C 的 whence
    int std_whence;
    printf("seek input seek to %d\n", whence);
    switch (whence) {
        case SEEK_SET: std_whence = SEEK_SET; break;
        case SEEK_CUR: std_whence = SEEK_CUR; break;
        case SEEK_END: std_whence = SEEK_END; break;
        default: return AVERROR(EINVAL);
    }

    if (fseek(ctx->input_file, offset, std_whence) != 0)
        return AVERROR(errno);

    return ftell(ctx->input_file);
}

// ========================================
// seek_output: 输出文件的 seek 回调
// ========================================
int64_t seek_output(void *opaque, int64_t offset, int whence)
{
    CustomIOContext *ctx = (CustomIOContext *)opaque;

    if (whence == AVSEEK_SIZE) {
        long cur = ftell(ctx->output_file);
        if (cur == -1) return AVERROR(errno);

        if (fseek(ctx->output_file, 0, SEEK_END) < 0)
            return AVERROR(errno);

        long size = ftell(ctx->output_file);
        fseek(ctx->output_file, cur, SEEK_SET);
        printf("seek output get file size %d\n", size);
        return size;
    }

    int std_whence;
    printf("seek output seek to %d\n", whence);
    switch (whence) {
        case SEEK_SET: std_whence = SEEK_SET; break;
        case SEEK_CUR: std_whence = SEEK_CUR; break;
        case SEEK_END: std_whence = SEEK_END; break;
        default: return AVERROR(EINVAL);
    }

    if (fseek(ctx->output_file, offset, std_whence) != 0)
        return AVERROR(errno);

    return ftell(ctx->output_file);
}

int main(int argc, char *argv[])
{
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <input_file> <output_file>\n", argv[0]);
        exit(1);
    }
    int ret = 0;
    const char *input_path  = argv[1];
    const char *output_path = argv[2];
    printf("read %s then write to %s\n", argv[1], argv[2]);
    av_register_all();
    avformat_network_init();

    AVFormatContext *ifmt_ctx = NULL;
    AVFormatContext *ofmt_ctx = NULL;

    CustomIOContext io_ctx = {0};

    // 打开输入文件
    io_ctx.input_file = fopen(input_path, "rb");
    if (!io_ctx.input_file) {
        perror("Cannot open input file");
        return -1;
    }

    // 打开输出文件
    io_ctx.output_file = fopen(output_path, "wb");
    if (!io_ctx.output_file) {
        perror("Cannot open output file");
        fclose(io_ctx.input_file);
        return -1;
    }

    // ================================
    // 设置输入 AVIOContext(用于读)
    // ================================

    unsigned char *input_buffer = av_malloc(BUFFER_SIZE);
    AVIOContext *avio_in = avio_alloc_context(
        input_buffer,           // 缓冲区
        BUFFER_SIZE,            // 缓冲区大小
        0,                      // 0=读模式,1=写模式
        &io_ctx,                // opaque 用户数据
        read_packet,            // read_callback
        NULL,                   // write_callback(解复用不需要)
        seek_input                    // seek callback(可选)
    );

    if (!avio_in) {
        fprintf(stderr, "Cannot allocate input AVIOContext\n");
        goto fail;
    }
    
    ifmt_ctx = avformat_alloc_context();
    if (!ifmt_ctx) {
        fprintf(stderr, "Cannot allocate input context\n");
        goto fail;
    }

    ifmt_ctx->pb = avio_in;
    ifmt_ctx->flags |= AVFMT_FLAG_CUSTOM_IO;
    
    // 创建输入格式上下文
    if ((ret = avformat_open_input(&ifmt_ctx, NULL, NULL, NULL)) < 0) {
        fprintf(stderr, "Cannot open input AVFormatContext ret %d\n", ret);
        goto fail;
    }

    if (avformat_find_stream_info(ifmt_ctx, NULL) < 0) {
        fprintf(stderr, "Cannot find stream info\n");
        goto fail;
    }

    // ================================
    // 设置输出 AVIOContext(用于写)
    // ================================

    unsigned char *output_buffer = av_malloc(BUFFER_SIZE);
    AVIOContext *avio_out = avio_alloc_context(
        output_buffer,          // 缓冲区
        BUFFER_SIZE,            // 缓冲区大小
        1,                      // 1=写模式
        &io_ctx,                // opaque
        NULL,                   // read_packet(复用不需要)
        write_packet,           // write_callback
        seek_output                    // seek(如果需要 seek 输出)
    );

    if (!avio_out) {
        fprintf(stderr, "Cannot allocate output AVIOContext\n");
        goto fail;
    }

    // 创建输出格式上下文
    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, argv[2]);
    if (!ofmt_ctx) {
        fprintf(stderr, "Cannot allocate output format context\n");
        goto fail;
    }

    ofmt_ctx->pb = avio_out;

    // 复制输入流到输出(简单拷贝流)
    for (int i = 0; i < ifmt_ctx->nb_streams; i++) {
        AVStream *in_stream = ifmt_ctx->streams[i];
        AVStream *out_stream = avformat_new_stream(ofmt_ctx, NULL);
        if (!out_stream) {
            fprintf(stderr, "Failed to create new stream\n");
            goto fail;
        }
        avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
        out_stream->time_base = in_stream->time_base;
    }

    // 设置输出格式(自动检测基于文件名)
    ofmt_ctx->oformat = av_guess_format(NULL, output_path, NULL);
    if (!ofmt_ctx->oformat) {
        fprintf(stderr, "Cannot guess output format\n");
        goto fail;
    }

    // 打印信息
    av_dump_format(ifmt_ctx, 0, input_path, 0);
    av_dump_format(ofmt_ctx, 0, output_path, 1);
    
#if 1
    AVDictionary *opts = NULL;
#if 0
    //mp4
    av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov", 0);
    avformat_write_header(ofmt_ctx, &opts);
#else
    //flv
    av_dict_set(&opts, "flv_metadata", "0", 0);        // 必须关闭
    av_dict_set(&opts, "flvflags", "no_duration_filesize", 0); // 显式关闭
#endif
    // 写文件头
    if (avformat_write_header(ofmt_ctx, &opts) < 0) {
        fprintf(stderr, "Error writing header\n");
        goto fail;
    }
#else
    // 写文件头
    if (avformat_write_header(ofmt_ctx, NULL) < 0) {
        fprintf(stderr, "Error writing header\n");
        goto fail;
    }
#endif
    AVPacket pkt;
    av_init_packet(&pkt);
    pkt.data = NULL;
    pkt.size = 0;

    // ==============
    // 开始转封装
    // ==============

    while (av_read_frame(ifmt_ctx, &pkt) >= 0) {
        // 修改鏃????戳(避免负值)
        AVStream *in_stream = ifmt_ctx->streams[pkt.stream_index];
        AVStream *out_stream = ofmt_ctx->streams[pkt.stream_index];
        pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base,
                                   AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
        pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base,
                                   AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
        pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
        pkt.pos = -1;

        // 写包
        if (av_interleaved_write_frame(ofmt_ctx, &pkt) < 0) {
            fprintf(stderr, "Error muxing packet\n");
            break;
        }
        av_packet_unref(&pkt);
    }

    // 写文浠????(如有必要)
    av_write_trailer(ofmt_ctx);
fail:
    // 清理资源
    printf("clear input resource\n");
    if (ifmt_ctx) {
        avformat_close_input(&ifmt_ctx);
    }

    printf("clear output resource\n");
    if (ofmt_ctx) {
        if (ofmt_ctx->pb) {
            av_freep(&ofmt_ctx->pb->buffer);
            avio_context_free(&ofmt_ctx->pb);
        }
        avformat_free_context(ofmt_ctx);
    }

    printf("close files\n");
    if (io_ctx.input_file) fclose(io_ctx.input_file);
    if (io_ctx.output_file) fclose(io_ctx.output_file);

    printf("Done.\n");
    return 0;
}

源码分析

write 和 read和seek 回调的调用时机如何

实际情况write回调并不是以packt为单位的写入,回调中的buf的缓存情况如何

相关推荐
扶尔魔ocy7 小时前
【QT window】ffmpeg实现录音功能之无损格式--PCM
ffmpeg·pcm
止礼9 小时前
FFmpeg8.0.1 源代码的深入分析
ffmpeg
小曾同学.com9 小时前
音视频中的“透传”与“DTS音频”
ffmpeg·音视频·透传·dts
vivo互联网技术10 小时前
数字人动画云端渲染方案
前端·ffmpeg·puppeteer·web3d
止礼11 小时前
FFmpeg8.0.1 编解码流程
ffmpeg
qs701611 小时前
c直接调用FFmpeg命令无法执行问题
c语言·开发语言·ffmpeg
止礼11 小时前
FFmpeg8.0.1 Mac环境 CMake本地调试配置
macos·ffmpeg
简鹿视频1 天前
视频转mp4格式具体作步骤
ffmpeg·php·音视频·实时音视频