本机搭建SRS服务器--------37

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源代码

https://github.com/ossrs/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;
    }
}

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值