1)前言
HLS复用器如下:
AVOutputFormat ff_hls_muxer = {
.name = "hls",
.long_name = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming"),
.extensions = "m3u8",
.priv_data_size = sizeof(HLSContext),
.audio_codec = AV_CODEC_ID_AAC,
.video_codec = AV_CODEC_ID_H264,
.subtitle_codec = AV_CODEC_ID_WEBVTT,
.flags = AVFMT_NOFILE | AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_NODIMENSIONS,
.init = hls_init,
.write_header = hls_write_header,
.write_packet = hls_write_packet,
.write_trailer = hls_write_trailer,
.priv_class = &hls_class,
};
注意:复用器定义了AVFMT_NOFILE,说明了不需要调用avio_open填充pb,也不需要调用avio_close释放资源
2)代码
int videoIndex = -1;
const char* pszFile = "D:/hls/1.mp4";
const char* pszRTMPURL = "F:/hls/home/index.m3u8";
AVFormatContext* pInputAVFormatContext = NULL;
AVOutputFormat* pAVOutputFormat = NULL;
//读取MP4文件
int nRet = avformat_open_input(&pInputAVFormatContext, pszFile, 0, NULL);
if (nRet < 0)
{
return avError(nRet);
}
//分析MP4中的视音频流数据
nRet = avformat_find_stream_info(pInputAVFormatContext, 0);
if (nRet != 0)
{
return avError(nRet);
}
av_dump_format(pInputAVFormatContext, 0, pszFile, 0);
AVFormatContext* pOutputAVFormatContext = NULL;
//指定输出文件,以及复用器类型
nRet = avformat_alloc_output_context2(&pOutputAVFormatContext, NULL, "hls", pszRTMPURL);
if (nRet < 0)
{
return avError(nRet);
}
pAVOutputFormat = pOutputAVFormatContext->oformat;
for (int i = 0; i < pInputAVFormatContext->nb_streams; i++)
{
AVStream* pInputAVStream = pInputAVFormatContext->streams[i];
AVStream* pOutputAVStream = avformat_new_stream(pOutputAVFormatContext, 0);
nRet = avcodec_parameters_copy(pOutputAVStream->codecpar, pInputAVStream->codecpar);
if (nRet < 0)
{
return avError(nRet);
}
pOutputAVStream->codecpar->codec_tag = 0;
}
for (int i = 0; i < pInputAVFormatContext->nb_streams; i++)
{
if (pInputAVFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoIndex = i;
break;
}
}
av_dump_format(pOutputAVFormatContext, 0, pszRTMPURL, 1);
//由于flags定义了AVFMT_NOFILE,不执行avio_open
if (!(pOutputAVFormatContext->oformat->flags & AVFMT_NOFILE))
{
nRet = avio_open(&pOutputAVFormatContext->pb, pszRTMPURL, AVIO_FLAG_WRITE);
if (nRet < 0)
{
return avError(nRet);
}
}
AVDictionary * opts = nullptr; av_dict_set_int(&opts, "hls_list_size", 0, 0);
av_dict_set_int(&opts, "hls_time", 60, 0);
//这里会创建打开索引文件
nRet = avformat_write_header(pOutputAVFormatContext, &opts);
if (nRet < 0)
{
return avError(nRet);
}
AVPacket pkt;
std::int64_t llStartTime = av_gettime();
std::int64_t llFrameIndex = 0;
while (true)
{
AVStream* pInputStream = NULL;
AVStream* pOutputStream = NULL;
nRet = av_read_frame(pInputAVFormatContext, &pkt);
if (nRet < 0)
{
break;
}
if (pkt.pts == AV_NOPTS_VALUE)
{
AVRational time_base1 = pInputAVFormatContext->streams[videoIndex]->time_base;
std::int64_t llCalcDuration = (double)AV_TIME_BASE / av_q2d(pInputAVFormatContext->streams[videoIndex]->r_frame_rate);
pkt.pts = (double)(llFrameIndex * llCalcDuration) / (double(av_q2d(time_base1)*AV_TIME_BASE));
pkt.dts = pkt.pts;
pkt.duration = (double)llCalcDuration / (double)(av_q2d(time_base1)*AV_TIME_BASE);
}
if (pkt.stream_index == videoIndex)
{
AVRational time_base = pInputAVFormatContext->streams[videoIndex]->time_base;
AVRational time_base_q = { 1, AV_TIME_BASE };
std::int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
std::int64_t now_time = av_gettime() - llStartTime;
AVRational avr = pInputAVFormatContext->streams[videoIndex]->time_base;
if (pts_time > now_time)
{
//av_usleep((unsigned int)(pts_time - now_time));
}
}
pInputStream = pInputAVFormatContext->streams[pkt.stream_index];
pOutputStream = pOutputAVFormatContext->streams[pkt.stream_index];
pkt.pts = av_rescale_q_rnd(pkt.pts, pInputStream->time_base, pOutputStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, pInputStream->time_base, pOutputStream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.duration = (int)av_rescale_q(pkt.duration, pInputStream->time_base, pOutputStream->time_base);
//字节流的位置,-1 表示不知道字节流位置
pkt.pos = -1;
if (pkt.stream_index == videoIndex)
{
llFrameIndex++;
}
nRet = av_interleaved_write_frame(pOutputAVFormatContext, &pkt);
if (nRet < 0) {
printf("发送数据包出错\n");
break;
}
//释放
av_packet_unref(&pkt);
}
av_write_trailer(pOutputAVFormatContext);
//不需要释放资源
if (pOutputAVFormatContext->oformat->flags & AVFMT_NOFILE)
{
avio_close(pOutputAVFormatContext->pb);
}
avformat_free_context(pOutputAVFormatContext);
avformat_close_input(&pInputAVFormatContext);
return 0;
3)总结
如果调用avio_open(&pOutputAVFormatContext->pb, pszRTMPURL, AVIO_FLAG_WRITE)在旧版本上没有任何问题,在新版本4.4会出现m3u8.tmp索引文件无法删除的问题