38.FFmpeg+OpenCV直播推流(FFmpeg api实现以文件为输入源做推流)

今天我们来ffmpeg api编程实现推流并测试效果
项目源码

开发环境

Visual Studio 2015 + FFmpeg-3.2 + nginx服务器

开发过程
1.环境准备

首先在FFmpeg官网下载ffmpeg dev 和share sdk


ffmpeg下载类型.png

下载解压之后,打开文件夹目录,拷贝dev中的include和lib目录,shared中的bin目录,然后创建一个项目目录,将这三个目录拷贝进去,方便我们编写代码时配置相对路径。其中bin存放的都是dll动态库文件,include存放的是头文件,lib存放的是每一个dll对应的lib文件


QQ图片20181125115923.png

然后创建src目录用于我们的项目根目录,我们将会使用Visual Studio在这个目录下创建控制台项目来编写推流代码。

项目创建完成之后,需要对AS做一些配置:

a.右键项目名打开属性,在配置属性的常规选项中的输出目录配置一个目录用于存放我们编译后的文件,这里直接设置为上边创建的bin目录


QQ图片20181125120741.png

b.然后同样在配置属性下找到调试,设置工作目录为bin


QQ图片20181125120901.png

然后在编译时就会去这里找动态库文件

c.下一步在C/C++选项中找到常规,设置头文件目录


QQ图片20181125121143.png

d.在链接器下配置lib目录位置


QQ图片20181125121255.png

这样一来,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播放器测试推流效果。本文代码比较简要,具体可查看源码项目源码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 明天周末了,第一次周末让我感到兴奋和激动。因为以前都是没有周末的(可怜兮兮),突然成为有周末,而且是双休不...
    灵轩_f7e9阅读 1,248评论 0 0
  • 今天家人聚会举杯共饮,一不小心喝多了,生平第一次喝多了,晕晕的吐了很多,但脑子清晰明白作业还没完成,要写分享...
    跑步遇到最好的自己阅读 1,436评论 0 0
  • 在三里屯的下午,阳光正好,均匀的散落在咖啡飘香的玻璃窗旁,与他一起去看了倪妮的新作。 二十八岁我还未成年,坐在影院...
    邊思文阅读 1,898评论 0 1
  • 今天的版纳,上午大雨倾盆,中午便雨过天晴。满眼的绿意肆意的生长,房檐下断断续续的滴着雨,拍打在绿叶上的声音错落有致...
    格小主阅读 2,839评论 0 1