1、在编码过程中涉及到一些时间戳相关概念主要:
PTS:度量解码后的视频帧什么时候被显示出来;
DTS:读入内存中的bit流在什么时候开始送入解码器中进行解码;
duration:表示当前帧的持续时间占多少个时间单位;
相关参数的理解可以参考:https://www.jianshu.com/p/1bc03e97004e/
2、实现流程图:
3、程序实现:
void MainWindow::h264Tomp4()
{
AVFormatContext* pInFmtCtx = nullptr;
int nInStreamIndex = -1;
AVFormatContext* pOutFmtCtx = nullptr;
AVStream* pOutVStream = nullptr;
int nOutStreamIndex = -1;
AVCodecContext* pOutCodecCtx = nullptr;
do{
//打开输入文件,并填充AVFormatContext上下文
QString strInFile = "in.h264";
if(avformat_open_input(&pInFmtCtx, strInFile.toStdString().c_str(), NULL, NULL) < 0)
{
qDebug() << "cannot open input file." << endl;
break;
}
//查找输入文件中的流信息
if(avformat_find_stream_info(pInFmtCtx, NULL) < 0){
qDebug() << "cannot find stream info." << endl;
break;
}
//查找视频流在流信息中的索引的索引
for(size_t i = 0;i < pInFmtCtx->nb_streams;i++){
if(pInFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
nInStreamIndex = (int)i;
break;
}
}
//获取输入视频流的编码器参数
AVCodecParameters *codecPara = pInFmtCtx->streams[nInStreamIndex]->codecpar;
//创建AVFormatContext上下文
QString strOutFile = "out.mp4";
if(avformat_alloc_output_context2(&pOutFmtCtx, NULL, NULL, strOutFile.toStdString().c_str()) < 0){
qDebug() << "cannot alloc outfile context." << endl;
break;
}
//打开输出文件并填充AVFormatContext上下文数据
if(avio_open(&pOutFmtCtx->pb, strOutFile.toStdString().c_str(), AVIO_FLAG_READ_WRITE) < 0){
qDebug() << "cannot open output file." << endl;
break;
}
//为输出AVFormatContext上下文添加视频流,并设置参数
if(nullptr == (pOutVStream = avformat_new_stream(pOutFmtCtx, NULL)))
{
qDebug() << "cannot new output stream." << endl;
break;
}
pOutVStream->time_base.den = 25;
pOutVStream->time_base.num = 1;
nOutStreamIndex = pOutVStream->index;
//mp4中的视频也是h264编码,所以输入和输出编码器一致:
//查找编码器
AVCodec* outCodec = const_cast<AVCodec*>(avcodec_find_encoder(codecPara->codec_id));
if(outCodec==NULL){
printf("Cannot find any encoder.\n");
break;
}
//创建输出编码器上下文
pOutCodecCtx = avcodec_alloc_context3(outCodec);
AVCodecParameters* outCodecPara = pOutFmtCtx->streams[pOutVStream->index]->codecpar;
if(avcodec_parameters_copy(outCodecPara, codecPara)<0){
qDebug() << "cannot copy codecparam";
break;
}
if(avcodec_parameters_to_context(pOutCodecCtx, outCodecPara)<0){
qDebug() << "avcodec_parameters_to_context failed";
break;
}
pOutCodecCtx->time_base.den=25;
pOutCodecCtx->time_base.num=1;
//打开输出流编码器
if(avcodec_open2(pOutCodecCtx,outCodec,NULL) < 0){
qDebug() << "Cannot open output codec";
break;
}
//写入文件头
if(avformat_write_header(pOutFmtCtx, NULL) < 0){
qDebug() << "Cannot write header to file";
break;
}
//
AVPacket pkt;
std::int64_t llFrameIndex = 0;
AVStream* inVStream = pInFmtCtx->streams[nInStreamIndex];
//循环读取帧
while(av_read_frame(pInFmtCtx, &pkt) >= 0){
//判断是视频流
if(pkt.stream_index == nInStreamIndex){
if(pkt.pts == AV_NOPTS_VALUE){
//PTS:度量解码后的视频帧什么时候被显示出来
//DTS:读入内存中的bit流在什么时候开始送入解码器中进行解码
//获取时间基:即用来度量单位刻度时间
AVRational time_base1 = inVStream->time_base;
//计算两帧视频之间的实际间隔时间,单位是us
int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(inVStream->r_frame_rate);
//计算PTS,即经过多少个刻度显示该帧视频
pkt.pts = (double)(llFrameIndex*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);
llFrameIndex++;
}
//将输入流中时间基的PTS转换到输出流时间基中的PTS
pkt.pts = av_rescale_q_rnd(pkt.pts, inVStream->time_base, pOutVStream->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
//将输入流中时间基的DTS转换到输出流时间基中的DTS
pkt.dts = av_rescale_q_rnd(pkt.dts, inVStream->time_base, pOutVStream->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
//将输入流中时间基的duration转换到输出流时间基中的duration
pkt.duration = av_rescale_q(pkt.duration, inVStream->time_base, pOutVStream->time_base);
pkt.pos = -1;
pkt.stream_index = nOutStreamIndex;
//写入一帧数据
if (av_interleaved_write_frame(pOutFmtCtx, &pkt) < 0) {
break;
}
av_packet_unref(&pkt);
}
}
av_write_trailer(pOutFmtCtx);
}while(0);
if(pOutCodecCtx)
{
avcodec_close(pOutCodecCtx);
avcodec_free_context(&pOutCodecCtx);
}
if(pInFmtCtx)
{
avformat_free_context(pInFmtCtx);
}
if(pOutFmtCtx)
{
if(pOutFmtCtx->pb)
{
avio_close(pOutFmtCtx->pb);
}
avformat_free_context(pOutFmtCtx);
}
}
4、下面使用自己写的播放器播放一下MP4文件: