以下是基于 FFmpeg 库实现 MP4 转码的详细步骤(以 C 语言为例):
一、环境准备
集成 FFmpeg 库
· 编译 FFmpeg 生成动态库(avformat、avcodec、avutil、swscale、swresample等)
· 在 SDK 项目中配置头文件路径和库文件链接
核心数据结构
· AVFormatContext:封装格式上下文(输入 / 输出文件)
· AVCodecContext:编解码器上下文
· AVCodec:编码器 / 解码器
· AVPacket:存储压缩的音视频数据
· AVFrame:存储未压缩的音视频数据
二、转码核心步骤
1. 初始化 FFmpeg
c
// 注册所有组件(旧版本需要,新版本可省略)
av_register_all();
// 初始化网络模块(如需网络流)
avformat_network_init();
2. 打开输入文件
c
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. 查找输入流的编码器
c
// 查找视频流和音频流的索引
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. 创建输出文件上下文
c
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. 配置输出流编码器
以视频流为例(音频流类似):
c
// 创建输出视频流
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. 写入输出文件头
c
ret = avformat_write_header(output_ctx, NULL);
if (ret < 0) {
// 错误处理
return -1;
}
7. 转码主循环
c
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. 清理资源
c
// 写入文件尾
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,实际开发中需根据需求扩展功能(如多格式支持、硬件加速等)。