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的缓存情况如何

相关推荐
aqi001 天前
FFmpeg开发笔记(八十二)使用国产直播服务器smart_rtmpd执行推流操作
ffmpeg·音视频·直播·流媒体
陈增林1 天前
用 PyQt5 + FFmpeg 打造批量视频音频提取器
qt·ffmpeg·音视频
西瓜er2 天前
JAVA:Spring Boot 集成 FFmpeg 实现多媒体处理
java·spring boot·ffmpeg
QMCY_jason2 天前
ubuntu 24.04 FFmpeg编译 带Nvidia 加速记录
linux·ubuntu·ffmpeg
eqwaak02 天前
动态图表导出与视频生成:精通Matplotlib Animation与FFmpeg
开发语言·python·ffmpeg·音视频·matplotlib
执尺量北斗2 天前
LinkMate 智能会议室系统:基于 Qt / QML / WebRTC / FFmpeg / Whisper / OpenGL 的实时音视频会议平台
qt·ffmpeg·webrtc
月起星九3 天前
为什么ffmpeg进行视频合成有时长误差
ffmpeg·音视频
长沙红胖子Qt5 天前
FFmpeg开发笔记(十二):ffmpeg音频处理、采集麦克风音频录音为WAV
ffmpeg·pcm·wav·录音·麦克风
aqi005 天前
FFmpeg开发笔记(八十一)FFmpeg代码对RTSP和RTMP的推流区别
ffmpeg·音视频·直播·流媒体