由于项目一直在忙,刚抽出空来,在这期间身体有些不适,这段时间中有一段时间感觉自己有上班恐惧症,一到工作环境中,不自然的就会有压力、焦虑;最后去医院看了医生,把情况给医生说了之后,医生认为是内脏比较敏感,压力和焦虑会在身体上有所体现,开了些药,回到家后妻子也是一直在开导,现在已经好了,在这里要感谢我妻子的开导和陪伴。也希望大家工作顺利,有个好心情。言归正传,这里记录一下在开发Android NDK模式下使用FFempg开发音视频解码和转流功能。
先说第一部分吧,FFmpeg接收网络流,在开发前期并没有去使用网络流,而是使用的本地文件作为推流的源,大家也可以先试试推送本地文件,然后再推送网络流,如rtsp流等。如何编译FFmpeg就不介绍了,网络上自己找一下吧,教程挺多的;这里说一下接收网络流时调用ffmpeg的流程:
(1)第一步就是找到一个能够使用的rtsp流地址,由于公司内网有rtsp服务器,我这边比较方便获取,大家可以在网上自己找一些开放的rtsp流地址使用。也可以使用海康球机自带的rtsp流,这里给大家找了几个:
rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov
rtmp://58.200.131.2:1935/livetv/cctv1
(2)初始化FFmpeg封装器,初始化网络库,解析本地文件时不需要初始化网络库,由于这里拉取的是rtsp网络流,所以需要添加网络库。
av_register_all();
avformat_network_init();
(3)设置接收流的方式及参数,ffmpeg会默认使用udp去通信,想用tcp的同学需要注意,在参数设置中“rtsp_transport”为“tcp”。
int res = 0;
//打开文件,解封装文件头
//输入封装上下文
AVFormatContext* ictx = nullptr;
//设置rtsp协议延时最大值
AVDictionary *opts = nullptr;
av_dict_set(&opts, "rtsp_transport", "tcp", 0);
av_dict_set(&opts, "max_delay", "500", 0);
if ((res = avformat_open_input(&ictx, inUrl, nullptr, &opts)) != 0)
return Error(res);
(4)获取音视频流输入信息,avformat_find_stream_info()该函数可以读取一部分视音频数据并且获得一些相关的信息。avformat_find_stream_info()的声明位于libavformat\avformat.h。 av_dump_format()打印关于输入或输出格式的详细信息,最后一个参数为0代表打印输入信息,1代表打印输出信息。
if ((res = avformat_find_stream_info(ictx, nullptr)) < 0)
return Error(res);
av_dump_format(ictx, 0, inUrl, 0);
(5)创建的AVFormatContext结构体,并让ffmpeg推理出输出格式,这里指定了输出格式为"flv",倒数第二个参数也可以填写NULL,通过最后一个参数,让ffmpeg自己推导出flv。
//创建输出上下文
AVFormatContext* octx = nullptr;
if ((res = avformat_alloc_output_context2(&octx, nullptr, "flv", outUrl) < 0))
return Error(res);
(6)配置输出流
for (int i = 0; i < ictx->nb_streams; ++i)
{
//创建输出流
AVStream* out = avformat_new_stream(octx, ictx->streams[i]->codec->codec);
if (out == nullptr)
{
printf("new stream error.\n");
return -1;
}
//复制配置信息
if ((res = avcodec_copy_context(out->codec, ictx->streams[i]->codec)) != 0)
return Error(res);
//out->codec->codec_tag = 0;//标记不需要重新编解码
}
av_dump_format(octx, 0, outUrl, 1);
第二部分就是使用ffmpeg推送rtmp流,这一部分重点在于重新设置视频流的时间戳、pts、dts,这一部分也是在网上参考的,非常感谢博主。
(1)打开IO输出并写入头文件
res = avio_open(&octx->pb, outUrl, AVIO_FLAG_WRITE);
if (octx->pb == nullptr)
return Error(res);
//写入头信息
//avformat_write_header可能会改变流的timebase
if ((res = avformat_write_header(octx, nullptr)) < 0)
return Error(res);
(2)转化pts、dts、duration并推送rtmp流。
long long begintime = av_gettime();
long long realdts = 0;
long long caldts = 0;
AVPacket pkt;
m_thread_status = true;
while (m_thread_status)
{
if ((res = av_read_frame(ictx, &pkt)) != 0)
break;
if(pkt.size <= 0)//读取rtsp时pkt.size可能会等于0
continue;
//转换pts、dts、duration
pkt.pts = pkt.pts * av_q2d(ictx->streams[pkt.stream_index]->time_base) / av_q2d(octx->streams[pkt.stream_index]->time_base);
pkt.dts = pkt.dts * av_q2d(ictx->streams[pkt.stream_index]->time_base) / av_q2d(octx->streams[pkt.stream_index]->time_base);
pkt.duration = pkt.duration * av_q2d(ictx->streams[pkt.stream_index]->time_base) / av_q2d(octx->streams[pkt.stream_index]->time_base);
pkt.pos = -1;//byte position in stream, -1 if unknown
if ((res = av_interleaved_write_frame(octx, &pkt)) < 0)//推流,推完之后pkt的pts,dts竟然都被重置了!而且前面几帧还因为dts没有增长而返回-22错误
Error(res);
av_free_packet(&pkt);//回收pkt内部分配的内存
}
av_write_trailer(octx);//写文件尾