视音频复用器:从输入文件中挑出需要的视频/音频流,再合并放到一个文件中
不同封装格式中的数据存放方式不同,其差异需要一个av_bitstream_filter_filter(AVBitStreamFilterContext.....)进行转换
PS1:mp4(没有bsf)->annexb(有bsf)的转换需要用到名称为“h264_mp4toannexb”的bitstream filter。
【效果:1、Add SPS,PPS in front of IDR frame
2、Add start code ("0,0,0,1") in front of NALU】
PS2:对于某些封装格式(例如MP4/FLV/MKV等)中的AAC,需要用到名称为“aac_adtstoasc”的bitstream filter。
【应该某些编解码器只认toannexb,不认mp4格式】
/*
FIX: H.264 in some container format (FLV, MP4, MKV etc.) need
"h264_mp4toannexb" bitstream filter (BSF)
*Add SPS,PPS in front of IDR frame
*Add start code ("0,0,0,1") in front of NALU
H.264 in some container (MPEG2TS) don't need this BSF.
*/
/**
* 本程序可以将视频码流和音频码流打包到一种封装格式中。
* 程序中将AAC编码的音频码流和H.264编码的视频码流打包成MPEG2TS封装格式的文件。
* 需要注意的是本程序并不改变视音频的编码格式。 不涉及编解码 输入输出使用的是相同的编解码上下文
*/
#include <stdio.h>
#include <libavformat/avformat.h>
#define USE_H264BSF 0 //'1': Use H.264 Bitstream Filter
#define USE_AACBSF 0 //'1': Use AAC Bitstream Filter
int main(int argc, char* argv[])
{
AVFormatContext *ifmt_ctx_v = NULL, *ifmt_ctx_a = NULL,*ofmt_ctx = NULL;
AVPacket pkt;
int ret, i;
int videoindex_v=-1,videoindex_out=-1;
int audioindex_a=-1,audioindex_out=-1;
int frame_index=0;
int64_t cur_pts_v=0,cur_pts_a=0;
const char *in_filename_v = "cuc_ieschool.h264"; //itput file URL
const char *in_filename_a = "huoyuanjia.mp3"; //itput file URL
const char *out_filename = "cuc_ieschool.mp4"; //Output file URL
av_register_all();
//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
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
AVOutputFormat *ofmt = ofmt_ctx->oformat;
//Create output AVStream according to input AVStream
for (i = 0; i < ifmt_ctx_v->nb_streams; i++) {
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); //输入流的编解码器->->创建输出流的编解码器
videoindex_v=i;
videoindex_out=out_stream->index;
avcodec_copy_context(out_stream->codec, in_stream->codec)
out_stream->codec->codec_tag = 0;
//有些格式需要头head
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
break;
}
}
for (i = 0; i < ifmt_ctx_a->nb_streams; i++) {
if(ifmt_ctx_a->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO)
{
AVStream *in_stream = ifmt_ctx_a->streams[i];
AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
avcodec_copy_context(out_stream->codec, in_stream->codec)
audioindex_a=i;
audioindex_out=out_stream->index;
out_stream->codec->codec_tag = 0;
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
break;
}
}
//av_dump_format
//Open output file
if (!(ofmt->flags & AVFMT_NOFILE)) avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE)
avformat_write_header(ofmt_ctx, NULL) //Write file header
#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;
AVStream *in_stream, *out_stream;
//Get an AVPacket 要先判断应该去拿音频or视频?----------------
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){
AVRational time_base1=in_stream->time_base;
//Duration between 2 frames
//每帧所占时间间隔(如1800)=AV_TIME_BAS【#define AV_TIME_BASE 1000000】/帧率(如50帧/秒)
int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);
//Parameters
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;
}
}while(av_read_frame(ifmt_ctx, &pkt) >= 0);
}else{
break;
}
}
else
{
ifmt_ctx=ifmt_ctx_a;
stream_index=audioindex_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==audioindex_a){
//FIX:No PTS
//Simple Write PTS
if(pkt.pts==AV_NOPTS_VALUE){
//Write PTS
AVRational time_base1=in_stream->time_base;
//Duration between 2 frames (us)
int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(in_stream->r_frame_rate);
//Parameters
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_a=pkt.pts;
break;
}
}while(av_read_frame(ifmt_ctx, &pkt) >= 0);
}else{
break;
}
}
#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
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;
//printf("Write 1 Packet. size:%5d\tpts:%lld\n",pkt.size,pkt.pts);
av_interleaved_write_frame(ofmt_ctx, &pkt) //Write
av_free_packet(&pkt);
}
av_write_trailer(ofmt_ctx); //Write file trailer
#if USE_H264BSF
av_bitstream_filter_close(h264bsfc);
#endif
#if USE_AACBSF
av_bitstream_filter_close(aacbsfc);
#endif
end:
avformat_close_input(&ifmt_ctx_v);
avformat_close_input(&ifmt_ctx_a);
/* close output */
if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
avio_close(ofmt_ctx->pb);
avformat_free_context(ofmt_ctx);
if (ret < 0 && ret != AVERROR_EOF) {
printf( "Error occurred.\n");
return -1;
}
return 0;
}
h264有两种封装:
一、annexb模式,传统模式,有startcode,SPS和PPS是在ES中
二、mp4模式,一般mp4 mkv会有,没有startcode,SPS和PPS以及其它信息被封装在container中,每一个frame前面是这个frame的长度
很多解码器只支持annexb这种模式,因此需要将mp4做转换:
【在ffmpeg中用h264_mp4toannexb_filter可以做转换】
实现:初始化 + 过滤转换 + 释放关闭
1、注册AVBitStreamFilterContext
AVBitStreamFilterContext *h264bsfc;
if (ifmt_ctx->streams[i]->codec->codec_id == AV_CODEC_ID_H264)
{
//这里注意:"h264_mp4toannexb",一定是这个字符串<-<-定义于h264_mp4toannexb_bsf.c
h264bsfc = av_bitstream_filter_init("h264_mp4toannexb");
//Create and initialize a bitstream filter context given a bitstream filter name.
if (h264bsfc == NULL){printf("H264 mp4 to annexb失败!\n");return;}
}
2、使用AVBitStreamFilterContext转换AVpacket:
/*
*每个AVPacket的data添加了H.264的NALU的起始码{0,0,0,1}
*每个IDR帧数据前面添加了SPS和PPS
*/
/*也可以av_bitstream_filter_filter的处理源m&目标均为pkt 这样比较简洁*/
AVPacket pkt;
AVPacket TmpPkt;
av_read_frame(ifmt_ctx, &pkt);
if (VideoIndex == pkt.stream_index)
{
uint8_t *out_data = NULL;
int out_size = 0;
av_bitstream_filter_filter(h264bsfc,
ofmt_ctx->streams[pkt.stream_index]->codec, NULL,
&out_data, &out_size, //填充后的数据指针
pkt.data, pkt.size, pkt.flags & AV_PKT_FLAG_KEY); //处理前的指针
av_init_packet(&TmpPkt);
av_packet_copy_props(&TmpPkt, &pkt);
av_packet_from_data(&TmpPkt, out_data, out_size);
TmpPkt.size = out_size;
av_packet_unref(&pkt);
av_copy_packet(&pkt, &TmpPkt);
av_packet_unref(&TmpPkt);
}
3、释放AVBitStreamFilterContext
if (h264bsfc != NULL)
{
av_bitstream_filter_close(h264bsfc);
h264bsfc = NULL;
}