基于FFmpeg将H.264视频流封装为MP4格式
一、项目背景
服务器的核心目标是:响应客户端的 “检测” 指令,检测声学故障场景中,一旦触发声学告警(比如分贝过高),按指定的 MP4 文件名(时间戳命名,如2026-01-10-15-46-58.mp4)持续保存摄像头采集的 H264 视频帧到文件,直到客户端发出 “结束检测” 指令。
H.264是一种裸视频流文件, windows自带的播放器没有办法直接打开它,它就像是一份纯文字稿件,没有索引,没有标题,没有页码,虽然内容都在,但是读起来不方便。时间戳(PTS/DTS)
ffmpeg -f lavfi -i testsrc=duration=5:size=960x408:rate=25 -c:v libx264 -f h264 test.h264
#include <stdio.h>
#include <stdlib.h>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
}
int main(int argc, char *argv[]) {
AVFormatContext *in_fmt_ctx = NULL;
const char *input_filename = "test.h264"; // H264文件
int video_stream_idx = -1;
// 1. 打开输入文件
if (avformat_open_input(&in_fmt_ctx, input_filename, NULL, NULL) < 0) {
fprintf(stderr, "无法打开输入文件: %s\n", input_filename);
return -1;
}
// 2. 探测流信息 解析出视频宽高、帧率、编码格式、SPS/PPS
if (avformat_find_stream_info(in_fmt_ctx, NULL) < 0) {
fprintf(stderr, "无法从输入文件中提取流信息\n");
avformat_close_input(&in_fmt_ctx);
return -1;
}
// 3. 找到视频流
for (int i = 0; i < in_fmt_ctx->nb_streams; i++) {
if (in_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream_idx = i;
break;
}
}
if (video_stream_idx == -1) {
fprintf(stderr, "未找到视频流\n");
avformat_close_input(&in_fmt_ctx);
return -1;
}
av_dump_format(in_fmt_ctx, 0, input_filename, 0);
// ==================== 输出 MP4 ====================
AVFormatContext *out_fmt_ctx = NULL;
const char *output_filename = "output.mp4";
avformat_alloc_output_context2(&out_fmt_ctx, NULL, NULL, output_filename);
if (!out_fmt_ctx) {
fprintf(stderr, "无法分配输出上下文\n");
avformat_close_input(&in_fmt_ctx);
return -1;
}
AVStream *out_stream = avformat_new_stream(out_fmt_ctx, NULL);
if (!out_stream) {
fprintf(stderr, "无法创建输出流\n");
avformat_close_input(&in_fmt_ctx);
avformat_free_context(out_fmt_ctx);
return -1;
}
// 复制编码参数
AVCodecParameters *in_codecpar = in_fmt_ctx->streams[video_stream_idx]->codecpar;
//把输入流的所有解码信息复制给输出流
avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
// 时间基
out_stream->time_base = (AVRational){1, 25};
// 打开输出文件
if (!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
if (avio_open(&out_fmt_ctx->pb, output_filename, AVIO_FLAG_WRITE) < 0) {
fprintf(stderr, "无法打开输出文件\n");
avformat_close_input(&in_fmt_ctx);
avformat_free_context(out_fmt_ctx);
return -1;
}
}
// 写入文件头
avformat_write_header(out_fmt_ctx, NULL);
AVPacket *pkt = av_packet_alloc();
AVStream *in_stream = in_fmt_ctx->streams[video_stream_idx];
int frame_count = 0;
// 读取 + 封装
//读取一帧 H.264,装进 AVPacket, AVPacket 包含:帧数据 时间戳 关键帧标记
while (av_read_frame(in_fmt_ctx, pkt) >= 0) {
if (pkt->stream_index == video_stream_idx) {
// 自动生成PTS(裸流必备),H.264 裸流没有时间戳,必须手动生成
if (pkt->pts == AV_NOPTS_VALUE) {
AVRational frame_rate = in_stream->r_frame_rate;
int64_t duration = AV_TIME_BASE / av_q2d(frame_rate);
pkt->pts = frame_count * duration / (av_q2d(in_stream->time_base) * AV_TIME_BASE);
pkt->dts = pkt->pts;
pkt->duration = duration / (av_q2d(in_stream->time_base) * AV_TIME_BASE);
frame_count++;
}
// 时间戳转换
pkt->pts = av_rescale_q_rnd(pkt->pts, in_stream->time_base, out_stream->time_base,
(AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt->dts = av_rescale_q_rnd(pkt->dts, in_stream->time_base, out_stream->time_base,
(AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt->duration = av_rescale_q(pkt->duration, in_stream->time_base, out_stream->time_base);
pkt->stream_index = out_stream->index;
pkt->pos = -1;
//把帧写入MP4
av_interleaved_write_frame(out_fmt_ctx, pkt);
}
//写入 MP4 文件结束标记
av_packet_unref(pkt);
}
// 写入尾
av_write_trailer(out_fmt_ctx);
// 释放资源
av_packet_free(&pkt);
if (out_fmt_ctx && !(out_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&out_fmt_ctx->pb);
}
avformat_close_input(&in_fmt_ctx);
avformat_free_context(out_fmt_ctx);
printf("MP4 封装成功!output.mp4\n");
return 0;
}
流程图
1. 打开 H264
2. 读取流信息(拿 SPS/PPS)
3. 创建 MP4
4. 复制 codecpar(复制 SPS/PPS)
5. 写 MP4 头
6. 循环读帧 → AVPacket
7. 生成时间戳
8. 写入帧
9. 写 MP4 尾
三、转换失败的原因
- 没有 SPS / PPS,MP4 必须要有 SPS/PPS 才能解码
- 原始 H.264 是没有时间戳(PTS/DTS)的,必须手动生成

浙公网安备 33010602011771号