1、通过av_register_allffmpeg 4.0已废弃直接调用avformat_open_input函数即可avformat_open_input函数内部会做初始化判断函数和avformat_network_init函数初始化FFmpeg2、打开输入文件原始的yuv视频数据文件、原始的音频文件pcm音视频pst时间戳文件音频时间戳可以不用可以用采样数来精确控制3、创建特定输出格式的上下文4、通过avcodec_find_encoder函数和avcodec_alloc_context3函数创建视频编码器及编码器上下文通过avformat_new_stream函数将输出格式上下文和视频编码器绑定初始化编码器上下文video_codec_ctx参数并通过avcodec_open2函数确认视频编码器打开正常5、通过avformat_new_stream函数创建视频流6、将视频编码器参数复制到视频流的编码器项7、音频编码器的相关处理和视频编码器的处理类似通过avcodec_find_encoder函数和avcodec_alloc_context3函数创建音频编码器及编码器上下文通avformat_new_stream函数将输出格式上下文和音频编码器绑定初始化音频编码器上下文audio_codec_ctx参数通过avcodec_open2函数确认音频编码器打开是否正常并将音频编码器参数复制到音频流相关项。8、循环处理yuv数据取出yuv数据通过avcodec_send_frame函数送入编码器从avcodec_receive_packet函数取出编码后的数据通过av_packet_rescale_ts函数进行时间基转换后再通过av_interleaved_write_frame函数写入文件最后再通过avcodec_send_frame函数刷新编码器并确认yuv数据是否处理完整9、循环处理pcm音频数据通过fread函数读取音频数据帧并进行重采样通过avcodec_send_frame函数将音频帧输入音频编码器进行编码从avcodec_receive_packet函数中取出编码后的音频数据通过av_packet_rescale_ts函数进行时间基转换后再通过av_interleaved_write_frame函数写入文件再通过avcodec_send_frame函数刷新编码器并确认yuv数据是否处理完整10、通过av_write_trailer函数写入文件尾关闭打开的文件并清理资源。#include stdio.h #include stdlib.h #include string.h #include libavutil/avutil.h #include libavformat/avformat.h #include libavcodec/avcodec.h #include libswscale/swscale.h #include libswresample/swresample.h #include ecode_av_example.h #define __STDC_CONSTANT_MACROS #define __STDC_LIMIT_MACROS // 视频参数 #define VIDEO_WIDTH 1280 #define VIDEO_HEIGHT 592 // 音频参数 #define AUDIO_CHANNELS 1 #define AUDIO_SAMPLE_RATE 44100 // 全局变量 static FILE *sg_video_file NULL; static FILE *sg_audio_file NULL; static FILE *sg_video_pts_file NULL; static FILE *sg_audio_pts_file NULL; // 读取下一帧视频数据 static int read_video_frame(uint8_t *y_data, uint8_t *u_data, uint8_t *v_data, int y_size, int u_size, int v_size, int64_t *pts, double *pts_seconds) { if (!sg_video_file || !sg_video_pts_file) return -1; // 读取YUV数据 if (fread(y_data, 1, y_size, sg_video_file) ! y_size) return -1; if (fread(u_data, 1, u_size, sg_video_file) ! u_size) return -1; if (fread(v_data, 1, v_size, sg_video_file) ! v_size) return -1; // 读取PTS if (fscanf(sg_video_pts_file, %lld %lf, pts, pts_seconds) ! 2) return -1; return 0; } // 读取下一帧音频数据 static int read_audio_frame(uint8_t *data, int size /*, int64_t *pts, double *pts_seconds*/) { if (!sg_audio_file) return -1; // 读取PCM数据 size_t read fread(data, 1, size, sg_audio_file); if (read 0) return -1; // 读取PTS每帧数据对应一个PTS /*if (sg_audio_pts_file) { if (fscanf(sg_audio_pts_file, %lld %lf, pts, pts_seconds) ! 2) { *pts AV_NOPTS_VALUE; *pts_seconds 0; } } else { *pts AV_NOPTS_VALUE; *pts_seconds 0; }*/ return read; } /** * brief 将原始 YUV 视频数据和 PCM 音频数据编码为 MP4 文件 * * param input_video_file 输入 YUV420P 视频文件路径 * param input_video_pts_file 视频 PTS 时间戳文件路径 * param input_audio_file 输入 PCM S16LE 音频文件路径 * param input_audio_pts_file 音频 PTS 时间戳文件路径 * param output_file 输出 MP4 文件路径 * return int 0 表示成功负数表示失败 */ int ecode_av_example(char *input_video_file, char *input_video_pts_file, char *input_audio_file, char *input_audio_pts_file, char *output_file) { int ret; // 函数返回值 AVFormatContext *fmt_ctx NULL; // 输出格式上下文管理 MP4 文件 AVStream *video_stream NULL; // 视频流 AVStream *audio_stream NULL; // 音频流 AVCodecContext *video_codec_ctx NULL; // 视频编码器上下文 AVCodecContext *audio_codec_ctx NULL; // 音频编码器上下文 AVCodec *video_codec NULL; // 视频编码器 AVCodec *audio_codec NULL; // 音频编码器 // FFmpeg 初始化 // 注册所有可用的格式和编解码器 av_register_all(); // 初始化网络模块用于协议支持 avformat_network_init(); // 打开输入文件 sg_video_file fopen(input_video_file, rb); // 打开 YUV 视频文件二进制只读 sg_audio_file fopen(input_audio_file, rb); // 打开 PCM 音频文件二进制只读 sg_video_pts_file fopen(input_video_pts_file, r); // 打开视频 PTS 文件文本只读 sg_audio_pts_file fopen(input_audio_pts_file, r); // 打开音频 PTS 文件文本只读 // 检查文件是否成功打开 if (!sg_video_file || !sg_audio_file) { printf(Error: Could not open input files\n); return -1; } // 创建输出格式上下文 // 根据输出文件名自动检测格式MP4 ret avformat_alloc_output_context2(fmt_ctx, NULL, mp4, output_file); if (ret 0) { printf(Error: Could not create output context\n); return -1; } // 视频流配置 printf([Encoder] Configuring video stream...\n); // 1. 查找 H.264 编码器 video_codec avcodec_find_encoder(AV_CODEC_ID_H264); if (!video_codec) { printf(Error: H.264 encoder not found\n); return -1; } // 2. 创建视频流 video_stream avformat_new_stream(fmt_ctx, video_codec); if (!video_stream) { printf(Error: Could not create video stream\n); return -1; } // 3. 分配编码器上下文并设置参数 video_codec_ctx avcodec_alloc_context3(video_codec); video_codec_ctx-codec_id AV_CODEC_ID_H264; // 编码器类型H.264 video_codec_ctx-codec_type AVMEDIA_TYPE_VIDEO; // 媒体类型视频 video_codec_ctx-width VIDEO_WIDTH; // 视频宽度必须与输入一致 video_codec_ctx-height VIDEO_HEIGHT; // 视频高度必须与输入一致 video_codec_ctx-pix_fmt AV_PIX_FMT_YUV420P; // 像素格式YUV420P video_codec_ctx-bit_rate 638615; // 目标码率bps video_codec_ctx-framerate (AVRational){30, 1}; // 帧率30 fps video_codec_ctx-time_base (AVRational){1, 600}; // 时间基1/600 秒 video_codec_ctx-gop_size 60; // 关键帧间隔每60帧一个关键帧 video_codec_ctx-max_b_frames 2; // 最大 B 帧数量 // 4. 设置全局头部标志MP4 格式需要 if (fmt_ctx-oformat-flags AVFMT_GLOBALHEADER) { video_codec_ctx-flags | AV_CODEC_FLAG_GLOBAL_HEADER; } // 5. 设置编码质量参数 AVDictionary *video_opts NULL; av_dict_set(video_opts, preset, medium, 0); // 编码预设中等速度/质量 av_dict_set(video_opts, crf, 23, 0); // 恒定质量模式0-51越小质量越高 // 6. 打开编码器 ret avcodec_open2(video_codec_ctx, video_codec, video_opts); if (ret 0) { printf(Error: Could not open video codec\n); return -1; } // 关键修复avcodec_open2 可能修改 time_base需要重新设置 video_codec_ctx-time_base (AVRational){1, 600}; video_codec_ctx-framerate (AVRational){30, 1}; // 7. 将编码器参数复制到流 avcodec_parameters_from_context(video_stream-codecpar, video_codec_ctx); video_stream-time_base video_codec_ctx-time_base; video_stream-r_frame_rate video_codec_ctx-framerate; // 音频流配置 printf([Encoder] Configuring audio stream...\n); // 1. 查找 AAC 编码器 audio_codec avcodec_find_encoder(AV_CODEC_ID_AAC); if (!audio_codec) { printf(Error: AAC encoder not found\n); return -1; } // 2. 创建音频流 audio_stream avformat_new_stream(fmt_ctx, audio_codec); if (!audio_stream) { printf(Error: Could not create audio stream\n); return -1; } // 3. 分配编码器上下文并设置参数 audio_codec_ctx avcodec_alloc_context3(audio_codec); audio_codec_ctx-codec_id AV_CODEC_ID_AAC; // 编码器类型AAC audio_codec_ctx-codec_type AVMEDIA_TYPE_AUDIO; // 媒体类型音频 audio_codec_ctx-channels AUDIO_CHANNELS; // 声道数1单声道 audio_codec_ctx-sample_rate AUDIO_SAMPLE_RATE; // 采样率44100 Hz audio_codec_ctx-sample_fmt AV_SAMPLE_FMT_FLTP; // 采样格式浮点平面格式 audio_codec_ctx-bit_rate 47214; // 目标码率 audio_codec_ctx-time_base (AVRational){1, 44100}; // 时间基1/44100 秒 audio_codec_ctx-channel_layout AV_CH_LAYOUT_MONO; // 声道布局单声道 // 4. 设置全局头部标志 if (fmt_ctx-oformat-flags AVFMT_GLOBALHEADER) { audio_codec_ctx-flags | AV_CODEC_FLAG_GLOBAL_HEADER; } // 5. 打开编码器 AVDictionary *audio_opts NULL; ret avcodec_open2(audio_codec_ctx, audio_codec, audio_opts); if (ret 0) { printf(Error: Could not open audio codec\n); return -1; } // 6. 将编码器参数复制到流 avcodec_parameters_from_context(audio_stream-codecpar, audio_codec_ctx); audio_stream-time_base audio_codec_ctx-time_base; // 打开输出文件 if (!(fmt_ctx-oformat-flags AVFMT_NOFILE)) { ret avio_open(fmt_ctx-pb, output_file, AVIO_FLAG_WRITE); if (ret 0) { printf(Error: Could not open output file\n); return -1; } } // 写入文件头 ret avformat_write_header(fmt_ctx, NULL); if (ret 0) { printf(Error: Could not write header\n); return -1; } // 编码视频帧 printf(Encoding video frames...\n); // 1. 分配视频帧结构 AVFrame *video_frame av_frame_alloc(); video_frame-format AV_PIX_FMT_YUV420P; video_frame-width VIDEO_WIDTH; video_frame-height VIDEO_HEIGHT; av_frame_get_buffer(video_frame, 0); // 分配帧数据缓冲区 // 2. 计算 YUV 各平面大小 int y_size VIDEO_WIDTH * VIDEO_HEIGHT; // Y 平面宽×高 int u_size VIDEO_WIDTH * VIDEO_HEIGHT / 4; // U 平面宽/2 × 高/2 int v_size VIDEO_WIDTH * VIDEO_HEIGHT / 4; // V 平面宽/2 × 高/2 // 3. 分配读取缓冲区 uint8_t *y_buf (uint8_t*)malloc(y_size); uint8_t *u_buf (uint8_t*)malloc(u_size); uint8_t *v_buf (uint8_t*)malloc(v_size); int64_t video_pts 0; double video_pts_sec 0; int video_frame_count 0; // 4. 视频编码循环 while (1) { // 读取 YUV 数据按 Y→U→V 顺序 if (fread(y_buf, 1, y_size, sg_video_file) ! y_size) break; if (fread(u_buf, 1, u_size, sg_video_file) ! u_size) break; if (fread(v_buf, 1, v_size, sg_video_file) ! v_size) break; // 读取 PTS 时间戳 if (fscanf(sg_video_pts_file, %lld %lf, video_pts, video_pts_sec) ! 2) break; // ✅ 关键修复逐行复制数据处理 linesize 与实际宽度的差异 // Y 平面亮度 for (int i 0; i video_frame-height; i) { memcpy(video_frame-data[0] i * video_frame-linesize[0], y_buf i * video_frame-width, video_frame-width); } // U 平面色度 for (int i 0; i video_frame-height / 2; i) { memcpy(video_frame-data[1] i * video_frame-linesize[1], u_buf i * (video_frame-width / 2), video_frame-width / 2); } // V 平面色度 for (int i 0; i video_frame-height / 2; i) { memcpy(video_frame-data[2] i * video_frame-linesize[2], v_buf i * (video_frame-width / 2), video_frame-width / 2); } // 设置帧的 PTS显示时间戳 video_frame-pts video_pts; // 发送帧到编码器 ret avcodec_send_frame(video_codec_ctx, video_frame); if (ret 0) { printf(Error sending video frame\n); break; } // 接收编码后的数据包 AVPacket pkt {0}; while (ret 0) { ret avcodec_receive_packet(video_codec_ctx, pkt); if (ret AVERROR(EAGAIN) || ret AVERROR_EOF) break; // 需要更多输入或结束 if (ret 0) { printf(Error receiving video packet\n); break; } // 设置数据包所属流索引 pkt.stream_index video_stream-index; // 时间戳转换从编码器时间基转换为流时间基 av_packet_rescale_ts(pkt, video_codec_ctx-time_base, video_stream-time_base); // 写入文件自动处理交织 ret av_interleaved_write_frame(fmt_ctx, pkt); // 释放数据包资源 av_packet_unref(pkt); } video_frame_count; if (video_frame_count % 100 0) { printf(Encoded %d video frames\n, video_frame_count); } } // 5. 刷新视频编码器处理剩余帧 avcodec_send_frame(video_codec_ctx, NULL); // 发送 NULL 表示结束 AVPacket pkt {0}; while (avcodec_receive_packet(video_codec_ctx, pkt) 0) { pkt.stream_index video_stream-index; av_packet_rescale_ts(pkt, video_codec_ctx-time_base, video_stream-time_base); av_interleaved_write_frame(fmt_ctx, pkt); av_packet_unref(pkt); } printf(Total video frames encoded: %d\n, video_frame_count); // 编码音频帧 printf(Encoding audio frames...\n); // 1. 分配音频帧结构 AVFrame *audio_frame av_frame_alloc(); if (!audio_frame) { printf(Error: Could not allocate audio frame\n); return -1; } audio_frame-format AV_SAMPLE_FMT_FLTP; // 编码器要求的格式 audio_frame-channels AUDIO_CHANNELS; audio_frame-sample_rate AUDIO_SAMPLE_RATE; audio_frame-nb_samples 1024; // 每帧采样数 av_frame_get_buffer(audio_frame, 0); // 2. 创建重采样上下文PCM S16LE → FLTP SwrContext *swr_ctx swr_alloc_set_opts( NULL, // 现有上下文NULL 表示新建 av_get_default_channel_layout(AUDIO_CHANNELS), // 输出声道布局 AV_SAMPLE_FMT_FLTP, // 输出采样格式 AUDIO_SAMPLE_RATE, // 输出采样率 av_get_default_channel_layout(AUDIO_CHANNELS), // 输入声道布局 AV_SAMPLE_FMT_S16, // 输入采样格式S16LE AUDIO_SAMPLE_RATE, // 输入采样率 0, NULL // 额外选项 ); swr_init(swr_ctx); // 初始化重采样器 // 3. 计算音频帧大小S16格式每个采样2字节 int audio_sample_size 2; int audio_frame_size audio_frame-nb_samples * AUDIO_CHANNELS * audio_sample_size; uint8_t *audio_buf (uint8_t*)malloc(audio_frame_size); int64_t audio_pts 0; double audio_pts_sec 0; int audio_frame_count 0; // 4. 音频编码循环 while (read_audio_frame(audio_buf, audio_frame_size) 0) { // 将 S16 数据重采样为 FLTP 格式 uint8_t *in_data[1] {audio_buf}; swr_convert(swr_ctx, audio_frame-data, audio_frame-nb_samples, (const uint8_t**)in_data, audio_frame-nb_samples); // 设置音频帧 PTS按采样数递增 audio_frame-pts audio_pts; // 发送帧到编码器 ret avcodec_send_frame(audio_codec_ctx, audio_frame); if (ret 0) { printf(Error sending audio frame\n); break; } // 接收编码后的数据包 while (ret 0) { ret avcodec_receive_packet(audio_codec_ctx, pkt); if (ret AVERROR(EAGAIN) || ret AVERROR_EOF) break; if (ret 0) { printf(Error receiving audio packet\n); break; } pkt.stream_index audio_stream-index; av_packet_rescale_ts(pkt, audio_codec_ctx-time_base, audio_stream-time_base); ret av_interleaved_write_frame(fmt_ctx, pkt); av_packet_unref(pkt); } // ✅ 音频 PTS 按采样数递增关键 audio_pts audio_frame-nb_samples; audio_frame_count; if (audio_frame_count % 100 0) { printf(Encoded %d audio frames\n, audio_frame_count); } } // 5. 刷新音频编码器 avcodec_send_frame(audio_codec_ctx, NULL); while (avcodec_receive_packet(audio_codec_ctx, pkt) 0) { pkt.stream_index audio_stream-index; av_packet_rescale_ts(pkt, audio_codec_ctx-time_base, audio_stream-time_base); av_interleaved_write_frame(fmt_ctx, pkt); av_packet_unref(pkt); } printf(Total audio frames encoded: %d\n, audio_frame_count); // 清理资源 // 写入文件尾 av_write_trailer(fmt_ctx); // 关闭输出文件 if (!(fmt_ctx-oformat-flags AVFMT_NOFILE)) { avio_closep(fmt_ctx-pb); } // 释放 FFmpeg 资源 avformat_free_context(fmt_ctx); // 释放格式上下文 avcodec_free_context(video_codec_ctx); // 释放视频编码器上下文 avcodec_free_context(audio_codec_ctx); // 释放音频编码器上下文 av_frame_free(video_frame); // 释放视频帧 av_frame_free(audio_frame); // 释放音频帧 swr_free(swr_ctx); // 释放重采样器 // 释放缓冲区 free(y_buf); free(u_buf); free(v_buf); free(audio_buf); // 关闭输入文件 fclose(sg_video_file); fclose(sg_audio_file); if (sg_video_pts_file) fclose(sg_video_pts_file); if (sg_audio_pts_file) fclose(sg_audio_pts_file); printf(Encoding completed! Output file: %s\n, output_file); return 0; }