使用librtmp实现本地推流

这个文档详细介绍了,如何在本地ubuntu上搭建自己的流服务器。并通过librtmp进行测试。
1.0 背景 客户需要我们提供rtmp推流的源代码,然后他们DVR的供应商会负责移植到盒子中。这个demo演示了如何用c实现rtmp推流。
2.0 安装配置流服务器 下面详细介绍如何在ubuntu14.04上安装配置流服务器
2.1 安装 nginx

$ sudo apt-get install build-essential libpcre3 libpcre3-dev libssl-dev $ wget http://nginx.org/download/nginx-1.15.1.tar.gz $ wget https://github.com/sergey-dryabzhinsky/nginx-rtmp-module/archive/dev.zip $ tar -zxvf nginx-1.15.1.tar.gz $ unzip dev.zip $ cd nginx-1.15.1 $ ./configure --with-http_ssl_module --add-module=../nginx-rtmp-module-dev $ make $ sudo make install

启动测试下
$ sudo /usr/local/nginx/sbin/nginx

浏览器访问 http://127.0.0.1
测试ngix正常启动了
2.2 安装nginx rtmp服务插件:
vim /usr/local/nginx/conf/nginx.conf

把下面这段添加到末尾
rtmp { server { listen 1935; chunk_size 4096; application live { live on; record off; } } }

上面配置了rtmp的默认端口是1935,以及rtmp app的名字,这里叫“live”
2.3 重启nginx
$ sudo /usr/local/nginx/sbin/nginx -s stop $ sudo /usr/local/nginx/sbin/nginx

2.4 测试效果
为了测试我们的流服务器,我们需要安装ffmpeg来往上面推视频流,然后浏览器拉流查看播放结果。
2.4.1 安装ffmpeg
$ sudo add-apt-repository ppa:mc3man/trusty-media $ sudo apt-get update $ sudo apt-get install ffmpeg

2.4.2 测试 从 https://sample-videos.com 下载一个mp4文件。
然后用 ffmpeg 推流,如下命令:
$ ffmpeg -re -i ./sample.mp4 -vcodec libx264 -vprofile baseline -acodec aac -ar 44100 -strict -2 -ac 1 -f flv -s 1280x720 -q 10 rtmp://localhost:1935/live/testav

最后在浏览器中输入:rtmp://127.0.0.1/live/testav 查看推流结果。第一次运行浏览器可能会要求你安装flash插件,点击“安装”即可。
3.0 使用librtmp推流 以上,是利用ffmpeg工具实现的推流,下面介绍如何用c代码实现推流。我们尝试把一个flv文件推到服务器上,并且用浏览器播放。
3.1 下载编译librtmp
首先,下载librtmp的源码。
git clone git://git.ffmpeg.org/rtmpdump

新建一个文件夹,用来存放我们的测试代码main函数,以及Makefile,首先是测试代码,保存为rtmp_push.c
#include #include #include #include #include "librtmp/rtmp_sys.h" #include "librtmp/log.h"typedef struct FINT16 { unsigned char Byte1; unsigned char Byte2; }fint16; typedef struct FINT24 { unsigned char Byte1; unsigned char Byte2; unsigned char Byte3; }fint24; typedef struct FINT32 { unsigned char Byte1; unsigned char Byte2; unsigned char Byte3; unsigned char Byte4; }fint32; typedef struct FLVHEADER { unsigned char F; unsigned char L; unsigned char V; unsigned char type; unsigned char info; fint32 len; }FlvHeader; typedef struct TAGHEADER { unsigned char type; fint24 datalen; fint32 timestamp; fint24 streamsid; }TagHeader; typedef struct VIDEODATAPRE { unsigned char FrameTypeAndCodecid; unsigned char AVCPacketType; fint24 CompositionTime; }VideoData; #pragma pack()#define FINT16TOINT(x) ((x.Byte1<<8 & 0xff00) | (x.Byte2 & 0xff)) #define FINT24TOINT(x) ((x.Byte1<<16 & 0xff0000) | (x.Byte2<<8 & 0xff00) | (x.Byte3 & 0xff)) #define FINT32TOINT(x) ((x.Byte1<<24 & 0xff000000) | (x.Byte2<<16 & 0xff0000) | (x.Byte3<<8 & 0xff00) | (x.Byte4 & 0xff))int main(int argc, char **argv) { int res = 0; RTMP* rtmp = RTMP_Alloc(); RTMP_Init(rtmp); res = RTMP_SetupURL(rtmp, "rtmp://127.0.0.1/live/testav"); //推流地址 if (res == FALSE) { printf("RTMP_SetupURL error.\n"); } RTMP_EnableWrite(rtmp); //推流要设置写 res = RTMP_Connect(rtmp, NULL); if (res == FALSE) { printf("RTMP_Connect error.\n"); } res = RTMP_ConnectStream(rtmp,0); if (res == FALSE) { printf("RTMP_ConnectStream error.\n"); }//推流 FILE *fp_push=fopen("save.flv","rb"); //本地用作推流的flv视频文件 FlvHeader flvheader; fread(&flvheader, sizeof(flvheader), 1, fp_push); int32_t preTagLen = 0; //前一个Tag长度 fread(&preTagLen, 4, 1, fp_push); TagHeader tagHeader; uint32_t begintime=RTMP_GetTime(),nowtime,pretimetamp = 0; while (1) { fread(&tagHeader, sizeof(tagHeader), 1, fp_push); if(tagHeader.type != 0x09) { int num = FINT24TOINT(tagHeader.datalen); fseek(fp_push, FINT24TOINT(tagHeader.datalen)+4, SEEK_CUR); continue; } fseek(fp_push, -sizeof(tagHeader), SEEK_CUR); if((nowtime=RTMP_GetTime()-begintime)

