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