FFmpeg实现音视频转码
以下是基于 FFmpeg 库实现 MP4 转码的详细步骤(以 C 语言为例):
一、环境准备
集成 FFmpeg 库
· 编译 FFmpeg 生成动态库(avformat、avcodec、avutil、swscale、swresample等)
· 在 SDK 项目中配置头文件路径和库文件链接
核心数据结构
· AVFormatContext:封装格式上下文(输入 / 输出文件)
· AVCodecContext:编解码器上下文
· AVCodec:编码器 / 解码器
· AVPacket:存储压缩的音视频数据
· AVFrame:存储未压缩的音视频数据
二、转码核心步骤
1. 初始化 FFmpeg
// 注册所有组件(旧版本需要,新版本可省略)
av_register_all();
// 初始化网络模块(如需网络流)
avformat_network_init();
2. 打开输入文件
AVFormatContext *input_ctx = NULL;
const char *input_path = "input.mp4";// 打开输入文件并读取封装格式信息
int ret = avformat_open_input(&input_ctx, input_path, NULL, NULL);
if (ret != 0) {// 错误处理:av_strerror(ret, err_msg, sizeof(err_msg))return -1;
}
// 读取流信息(音视频轨道等)
ret = avformat_find_stream_info(input_ctx, NULL);
if (ret < 0) {// 错误处理avformat_close_input(&input_ctx);return -1;
}
3. 查找输入流的编码器
// 查找视频流和音频流的索引
int video_stream_idx = av_find_best_stream(input_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
int audio_stream_idx = av_find_best_stream(input_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);// 获取输入视频流和解码器
AVStream *input_video_stream = input_ctx->streams[video_stream_idx];
AVCodec *input_video_codec = avcodec_find_decoder(input_video_stream->codecpar->codec_id);
AVCodecContext *input_video_ctx = avcodec_alloc_context3(input_video_codec);
avcodec_parameters_to_context(input_video_ctx, input_video_stream->codecpar);
avcodec_open2(input_video_ctx, input_video_codec, NULL); // 打开解码器// 音频流同理
4. 创建输出文件上下文
AVFormatContext *output_ctx = NULL;
const char *output_path = "output.mp4";// 分配输出上下文(根据输出路径自动推断封装格式)
avformat_alloc_output_context2(&output_ctx, NULL, NULL, output_path);
if (!output_ctx) {// 错误处理return -1;
}// 打开输出文件(本地文件用avio_open)
if (!(output_ctx->oformat->flags & AVFMT_NOFILE)) {ret = avio_open(&output_ctx->pb, output_path, AVIO_FLAG_WRITE);if (ret < 0) {// 错误处理return -1;}
}
5. 配置输出流编码器
以视频流为例(音频流类似):
// 创建输出视频流
AVStream *output_video_stream = avformat_new_stream(output_ctx, NULL);
// 设置输出编码器(如H.264)
AVCodec *output_video_codec = avcodec_find_encoder(AV_CODEC_ID_H264);
AVCodecContext *output_video_ctx = avcodec_alloc_context3(output_video_codec);// 配置编码器参数
output_video_ctx->width = input_video_ctx->width; // 宽度
output_video_ctx->height = input_video_ctx->height; // 高度
output_video_ctx->pix_fmt = output_video_codec->pix_fmts[0]; // 像素格式(如YUV420P)
output_video_ctx->bit_rate = 2000000; // 比特率(2Mbps)
output_video_ctx->time_base = (AVRational){1, 25}; // 时间基(帧率25fps)
output_video_ctx->framerate = (AVRational){25, 1};// 打开输出编码器
ret = avcodec_open2(output_video_ctx, output_video_codec, NULL);
if (ret < 0) {// 错误处理return -1;
}// 将编码器参数复制到输出流
avcodec_parameters_from_context(output_video_stream->codecpar, output_video_ctx);
output_video_stream->time_base = output_video_ctx->time_base;
6. 写入输出文件头
ret = avformat_write_header(output_ctx, NULL);
if (ret < 0) {// 错误处理return -1;
}
7. 转码主循环
AVPacket *pkt = av_packet_alloc(); // 输入数据包
AVFrame *frame = av_frame_alloc(); // 解码后的帧
AVFrame *output_frame = av_frame_alloc(); // 编码前的帧(可能需要格式转换)while (av_read_frame(input_ctx, pkt) >= 0) { // 读取输入数据包if (pkt->stream_index == video_stream_idx) {// 解码视频avcodec_send_packet(input_video_ctx, pkt);while (avcodec_receive_frame(input_video_ctx, frame) == 0) {// 格式转换(如像素格式转换)sws_scale(/* 转换参数 */);// 编码avcodec_send_frame(output_video_ctx, output_frame);while (avcodec_receive_packet(output_video_ctx, pkt) == 0) {// 调整时间戳pkt->stream_index = output_video_stream->index;av_packet_rescale_ts(pkt, input_video_stream->time_base, output_video_stream->time_base);// 写入输出文件av_interleaved_write_frame(output_ctx, pkt);av_packet_unref(pkt);}}} else if (pkt->stream_index == audio_stream_idx) {// 音频转码(类似视频流程,使用swresample处理音频格式)}av_packet_unref(pkt);
}// 刷新编码器缓存
avcodec_send_frame(output_video_ctx, NULL);
while (avcodec_receive_packet(output_video_ctx, pkt) == 0) {av_interleaved_write_frame(output_ctx, pkt);av_packet_unref(pkt);
}
8. 清理资源
// 写入文件尾
av_write_trailer(output_ctx);// 释放上下文
avcodec_free_context(&input_video_ctx);
avcodec_free_context(&output_video_ctx);
avformat_close_input(&input_ctx);
if (!(output_ctx->oformat->flags & AVFMT_NOFILE)) {avio_closep(&output_ctx->pb);
}
avformat_free_context(output_ctx);// 释放帧和数据包
av_frame_free(&frame);
av_frame_free(&output_frame);
av_packet_free(&pkt);
三、关键注意事项
错误处理:每个 FFmpeg API 调用都需检查返回值,使用av_strerror()解析错误信息。
格式转换:
· 视频:使用sws_scale()转换像素格式 / 分辨率
· 音频:使用swr_convert()转换采样率 / 声道数
时间戳同步:转码时需通过av_packet_rescale_ts()调整时间戳,避免音视频不同步。
编码器参数优化:
· 视频:可设置crf参数(恒定质量模式)替代固定比特率
· 音频:设置采样率(如 44100Hz)和声道数(如立体声)
线程安全:FFmpeg 部分 API 非线程安全,多线程转码需加锁或使用独立上下文。
四、SDK 封装建议
提供高层 API:如transcode_mp4(const char* input, const char* output, TranscodeParam* param)
支持参数配置:分辨率、比特率、帧率、编码格式等
回调机制:转码进度、错误信息通过回调函数通知上层
跨平台适配:处理 Windows/Linux/macOS 的库依赖和路径问题
通过以上步骤,可构建一个基础的 MP4 转码 SDK,实际开发中需根据需求扩展功能(如多格式支持、硬件加速等)。