然后,我们需要编写Makefile编译工程,我们只需要使用librtmp中amf.c log.c parseurl.c rtmp.c hashswf.c这几个文件就好了:
下面是Makefile,对于需要修改的地方,都注释好了。根据自己的系统路径,做适当的修改。
CFLAGS= #添加下面的编译参数,不使用ssl库 zlib等等 DFLAGS=-DNO_SSL -DNO_CRYPTO LDFLAGS=CC=gccBUILD_DIR=./build OBJ_DIR=$(BUILD_DIR)/objs # 修改为你下载下来的librtmp库的目录 SRC_DIR=../../rtmpdump/librtmp# 修改为librtmp库的头文件目录 INC= \ -I../../rtmpdump/librtmp \ -I../../rtmpdumpSRC = https://www.it610.com/ amf.c / log.c / parseurl.c / rtmp.c / hashswf.c / rtmp_push.cvpath %.c $(SRC_DIR) ./OBJS = $(notdir $(patsubst %c,%o,$(SRC)))%.o:%.c | out @true"CC $<" $(CC) $(CFLAGS) $(DFLAGS) $(INC) -o $(addprefix $(OBJ_DIR)/,$@) -c $

Makefile修改完成后,直接make就可以了。这样,我们的测试代码连同librtmp库就编译完成了。
3.2 测试librtmp库
首先,用ffmpeg工具把之前的mp4文件,转化为flv文件:
ffmpeg -i source.mp4 -c:v libx264 -crf 19 save.flv ./rtmp_push

然后,打开浏览器,输入 rtmp://127.0.0.1/live/testav 就可以看到我们推的rtmp流了。测试结束。
最后,附上相关文件:
Makefile
rtmp_push.c
4. 推h264裸流 一般地,客户会发一段h264裸流视频文件让云端验证前端播放器的兼容性问题。这就涉及到如何推h264裸流文件。
我们可以参考雷神的代码:
git clone https://github.com/leixiaohua1020/simplest_librtmp_example.git cd simplest_librtmp_example/simplest_librtmp_send264/# 这个是推送264的example代码

这个代码是在VS里面编译的工程,我们移植起来会不方便,所以,选择在linux下编译安装测试。这个代码主要的功能是解析h264文件,并且按照flv格式用RTMP推送视频流到服务器。
下面开始编译。
4.1 复制lei神代码
mkdir push_test && cd push_test/# 新建一个工程文件夹 我们把需要的源码从git里面拷贝出来编译 cp ../simplest_librtmp_example/simplest_librtmp_send264/cuc_ieschool.h264 \ ../simplest_librtmp_example/simplest_librtmp_send264/librtmp_send264.cpp \ ../simplest_librtmp_example/simplest_librtmp_send264/librtmp_send264.h \ ../simplest_librtmp_example/simplest_librtmp_send264/sps_decode.h \ ../simplest_librtmp_example/simplest_librtmp_send264/simplest_librtmp_send264.cpp.

4.2 编写Makefile
这里的Makefile和上面的类似,只是增加了两个cpp文件需要一起集成编译一下
CFLAGS= #添加下面的编译参数,不使用ssl库 zlib等等 DFLAGS=-DNO_SSL -DNO_CRYPTO LDFLAGS=CC=gccBUILD_DIR=./build OBJ_DIR=$(BUILD_DIR)/objs # 修改为你下载下来的librtmp库的目录 SRC_DIR=../../../rtmpdump/librtmp# 修改为librtmp库的头文件目录 INC= \ -I../../../rtmpdump/librtmp \ -I../../../rtmpdumpSRC = https://www.it610.com/ amf.c / log.c / parseurl.c / rtmp.c / hashswf.cvpath %.c $(SRC_DIR) ./ vpath %.cpp $(SRC_DIR) ./OBJS = $(notdir $(patsubst %c,%o,$(SRC))) simplest_librtmp_send264.o librtmp_send264.o%.o:%.c | out @echo"CC $<" $(CC) $(CFLAGS) $(DFLAGS) $(INC) -o $(addprefix $(OBJ_DIR)/,$@) -c $

