使用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/nginx2.4 測試效果
為了測試我們的流服務器,我們需要安裝ffmpeg來往上面推視頻流,然后瀏覽器拉流查看播放結果。
2.4.1 安裝ffmpeg
$ sudo add-apt-repository ppa:mc3man/trusty-media $ sudo apt-get update $ sudo apt-get install ffmpeg2.4.2 測試
從 https://sample-videos.com 下載一個mp4文件。
然后用 ffmpeg 推流,如下命令:
最后在瀏覽器中輸入: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 <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h>#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)<pretimetamp) { printf("%d - %d\n", pretimetamp, nowtime); usleep(1000 * (pretimetamp-nowtime)); continue; }char* pFileBuf=(char*)malloc(11+FINT24TOINT(tagHeader.datalen)+4); memset(pFileBuf,0,11+FINT24TOINT(tagHeader.datalen)+4); if(fread(pFileBuf,1,11+FINT24TOINT(tagHeader.datalen)+4,fp_push)!=11+FINT24TOINT(tagHeader.datalen)+4) break;if ((res = RTMP_Write(rtmp,pFileBuf,11+FINT24TOINT(tagHeader.datalen)+4)) <= 0) { printf("RTMP_Write end.\n"); break; } pretimetamp = FINT24TOINT(tagHeader.timestamp);free(pFileBuf); pFileBuf=NULL; }return 0; }然后,我們需要編寫Makefile編譯工程,我們只需要使用librtmp中amf.c log.c parseurl.c rtmp.c hashswf.c這幾個文件就好了:
下面是Makefile,對于需要修改的地方,都注釋好了。根據自己的系統路徑,做適當的修改。
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裸流文件。
我們可以參考雷神的代碼:
這個代碼是在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 = \ 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 $<rtmp_push: $(OBJS)@echo "TARGET rtmp_push"$(CC) -o rtmp_push $(addprefix $(OBJ_DIR)/,$(OBJS)) $(LDFLAGS)simplest_librtmp_send264.o:simplest_librtmp_send264.cpp | out@echo "CC $<"$(CC) $(CFLAGS) $(DFLAGS) $(INC) -o $(addprefix $(OBJ_DIR)/,$@) -c $<librtmp_send264.o:librtmp_send264.cpp | out@echo "CC $<"$(CC) $(CFLAGS) $(DFLAGS) $(INC) -o $(addprefix $(OBJ_DIR)/,$@) -c $<.PHONY : clean clean:rm -rf $(BUILD_DIR)rm -rf rtmp_pushrm -rf *~ out:mkdir -p $(OBJ_DIR)直接make一下,報錯:
librtmp_send264.cpp:18:10: fatal error: 'librtmp\rtmp.h' file not found #include "librtmp\rtmp.h"因為是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
有了該工具,我們只需要找到264文件中第一個sps的偏移地址就可以了,可以直接用vim 查看
vim -b h264data.h264 :%!xxd找到偏移,并且用工具去掉無用P幀之后,就可以用上面的demo推客戶的流了,步驟就不贅述。
但是,我這里遇到一個很奇怪的問題,發現打開還是很慢,需要30s多。經過百度lss同學指點,說需要修改 librtmp_send264.cpp 中,只需要在開始時推一次sps pps,推流過程中,不再推sps pps,經過驗證,在去掉推流中間過程的sps pps時候,首屏開啟5s左右!那為何demo中的264文件在不改代碼時也是沒問題的呢?暫時沒結果。
總結
以上是生活随笔為你收集整理的使用librtmp实现本地推流的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CAD笔记
- 下一篇: Linux 可执行文件结构与进程结构