SRS ------Simple Rtmp Server
SRS是单进程实现的,在同一个服务器上可以启动多个进程进行同时服务。实际上SRS并不简单。
常用在运营级的互联网直播服务器集群;他提供了非常丰富的接入方案,支持RTMP, HLS, HTTP-FLV等。
要想实现运营级别必须保证99.99%,保证72小时不间断。
Feature | SRS | NGINX | CRTMPD | FMS(Adobe) | WOWZA(Java 实现) |
RTMP | 支持 | 支持 | 支持 | 支持 | 支持 |
HLS | 支持 | 支持 | X | 支持 | 支持 |
HDS | 尝试 | x | x | 支持 | 支持 |
HTTP FLV | 支持 | X | X | X | X |
HLS(aonly 音频) | 支持 | x | x | 支持 | 支持 |
HTTP SERVER | 支持 | 支持 | X | X | 支持 |
Feature | SRS | NGINX | CRTMPD | FMS(Adobe) | WOWZA(Java 实现) |
Concurrency | 7.5k | 3k | 2k | 2k | 3k |
MultipleProcess | 尝试 | 支持 | x | X | X |
RTMP Latency | 0.1s | 3s | 3s | 3s | 3s |
HLS Latency | 10s | 30s | x | 30s | 30s |
我们现在下载下SRS源代码
按照github上的下载和方法进行安装
>>> Step 1: Get SRS.
git clone https://gitee.com/winlinvip/srs.oschina.git srs &&
cd srs/trunk && git remote set-url origin https://github.com/ossrs/srs.git &&
git checkout 3.0release && git pull
Note: We use mirrors(gitee) here, but it's also ok to directly clone by
git clone https://github.com/ossrs/srs.git && cd srs/trunk
>>> Step 2: Build SRS.
./configure && make
Remark: Recommend to use Centos7 64bits, please read wiki(CN,EN).
Note: You can also build SRS in docker, please read docker.
>>> Step 3: Run SRS
./objs/srs -c conf/srs.conf
在这里需要注意不需要执行make install。
否者会报奇怪错误,
Now mkdir /usr/local/srs
Now make the http root dir
Now copy binary files
Now copy srs conf files
Now copy init.d script files
sed: 1: "/usr/local/srs/etc/init ...": extra characters at the end of l command
make: *** [install] Error 1
执行 ./objs/srs -c conf/srs.conf启动后
我们可以通过 下面的命令来查看srs是否已经启动
yuanxuzhen@yuanxuzhendeMacBook-Pro trunk % ps -ef | grep srs
501 59955 1 0 2:09下午 ttys000 0:00.28 ./objs/srs -c ./conf/srs.conf
501 60113 59962 0 2:22下午 ttys001 0:00.00 grep srs
我们可以看到59955的srs已经启动了
接下来我们看看端口是否已经启动
yuanxuzhen@yuanxuzhendeMacBook-Pro trunk % netstat -an | grep 1935
tcp4 0 0 *.1935 *.* LISTEN
通过查看我们也知道端口已经启动了。
接下来我们来进行推流
第一种方法:通过ffmpeg命令进行推流
ffmpeg -re -i 文件 -c copy -f flv rtmp://localhost/live/room
ffplay rtmp://localhost/live/room
第二种方法 我们通过ffmpeg的api进行推流
//
// pushstream.c
// PushStream
//
// Created by yuanxuzhen on 2021/4/15.
//
#include "pushstream.h"
#include "librtmp/rtmp.h"
/*
flv文件 头部有9个字节,
第一个字节是字母F,
第二个字节是L,
第三个字节是V,
第四个字节是版本号,
第五个字节 1-5位保留,必须是0,第6位是否有音频tag 第7位保留必须是0,第8位是否有视频tag
第六到九字节代表header的大小,必须是9
*/
static int status = 1;
void set_status(int state){
status = state;
}
static FILE* open_flv(char* flvaddr){
FILE* fp = NULL;
fp = fopen(flvaddr, "rb");
if(!fp){
printf("failed to open flv:%s", flvaddr);
return NULL;
}
fseek(fp, 9, SEEK_SET); //跳过flv头 9个字节
fseek(fp, 4, SEEK_CUR); //跳过pretagsize 4个字节
return fp;
}
static RTMP* connect_rtmp_server(char* rtmp_addr){
RTMP* rtmp = NULL;
rtmp = RTMP_Alloc();
if(!rtmp){
printf("NO Memory");
goto __ERROR;
}
RTMP_Init(rtmp);
//设置rtmp的超时时间和rtmp的连接地址
rtmp->Link.timeout = 10;
RTMP_SetupURL(rtmp, rtmp_addr);
//设置推流还是拉流,设置开启是推流,不设置是拉流
RTMP_EnableWrite(rtmp);
//建立链接
if(!RTMP_Connect(rtmp, NULL)){
printf("failed to connect");
goto __ERROR;
}
//创建流
RTMP_ConnectStream(rtmp, 0);
return rtmp;
__ERROR:
if(rtmp){
RTMP_Close(rtmp);
RTMP_Free(rtmp);
}
return NULL;
}
static RTMPPacket *alloc_packet(){
RTMPPacket *pack = NULL;
// pack = (RTMPPacket*)malloc(sizeof(RTMPPacket));
pack =(RTMPPacket*)malloc(sizeof(RTMPPacket));
if(!pack){
printf("NO Memory alloc_packet");
return NULL;
}
RTMPPacket_Alloc(pack, 64 * 1024);
RTMPPacket_Reset(pack);
pack->m_hasAbsTimestamp = 0;
pack->m_nChannel = 0x4;
return pack;
}
static int read_u8(FILE* fp, unsigned int *u8){
unsigned int tmp;
if(fread(&tmp, 1, 1, fp) != 1){
printf("Failed to read_u8!\n");
return -1;
}
*u8 = tmp & 0xFF;
return 0;
}
static int read_u24(FILE* fp, unsigned int *u24){
unsigned int tmp;
if(fread(&tmp, 1, 3, fp) != 3){
printf("Failed to read_u24!\n");
return -1;
}
*u24 = ((tmp >> 16) & 0xFF)| ((tmp << 16) & 0xFF0000) | (tmp &0xFF00);
return 0;
}
static int read_u32(FILE* fp, unsigned int *u32){
unsigned int tmp;
if(fread(&tmp, 1, 4, fp) != 4){
printf("Failed to read_u32!\n");
return -1;
}
*u32 = ((tmp >> 24) & 0xFF) || ((tmp >> 8) & 0xFF00)| ((tmp << 8) & 0xFF0000) | ((tmp << 24)&0xFF000000);
return 0;
}
static int read_ts(FILE *fp, unsigned int *ts){
unsigned int tmp;
if(fread(&tmp, 1, 4, fp) !=4) {
printf("Failed to read_ts!\n");
return -1;
}
*ts = ((tmp >> 16) & 0xFF) | ((tmp << 16) & 0xFF0000) | (tmp & 0xFF00) | (tmp & 0xFF000000);
return 0;
}
int read_data(FILE* fp, RTMPPacket **pack){
/*
* tag header
* 第一个字节 TT(Tag Type), 0x08 音频,0x09 视频, 0x12 script
* 2-4, Tag body 的长度, PreTagSize - Tag Header size
* 5-7, 时间戳,单位是毫秒; script 它的时间戳是0
* 第8个字节,扩展时间戳。真正时间戳结格 [扩展,时间戳] 一共是4字节。
* 9-11, streamID, 0
*/
unsigned int tt;
unsigned int tag_data_size;
unsigned int ts;
unsigned int streamId;
unsigned int tag_pre_size;
int ret = -1;
size_t data_size = 0;
if(read_u8(fp, &tt)){
goto __ERROR;
}
if(read_u24(fp, &tag_data_size)){
goto __ERROR;
}
if(read_ts(fp, &ts)){
goto __ERROR;
}
if(read_u24(fp, &streamId)){
goto __ERROR;
}
data_size = fread((*pack)->m_body, 1, tag_data_size, fp);
if(tag_data_size != data_size){
printf("read data size error tag_data_size = %d, real data size = %d\n", tag_data_size, data_size);
goto __ERROR;
}
(*pack)->m_headerType = RTMP_PACKET_SIZE_LARGE;
(*pack)->m_nTimeStamp = ts;
(*pack)->m_packetType = tt;
(*pack)->m_nBodySize = tag_data_size;
read_u32(fp, &tag_pre_size);
ret = 0;
__ERROR:
return ret;
}
static void send_data(FILE* fp, RTMP *rtmp){
//1、创建RTMPPacket对象
RTMPPacket *packet = NULL;
unsigned int pre_ts = 0;
packet = alloc_packet();
packet->m_nInfoField2 = rtmp->m_stream_id;
while (1) {
//从flv读取文件
//2.从flv文件中读取数据
if(read_data(fp, &packet)){
printf("over!\n");
break;
}
//判断rtmp是否还处在链接状态
if(!RTMP_IsConnected(rtmp)){
printf("Disconnect....\n");
break;
}
unsigned int diff = packet->m_nTimeStamp - pre_ts;
usleep(diff * 1000);
//发送数据
RTMP_SendPacket(rtmp, packet, 0);
pre_ts = packet->m_nTimeStamp;
}
}
void push_stream(){
char* rtmp_addr = "rtmp://localhost/live/room";
char* flv = "/Users/yuanxuzhen/study/mac/PushStream/PushStream/output/test_yuv.flv";
//读flv文件
FILE* fp = open_flv(flv);
//链接RTMP服务器
RTMP* rtmp = connect_rtmp_server(rtmp_addr);
//push video/audio data
send_data(fp, rtmp);
}
ffplay rtmp://localhost/live/room
这样我们就完成了srs的搭建,推流和客户端播放。
我们接下来分析一下srs.conf
# main config for srs.
# @see full.conf for detail config.
listen 1935; #监听端口
max_connections 1000; #最大连接数
srs_log_tank file; #配置日志答应到文件,需要和srs_log_level配合使用
srs_log_file ./objs/srs.log; #制定日志文件的位置。
daemon on;#以daemon的方式启动,如果要启动在console,那么需要配置daemon off;并且,需要配置srs_log_tank console;
http_api {
enabled on;
listen 1985;
}
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
stats {
network 0;
disk sda sdb xvda xvdb;
}
#默认的vhost,在没有指明vhost的情况,默认使用这个vhost。
vhost __defaultVhost__ {
hls {
enabled on;
}
http_remux {
enabled on;
mount [vhost]/[app]/[stream].flv;
}
}