直接make一下,报错:
librtmp_send264.cpp:18:10: fatal error: 'librtmp\rtmp.h' file not found #include "librtmp\rtmp.h"

【使用librtmp实现本地推流】因为是windows下的程序,路径中的反斜杠需要改成linux中的斜杠,修改完成,继续make,还是报错
gcc -o rtmp_push ./build/objs/amf.o ./build/objs/log.o ./build/objs/parseurl.o ./build/objs/rtmp.o ./build/objs/hashswf.o ./build/objs/simplest_librtmp_send264.o ./build/objs/librtmp_send264.o Undefined symbols for architecture x86_64: "operator delete[](void*)", referenced from: h264_decode_sps(unsigned char*, unsigned int, int&, int&, int&) in librtmp_send264.o "operator new[](unsigned long)", referenced from: h264_decode_sps(unsigned char*, unsigned int, int&, int&, int&) in librtmp_send264.o ld: symbol(s) not found for architecture x86_64 clang: error: linker command failed with exit code 1 (use -v to see invocation) make: *** [rtmp_push] Error 1

因为是用c编译器,无法识别c++中的new delete等关键字,所以,我们还得修改代码。。定位到 sps_decode.h中176行位置,
// 这几句话看起来没具体作用,直接注释掉 // int *offset_for_ref_frame=new int[num_ref_frames_in_pic_order_cnt_cycle]; // for( int i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++ ) //offset_for_ref_frame[i]=Se(buf,nLen,StartBit); // delete [] offset_for_ref_frame;

继续make,编译通过。
4.3 测试demo中的h264文件推流
修改 simplest_librtmp_send264.cpp 38行
// 这里,我们使用百度 lss 提供的RTMP推流地址 RTMP264_Connect("rtmp://push.ivc.gz.baidubce.com/xxx/test");

推流,发现程序在发完第一个relu之后就卡住了,发现是msleep的问题,修改 librtmp_send264.cpp 680行位置
tick +=tick_gap; now=RTMP_GetTime(); msleep((int)(tick_gap-now+last_update)); // 这里需要用 int强制类型转化,不然就会卡住。莫名其妙,不懂,求大佬指点。 //msleep(40);

这样修改之后,运行 ./rtmp_push 就可以推流了,在客户端使用ffplay播放:
用rtmp格式播放 ffplay "rtmp://rtmp.play.ivc.gz.baidubce.com/xxx/test?only-video=1" 或者用flv格式播放 ffplay "http://flv.play.ivc.gz.baidubce.com/xxx/test.flv?only-video=1"# 必须加上 only-video=1 参数因为我们的264文件中只有视频 没有音频,默认情况下server回去做音/视频同步,导致30s左右延迟! # 加上这个参数直接跳过“同步”的过程,差不多5s内开首屏。

4.4 测试客户h264文件
如果你测试客户发过来的h264文件,你会发现用上面的代码多半是跑不起来的。
雷神代码中默认是按照第一个帧是sps pps来解析的,这本身应该没有问题,因为客户手机一般也是在检测到第一个sps pps之后,才开始推流的。开头并不会出现“无用的”P帧数据。
但是,客户发过来的h264文件一般都是在开头夹杂着“无用的”P帧数据,所以用上面的代码肯定是不行的,我们要做的是把客户h264文件开头的P帧数据去掉,才开始用上面的代码推。
这就涉及到如何编辑二进制h264文件了。首先提供一个工具:
truncate_head_n.c
gcc truncate_head_n.c -o truncate_head_n ./truncate_head_n 1000# 该命令会去掉当前目录下 命名为 temp的二进制文件的开始1000个字节

有了该工具,我们只需要找到264文件中第一个sps的偏移地址就可以了,可以直接用vim 查看
vim -b h264data.h264 :%!xxd

找到偏移,并且用工具去掉无用P帧之后,就可以用上面的demo推客户的流了,步骤就不赘述。
但是,我这里遇到一个很奇怪的问题,发现打开还是很慢,需要30s多。经过百度lss同学指点,说需要修改 librtmp_send264.cpp 中,只需要在开始时推一次sps pps,推流过程中,不再推sps pps,经过验证,在去掉推流中间过程的sps pps时候,首屏开启5s左右!那为何demo中的264文件在不改代码时也是没问题的呢?暂时没结果。

    推荐阅读