目录

前言

环境准备及编译

测试

遇到的问题


前言

RTMP是Real Time Messaging Protocol(实时消息传输协议)的首字母缩写。该协议基于TCP,是一个协议族,包括RTMP基本协议及RTMPT/RTMPS/RTMPE等多种变种。RTMP是一种设计用来进行实时数据通信的网络协议,主要用来在Flash/AIR平台和支持RTMP协议的流媒体/交互服务器之间进行音视频和数据通信。——摘录自百度。 

本文要处理的是:用 Nginx+rtmp 搭建流媒体服务器,用来做推拉流的中转。rtmp是开源的插件,Nginx是支持高并发的服务器。

ffmpeg推流到 Nginx 服务器,然后VLC从服务器拉流播放。那么这个服务器怎么搭建呢,下面就详细的记录一下。

环境准备及编译

1、win10上,装VMWare Workstation, 然后安装Ubuntu16.04

2、检查一下用到的工具,是否存在

 cmake ,gcc,  g++, libssl-dev, libpcre3-dev, git等,需要先配置好。检查的方式: 直接输入工具名,回车,就可以看到有没有了。

如果没有,就像下面这样去安装。安装不成的时候,头部加上sudo 就可以了,以管理员权限进行安装。

apt-get install libpcre3-dev

3、当上面的工具都准备好了,就可以进行下载

// 创建一个文件夹
mkdir nginx

// 进入文件夹
cd nginx

// 下载文件
wget http://nginx.org/download/nginx-1.12.1.tar.gz

// 解压
tar -zxvf nginx-1.12.1.tar.gz

4、rtmp作为一个模块,添加到nginx文件夹中。

//下载rtmp模块
git clone https://github.com/arut/nginx-rtmp-module.git

5、作为一个模块,编译到Nginx中。 切回到nginx-1.12.1目录下:

//进入nginx-1.12.1文件中
cd nginx-1.12.1
 // 用的绝对路径进行安装配置
./configure --add-module=/home/ht-dong/nginx/nginx-rtmp-module   

// 相对路径安装的话:
./configure --add-module=../nginx-rtmp-module

6、编译

make

7、安装

make install

8、至此,nginx就安装在了 /usr/local/nginx/下面。

9、切换到安装目录下的sbin ,

cd sbin

10、启动运行  

./nginx

如果不成功,用sudo ./nginx  (sudo是用管理员身份进行)

11、 然后查看是否启动成功

ps -ef|grep nginx

12、Chrome浏览器直接输入:http://192.168.87.6/。 显示如下:登录成功!(此IP为虚拟机的IP,通过ifconfig, 命令可以查看)

Welcome to nginx!

If you see this page, the nginx web server is successfully installed and working. 
Further configuration is required.

For online documentation and support please refer to nginx.org.
Commercial support is available at nginx.com.

Thank you for using nginx.

13、 关闭服务

sudo pkill nginx

================以上 完成了 Nginx的安装, 下面,进行rtmp的配置===================

1、进入到安装目录下

cd /usr/local/nginx/conf/

2、打开nginx.conf文件

sudo vim nginx.conf

3、添加信息, 在这个文件的最后添加,就可以。

rtmp
{
        server
        {
                listen 1935; // 端口
                chunk_size 4096;// 块尺寸
                application live  // 目录
                {
                        live on;
                }
        }
}

4、在http的大括号中,与未被注释的server平行,在server之上,添加如下server信息。使得两个server并列。添加如下消息,就可以进行通过浏览器看到视频详细信息。

// 跟另外一个server平行设置的
server
    {
        listen 8080; // 端口
        location /stat
        {
                rtmp_stat all;
                rtmp_stat_stylesheet stat.xsl; //Excel表格样式,在浏览器中显示用的
        }
        location /stat.xsl
        {
                root /home/ht-dong/nginx/nginx-rtmp-module;//模块路径
        }
    }

这段信息添加完,就需要关闭服务/重启服务。

sudo pkill nginx

sudo ./nginx

在推拉流的过程中,在Chrome浏览器中输入:http://192.168.87.6:8080/stat

就可以看到详细信息列表了。

 

测试

测试一

进行到上面的第3步时,就可以进行推拉流测试了。

准备好我们下载的ffmpeg—shared版本,进入bin,找到 ffmpeg.exe, 在这个文件中放一个flv文件。

然后在这个文件夹的目录栏中,输入 cmd, 进入命令行:

ffmpeg  -i China.flv  -f flv rtmp://192.168.87.6/live

1》打开VLC, 媒体——>打开网络串流,输入:rtmp://192.168.87.6/live, 播放。这里的live,就是上面配置的文件路径。

可以看到推流的文件,进行播放了。

2》还可以用ffplay拉流播放。

ffplay rtmp://192.168.87.6/live -fflags nobuffer // 不要缓冲,会更快

测试二

进行上面第4步时,通过浏览器,可以看到推拉流视频文件的详细信息表。

在推拉流的过程中,在Chrome浏览器中输入:http://192.168.87.6:8080/stat

就可以看到详细信息列表了。

测试三

通过ffmpeg的代码,开发一个win32的控制台工程,进行推流。不用ffmpeg.exe进行推流。代码后续加上。

// 新建win32控制台项目,空项目,添加一个*.cpp文件,配置好include/lib/dll即可
// 将flv文件直接进行推流到nginx服务器,然后VLC拉流播放

#include <iostream>

using namespace std;

