前言:
本文是下面blog的笔记并增加了步骤解析和注释.
muxer和demux原理和使用的函数一样,audio,video是一样的,所以4种情况分析一种即可.
最简单的基于FFmpeg的封装格式处理:视音频复用器(muxer)_雷霄骅(leixiaohua1020)的专栏-CSDN博客_muxer
正文
上图是raw流,压缩,文件封装,流媒体封装的流程
原理
视音频复用器(Muxer)即是将视频压缩数据(例如H.264)和音频压缩数据(例如AAC)合并到一个封装格式数据(例如MKV)中去。
如图所示。在这个过程中并不涉及到编码和解码。
本文记录的程序将一个H.264编码的视频码流文件和一个MP3编码的音频码流文件,合成为一个MP4封装格式的文件。
流程图
- 1: 打开输入文件和codec
avformat_open_input():,avformat_find_stream_info- 2 : 拷贝new context( avcodec_copy_context)
- 3 : 打开输出文件 avio_open-->avformat_write_header
- 4:av_compare_ts 音视频分离
- 5: 写pts :
在编码之前写pts,否则编码不会产生pts
- 6: av_bitstream_filter_filter(h264bsfc...)
使用bitstream filter add h264 header 0x00 00 00 01- 7 Convert PTS/DTS //转不同时间基对应的pts 和响应的duration
- 8 : av_interleaved write_frame 向文件写一帧
本文介绍的视音频复用器,输入的视频不一定是H.264裸流文件,音频也不一定是纯音频文件。
可以选择两个封装过的视音频文件作为输入。程序会从视频输入文件中“挑”出视频流,音频输入文件中“挑”出音频流,再将“挑选”出来的视音频流复用起来。
PS1:对于某些封装格式(例如MP4/FLV/MKV等)中的H.264,需要用到名称为“h264_mp4toannexb”的bitstream filter。
PS2:对于某些封装格式(例如MP4/FLV/MKV等)中的AAC,需要用到名称为“aac_adtstoasc”的bitstream filter。
代码
int main(int argc, char* argv[])
{
const char *in_filename_v = "cuc_ieschool.h264";
const char *in_filename_a = "huoyuanjia.mp3";
const char *out_filename = "cuc_ieschool.mp4";//Output file URL
//Input
avformat_open_input(&ifmt_ctx_v, in_filename_v, 0, 0));
avformat_find_stream_info(ifmt_ctx_v, 0));
avformat_open_input(&ifmt_ctx_a, in_filename_a, 0, 0));
avformat_find_stream_info(ifmt_ctx_a, 0));
//Output //1 avformat_new_stream & avcodec_copy_context
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
ofmt = ofmt_ctx->oformat;
for (i = 0; i < ifmt_ctx_v->nb_streams; i++) {
//Create output AVStream according to input AVStream
if(ifmt_ctx_v->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
AVStream *in_stream = ifmt_ctx_v->streams[i];
AVStream *out_stream = avformat_new_stream(ofmt_ctx,in_stream->codec->codec);
//Copy the settings of AVCodecContext
avcodec_copy_context(out_stream->codec, in_stream->codec);
......
break;
}
}
//audio is the same, skip
......
printf("==========Output Information==========\n");
//Open output file
avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE) ;
//Write file header
avformat_write_header(ofmt_ctx, NULL);
//FIX
#if USE_H264BSF
AVBitStreamFilterContext* h264bsfc = av_bitstream_filter_init("h264_mp4toannexb");
#endif
#if USE_AACBSF
AVBitStreamFilterContext* aacbsfc = av_bitstream_filter_init("aac_adtstoasc");
#endif
while (1) {
AVFormatContext *ifmt_ctx;
int stream_index=0;
AVStream *in_stream, *out_stream;
//Get an AVPacket //2 av_compare_ts比较音视频pts,大于0表示视频帧在前,音频需要连续编码。小于0表示,音频帧在前,应该至少编码一帧视频
if(av_compare_ts(cur_pts_v,ifmt_ctx_v->streams[videoindex_v]->time_base,cur_pts_a,ifmt_ctx_a->streams[audioindex_a]->time_base) <= 0){
ifmt_ctx=ifmt_ctx_v;
stream_index=videoindex_out;
if(av_read_frame(ifmt_ctx, &pkt) >= 0){
do{
in_stream = ifmt_ctx->streams[pkt.stream_index];
out_stream = ofmt_ctx->streams[stream_index];
if(pkt.stream_index==videoindex_v){
//FIX:No PTS (Example: Raw H.264)
//Simple Write PTS
if(pkt.pts==AV_NOPTS_VALUE){
//Write PTS
AVRational time_base1=in_stream->time_base;
//Duration between 2 frames (us)//按ffmpeg中的1秒(即90000)来计算每帧的间隔;90000 / 25 = 3600(ffmpeg)
int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);
//Parameters//计算一桢在整个视频中的时间位置timestamp(秒) = pts * av_q2d(st->time_base);
pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
pkt.dts=pkt.pts;
pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
frame_index++;
}
cur_pts_v=pkt.pts;
//注意:读取一帧之后break
break;
}
}while(av_read_frame(ifmt_ctx, &pkt) >= 0);
}
else { //skip audio
...
}
}
//3 FIX:Bitstream Filter
#if USE_H264BSF
av_bitstream_filter_filter(h264bsfc, in_stream->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);
#endif
#if USE_AACBSF
av_bitstream_filter_filter(aacbsfc, out_stream->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);
#endif
//Convert PTS/DTS //转不同时间基对应的pts
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.pos = -1;
pkt.stream_index=stream_index;
//Write
av_interleaved_write_frame(ofmt_ctx, &pkt);
av_free_packet(&pkt);
}
//Write file trailer
av_write_trailer(ofmt_ctx);
....}
- 1:打开输入文件和codec
avformat_open_input(),avformat_find_stream_info... - 2 : 拷贝new context( avcodec_copy_context)
1) 根据codec codec_type 分流audio 和video .
2) 创建new stream
3) 拷贝context到new stream的context (avcodec_copy_context).
audio,video不同stream对应的context不同,
对应上面流程图的acodec_copy_context-->AVCodecContext部分.
avcodec_copy_context作用是复制AVCodecContext的设置(Copy the settings of AVCodecContext)
常用于codec设置拷贝avcodec_copy_context(out_stream->codec, in_stream->codec)
audio,video原理一样 - 3 : 打开输出文件 avio_open-->avformat_write_header
- 4: av_compare_ts比较音视频pts,大于0表示视频帧在前,音频需要连续编码。小于0表示,音频帧在前,应该至少编码一帧视频.
video: av_read_frame-->Get AVPacket
输入的视频不一定是H.264裸流文件,可以选择封装过的视音频文件作为输入。
av_read_frame可以解封装.
读取一帧之后会break,在外层循环读下一帧
audio,video原理一样 - 5: 写pts :
在编码之前写pts,否则编码不会产生pts
//FIX:No PTS (Example: Raw H.264)
//Simple Write PTS
if(pkt.pts==AV_NOPTS_VALUE){
//Write PTS
AVRational time_base1=in_stream->time_base;
//Duration between 2 frames (us)//按ffmpeg中的1秒(即90000)来计算每帧的间隔;90000 / 25 = 3600(ffmpeg)
int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);
//Parameters//计算一桢在整个视频中的时间位置timestamp(秒) = pts * av_q2d(st->time_base);
pkt.pts=(double)
(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
pkt.dts=pkt.pts;
pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
frame_index++;
}
- 6: av_bitstream_filter_filter(h264bsfc...)
使用bitstream filter add h264 header 0x00 00 00 01
详见:最简单的基于FFmpeg的封装格式处理:视音频分离器简化版(demuxer-simple)_雷霄骅(leixiaohua1020)的专栏-CSDN博客_ffmpeg 封装 "分离某些封装格式中的H.264"部分 - 7 Convert PTS/DTS //转不同时间基对应的pts 和响应的duration
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);
时间基转换详见:https://blog.csdn.net/fdsafwagdagadg6576/article/details/122844785
3.6 转封装过程中的时间基转换
- 8 : av_interleaved write_frame 向文件写一帧
小结: 对于文件格式的转换,业务逻辑是使用封装好的函数读写,不需要自己根据协议实现文件格式。比如:调用avio_open,av_interleaved_write_frame,实现文件格式转换。
自己只需做时间戳转换和bsf 添加nalu.