当前位置: 首页 > news >正文

基于ffmpeg,实现对yuv格式的视频及pcm格式的音频数据编码

1、通过av_register_all(ffmpeg 4.0+已废弃,直接调用avformat_open_input函数即可,avformat_open_input函数内部会做初始化判断)函数和avformat_network_init函数初始化FFmpeg;

2、打开输入文件(原始的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; }
http://www.jsqmd.com/news/1336639/

相关文章:

  • 揭秘asp网站建设 文献中的那些被忽视的技术细节与实战心得
  • Linux下CANFD与经典CAN配置实战:从SocketCAN驱动到数据收发调试
  • HarmonyOS 7 / API 26 折叠屏适配实战:窗口断点、双栏切换和状态保留一次验清
  • 2026.7.13(5)【图片隐写】镜子里面的世界
  • 唐山母婴除甲醛公司甲醛检测测评推荐:康之居母婴除甲醛标准、流程、避坑指南 - CMA甲醛检测中心
  • 从ISO到可运行系统:详解操作系统安装全流程与避坑指南
  • 跨境卖家必看:批量图片翻译与视频字幕翻译工具推荐
  • Excel VLOOKUP函数从入门到精通:跨表匹配数据与常见错误排查
  • 构建安全可追溯的AIGC应用:大模型API集成与工程实践指南
  • 网站建设费计入什么科目深度解析与实操指南:从财务合规到税务筹划的全方位解答
  • 具身智能推理链路:视觉识别→语义解析→运动规划→机械臂执行
  • js async
  • 国家中小学智慧教育平台电子课本下载工具:三步实现教育资源高效管理
  • 基于 RK3588 的多输入视频 AI 推理流水线:YOLO 车牌检测、OCR、WebRTC 推流与边缘联动
  • Deskreen屏幕共享终极指南:3分钟快速上手多屏协作
  • 2026高性价比树洞测评!零隐形消费情绪陪伴平台 - nuanyin
  • DirBridge:从远程文件管理到内嵌 SSH 终端
  • 冷暖一体机哪个品牌适合自建房安装:【芬尼】自建房优选 - 17328623207
  • 计算机毕业设计基于知识图谱(Neo4j)和大语言模型(LLM)的图检索增强(GraphRAG)的地质矿产知识管理智能问答系统 人工智能 大模型毕业设计(源码+文档+PPT+讲解)
  • Hive性能调优实战:从基础配置到高级技巧
  • 有哪些靠谱、不容易翻车的 AI 毕业论文辅助工具推荐?
  • 卷积神经网络(CNN)原理详解:从核心组件到PyTorch实战
  • 如何用Teable在5分钟内搭建企业级数据协作平台?终极指南
  • H3C交换机Console登录与SSH远程管理配置全流程详解
  • 玩客云(S805)刷 Armbian + CasaOS 完整全流程
  • Python数据分析实战:从增速放缓预测内容增长潜力
  • 大户型家用冷暖系统推荐哪个品牌:【芬尼】大户型适配 - 17728181569
  • SM2是中国国家密码管理局发布的基于椭圆曲线密码学(ECC)的非对称加密算法标准
  • 嵌入式系统时钟配置实战:从原理到低功耗调试全解析
  • php8 常量折叠