extern "C"
{
#include "include\libavformat\avformat.h"
#include "include\libavutil\time.h"
}

int ERROR(int re)
{
	char buf[1024] = { 0 };
	av_strerror(re, buf, sizeof(buf));
	std::cout << buf << std::endl;
	return -1;
}

static double r2d(AVRational r)
{
	return r.num == 0 || r.den == 0 ? 0.0 : r.num / r.den;
}


int main()
{
	av_register_all();
	avformat_network_init();
	// 打开格式,解封mp4 flv, 后面都基于ictx进行操作
	AVFormatContext *ictx = NULL;// 最后需要释放, 输入


	const char *inUrl = "China.flv";// 放在bin目录下, 最好用flv格式
	const char *outUrl = "rtmp://192.168.87.6/live"; // 输出流

	// 打开文件,解封文件头。不能用于H264, 因为H264没有头
	int ret = avformat_open_input(&ictx, inUrl, NULL, NULL);// 打开文件
	if (ret < 0)
	{
		return ERROR(ret);
	}

	// 获取音视频流信息
	ret = avformat_find_stream_info(ictx, 0);
	if (ret < 0)
	{
		return ERROR(ret);
	}

	// 打印一下流信息,看是否有问题
	av_dump_format(ictx, 0, inUrl, 0);

	// 输出流,
	AVFormatContext *octx = NULL;// 最后需要释放, 输出
    //创建输出流的上下文空间。因为流媒体不能根据输出文件,判断封装格式,所以需要给出 flv
	ret = avformat_alloc_output_context2(&octx, 0, "flv", outUrl);
	// 如果是输出文件,与上面有差别,可以不用写封装格式,会根据输出文件url进行判断。
	if (!octx)
	{
		return ERROR(ret);
	}

	// 配置输出流
	for (int i = 0; i < ictx->nb_streams; ++i)
	{
		AVStream *out;
		out = avformat_new_stream(octx, ictx->streams[i]->codec->codec);
        // 将新的流,创建到octx中,用输入流的音视频codec的格式信息

		if (!out)
		{
			return -1;
		}
		// 复制编解码信息, 用于mp4
		// ret= avcodec_copy_context(out->codec, ictx->streams[i]->codec);
		ret = avcodec_parameters_copy(out->codecpar, ictx->streams[i]->codecpar);
		out->codec->codec_tag = 0;// 表示后面不需要编码,直接写入。 另外流媒体不需要协议头。
	}
	// 输出流信息配置好之后,打印输入,看一下是否正确
	av_dump_format(octx, 0, outUrl, 1);


	// 上面准备好之后,rtmp开始推流

	// 打开io
	ret = avio_open(&octx->pb, outUrl, AVIO_FLAG_WRITE);
	if (!octx->pb)
	{
		return ERROR(ret);
	}
	// 写入头信息
	ret = avformat_write_header(octx, 0);
	if (ret < 0)
	{
		return ERROR(ret);
	}
	// 推流每一帧数据
	AVPacket pkt;
	pkt.data = nullptr;
	pkt.size = 0;

	long long startTime = av_gettime();// 获取当前时间戳,为了更准确的得到
	while (true)
	{
		ret = av_read_frame(ictx, &pkt);
		if (ret < 0)
		{
			break;// 读完了
		}
		// cout << pkt.pts << " " << flush;// 直接刷新输出
		// 计算转换时间戳,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_PASS_MINMAX));

		pkt.dts = av_rescale_q_rnd(pkt.dts, itime, otime,
             (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));

		pkt.duration = av_rescale_q_rnd(pkt.duration, itime, otime,
             (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));

		pkt.pos = -1;


		// 添加一个休息时间,否则推流太快了
		// 根据实际的dts控制视频帧的推送速度
		if (AVMEDIA_TYPE_VIDEO == ictx->streams[pkt.stream_index]->codecpar->codec_type)// 仅视频的时候,
		{
			long long now = av_gettime() - startTime;// 得到当前时间
			long long dts = 0;
			dts = pkt.dts * 1000 * 1000 * r2d(itime);// s变为us。 itime.num / itime.den
            //需要一个libutil/time.h的头文件. 这是一个us函数,需要*1000得到ms, 再*1000得到us。 
			if (dts > now)
			{
				av_usleep(dts - now);
			}			
		}

		ret = av_interleaved_write_frame(octx, &pkt);// 内部缓存排序并释放	
		if (ret < 0)
		{
			return ERROR(ret);
		}
		av_packet_unref(&pkt);// 这里不可以省略
	}
    std::cout << "finished! "<< std::endl; 
    return 0;
}

 

遇到的问题

重启服务,遇到的问题:

nginx: [emerg] listen() to 0.0.0.0:1935, backlog 511 failed (98: Address already in use)
......
nginx: [emerg] listen() to 0.0.0.0:1935, backlog 511 failed (98: Address already in use)
nginx: [emerg] still could not bind()

这是因为端口被占用,查看一下是否被占用:

sudo netstat -ntpl

当发现哪个线程被占用时,就kill哪个id, 或者根据没有进程id的时候,就需要单独kill掉端口。

sudo kill PID  // (PID是线程ID)

或者 sudo fuser -k 1935/tcp  // 按照端口号把进程干掉

然后就可以重启服务器了:sudo ./nginx

打开浏览器,http://192.168.87.6, 可以看到成功了!

后续还实现了,录制桌面,推流,拉流,播放的功能。