这个文档详细介绍了,如何在本地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 <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,对于需要修改的地方,都注释好了。根据自己的系统路径,做适当的修改。

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.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 $<rtmp_push: $(OBJS)@true "TARGET rtmp_push"$(CC) -o rtmp_push $(addprefix $(OBJ_DIR)/,$(OBJS)) $(LDFLAGS).PHONY : clean
clean:rm -rf $(BUILD_DIR)rm -rf rtmp_pushrm -rf *~
out:mkdir -p $(OBJ_DIR)

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 = \
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

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文件在不改代码时也是没问题的呢?暂时没结果。

使用librtmp实现本地推流相关推荐

  1. 使用librtmp库进行推流与拉流

    使用librtmp库进行推流与拉流 目前比较主流的直播技术有RTMP.HLS,其中RTMP主要基于TCP协议,HLS主要基于HTTP协议,二者在实施成本.延迟性等方面有较大差异.本文主要讲解RTMP的 ...

  2. 视频直播推流技术(MediaCodec硬编码+libRTMP,编码器),Demo - Android

    - aac audio_codec; h264,video_codec;25 framerate 25帧; - Camera-YUV帧序列-YUV帧预处理(镜像 缩放 旋转)-编码器-H264数据 从 ...

  3. datagrid 重载本地数据_音视频系列3:使用ffmpeg + nginx搭建本地转发服务器

    本文与csdn博客同步:https://blog.csdn.net/Hanghang_/article/details/104893135,欢迎关注,点赞,评论. 前言 音视频系列: HectoorZ ...

  4. 推流和拉流的概念以及RTMP和HLS协议

    https://www.bbsmax.com/A/x9J2wZM56o/ 推流为将直播内容推送至服务器的过程:拉流为服务器已有直播内容,用指定地址进行拉取的过程. rtmp rtmp是Real Tim ...

  5. Windows搭建Nginx直播推流服务器

    转载请以链接形式标明出处: 本文出自:103style的博客 目录 资源下载 配置环境变量 检查Nginx 是否能成功启动 修改 Nginx 配置 推流测试 关闭 Nginx 资源下载 将下述资源下载 ...

  6. 20190227最近比较纠结的问题vue的video中视频的播放和nginx-rtmp的推流以及什么时候推流的分析

    1.vue中的video的使用(支持MP4) Vue中引入Video.js视频播放器 参考:https://www.jianshu.com/p/8b8023c7ed37 Video.js是一个有着HT ...

  7. FFmpeg循环推流

    正常推流命令: ffmpeg -i 文件名 -c copy -f flv rtmp://IP:端口/test(分组名)/1 循环推流命令: ffmpeg -re -stream_loop -1 -i ...

  8. iOS-直播的推流与拉流框架

    iOS-直播的推流与拉流框架 前言 前期准备:搭建基于RTMP的本地Nginx服务器和VLC安装 搭建基于RTMP的本地Nginx服务器 VLC安装(此步骤可在集成完LFLiveKit和IJKPlay ...

  9. ffmpeg rtmp 不清晰_音视频系列3:使用ffmpeg + nginx搭建本地转发服务器

    本文与csdn博客同步:https://blog.csdn.net/Hanghang_/article/details/104893135,欢迎关注,点赞,评论. 前言 音视频系列: HectoorZ ...

  10. 【mia】服务器构建和启动及srssdk推流拉流测试

    构建不带本地推流的mia纯服务器,以使用srspublisher 推流测试. mia.cpp // // Copyright (c) 2021- anjisuan783 // // SPDX-Lice ...

最新文章

  1. RecyclerView的使用(1)之HelloWorld
  2. 推荐8个极受欢迎的网站和软件,让你总有一天你会用到!
  3. matplotlib如何绘制两点间连线_如何用 Python 快速揭示数据之间的各种关系
  4. Scala比较器:Ordered与Ordering
  5. ov5640帧率配置_赛博朋克2077 优化设置大全!帧数50暴涨100
  6. petshop4.0 详解之五(PetShop之业务逻辑层设计)[转]
  7. 具有中央异常处理和VO验证的Spring Data JPA –框架
  8. 网络定位-能定位到国家省份市区县街道
  9. Delphi IOS (二)
  10. Oracle中 ORA-12704:字符集不匹配
  11. 拓端tecdat|R语言自定义两种统计量度:平均值和中位数,何时去使用?
  12. 一个基于特征向量的近似网页去重算法
  13. 视频教程-MATLAB图像处理-Matlab
  14. 银河麒麟Linux系统安装谷歌浏览器
  15. 16位LED恒流源芯片TC5020A,32*128点阵屏驱动函数
  16. MYSQL数据库日志
  17. 时间序列数据处理2——时间序列聚类算法
  18. Java Swing中JFreeChart构建双纵轴(双Y轴)图表的使用纪要
  19. OpenGL glMaterialfv材质设置 用例
  20. 新商用密码产品认证梳理——政策法规篇

热门文章

  1. 阵列信号处理笔记-阵列信号处理基础
  2. Html - Json转excel文件
  3. Delphi 鼠标移动
  4. 破解Kindle,轻松自定义字体
  5. ubuntu系统配置i3wm窗口管理器
  6. 干净卸载VS2015
  7. r语言熵权法求权重(真实案例完整流程)
  8. 营业执照识别项目记录--CTPN使用
  9. cesium 构建天空盒
  10. eeglab新建电极位置并保存为文件