今天我们来ffmpeg api编程实现推流并测试效果
项目源码
开发环境
Visual Studio 2015 + FFmpeg-3.2 + nginx服务器
开发过程
1.环境准备
首先在FFmpeg官网下载ffmpeg dev 和share sdk
下载解压之后,打开文件夹目录,拷贝dev中的include和lib目录,shared中的bin目录,然后创建一个项目目录,将这三个目录拷贝进去,方便我们编写代码时配置相对路径。其中bin存放的都是dll动态库文件,include存放的是头文件,lib存放的是每一个dll对应的lib文件
然后创建src目录用于我们的项目根目录,我们将会使用Visual Studio在这个目录下创建控制台项目来编写推流代码。
项目创建完成之后,需要对AS做一些配置:
a.右键项目名打开属性,在配置属性的常规选项中的输出目录配置一个目录用于存放我们编译后的文件,这里直接设置为上边创建的bin目录
b.然后同样在配置属性下找到调试,设置工作目录为bin
然后在编译时就会去这里找动态库文件
c.下一步在C/C++选项中找到常规,设置头文件目录
d.在链接器下配置lib目录位置
这样一来,VS的工作环境就搭建好了
2.代码编写
接下来正式开始写代码,我们这次的推流以本地文件为源进行推,推流的过程大概是这么几步
1.ffmpeg初始化
av_register_all注册所有封装器和解封装器以及协议,这个方法在前边文章已经提到过
avformat_network_init初始化网络相关的东西,推流必须用到这个
av_register_all();
avformat_network_init();
2.读取源文件
avformat_open_input打开一个流读取流的文件头信息,如果没有文件头则无法读取到正确的信息
avformat_find_stream_info 针对一些不包含头信息的源(MPEG)很有效,调用防止无法读取到文件的正确信息
avformat_open_input(&ictx, input, 0, &opts);
avformat_find_stream_info(ictx, 0);
3.推流的准备工作
源文件信息我们已经读取到了,接下来开始准备推流,推流之前我们需要构造出几个对象:
AVFormatContext:输出格式上下文
AVStream:输出流,有几条stream就构造几个
avcodec_parameters_copy的作用是将输入流也就是源文件的parameter信息拷贝到输出流的参数中
avformat_alloc_output_context2(&octx, 0, "flv", output)
avformat_new_stream(octx, ictx->streams[i]->codec->codec)
avcodec_parameters_copy(out->codecpar, ictx->streams[i]->codecpar)
4.开始推流
avio_open会初始化出一个AVIOContext对象,我们可以把他看作一个普通的io流对象,只不过区别在于它是向流媒体服务器写入数据的
avformat_write_header是初始化推流的头信息
av_read_frame是在一个循环中进行的,不断的读取源得到AVPacket,然后由av_interleaved_write_frame负责开始推
avio_open(&octx->pb, output, AVIO_FLAG_WRITE);
avformat_write_header(octx, 0);
av_read_frame(ictx, &pkt)
av_interleaved_write_frame(octx, &pkt);
推流的过程很简单,不过其中涉及到一点比较重要的东西就是推流中的速度控制,我们可以设置一个时间基准,通过对比packet的pts和时间基准的大小来决定推送的速度,从而实现正常效果的推送,关键代码如下
//推流每一帧数据
AVPacket pkt;
long long startTime = av_gettime();
for (;;) {
result = av_read_frame(ictx, &pkt);
if (result != 0) {
break;
}
//计算转换时间戳pts dts
AVRational itime = ictx->streams[pkt.stream_index]->time_base;
AVRational otime = octx->streams[pkt.stream_index]->time_base;
pkt.pts = av_rescale_q_rnd(pkt.pts, itime, otime, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_NEAR_INF));
pkt.dts = av_rescale_q_rnd(pkt.pts, itime, otime, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_NEAR_INF));
pkt.duration = av_rescale_q_rnd(pkt.duration, itime, otime, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_NEAR_INF));
pkt.pos = -1;
//视频帧推送速度
if (ictx->streams[pkt.stream_index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
//获取视频的时间戳
AVRational rational = ictx->streams[pkt.stream_index]->time_base;
//从开始解码到现在过去的时间,可以作为推流进行的时间,用当前帧的pts做对比,进行同步
long long now = av_gettime() - startTime;
long long dts = 0;
//单位微秒
dts = pkt.dts*r2d(rational)*1000*1000;
//说明推送太快,等一等
if (dts > now) {
av_usleep(dts - now);
cout << "等待"<< dts - now<< endl;
}
else {
cout << "无需等待" << dts - now << endl;
}
}
result = av_interleaved_write_frame(octx, &pkt);
if (result < 0) {
return XError(result);
}
//av_packet_unref(&pkt);
}
总结
本篇文章简单讲述了ffmpeg实现推流的过程,推流服务器使用的是nginx,所以在开始推之前要确保nginx服务器已经开始,并且正确配置服务器的ip地址,推流之后我们可以通过vlc播放器测试推流效果。本文代码比较简要,具体可查看源码项目源码