分类目录WebRTC

SDL渲染窗口

SDL官网
http://www.libsdl.org/

下载:
http://www.libsdl.org/download-2.0.php

生成Makefile
configure --prefix=/usr/local/sdl

安装
sudo make -j 8 && make install

使用
添加头文件 #include<SDL.h>
初始化SDL SDL_Init()
退出SDL SDL_Quit()

SDL_CreateWindow()/SDL_DestoryWindow()

SDL_CreateRender()

clang -g -o xxsdl xxxsdl.c `pkg-config --cflags --libs sdl2`

ffmpeg

是一个跨平台的多媒体框架
windows,macOS下载
https://ffmpeg.zeranoe.com/builds/
linux
http://ffmpeg.org/releases/
查看文件信息工具
mediaInfo

tar -jxvf ffmpeg-3.4.1.tar.bz2
cd ffmpeg-3.4.1
安装yasm ----yum install yasm
./configure --enabled-shared --prefix=/usr/local/ffmpeg
安装:make && make install
修改文件/etc/ld.so.conf 中增加/usr/local/ffmpeg/lib.
        使其生效:ldconfig
加入环境变量:vi /etc/profile
        最后一行:export FFMPEG_HOME=/usr/local/ffmpeg
export PATH=$FFMPEG_HOME/bin:$PATH
        使环境变量生效:source /etc/profile

源码下载
git clone https://git.ffmpeg.org/ffmpeg.git
按需配制模块
./configure –help | more
安装yasm的汇编编译器
yasm:http://yasm.tortall.net/Download.html
sudo apt-get install yasm

Mac 平台:

./configure –prefix=/usr/local/ffmpeg
–enable-gpl
–enable-nonfree
–enable-libfdk-aac
–enable-libx264
–enable-libx265
–enable-filter=delogo
–enable-debug
–disable-optimizations
–enable-libspeex
–enable-videotoolbox
–enable-shared
–enable-pthreads
–enable-version3
–enable-hardcoded-tables
–cc=clang
–host-cflags=
–host-ldflags=
Linux平台
./configure –prefix=/usr/local/ffmpeg \
–enable-gpl \
–enable-nonfree \
–enable-libfdk-aac \
–enable-libx264 \
–enable-libx265 \
–enable-filter=delogo \
–enable-debug \
–disable-optimizations \
–enable-libspeex \
–enable-shared \
–enable-pthreads

Windows平台

./configure –prefix=/usr/local/ffmpeg
–enable-gpl
–enable-nonfree
–enable-libfdk-aac
–enable-libx264
–enable-libx265
–enable-filter=delogo
–enable-debug
–disable-optimizations
–enable-libspeex
–enable-static

编译安装
sudo make && make install
执行
/usr/local/ffmpeg/bin/ffmpeg
为方便执行可加入环境变量~/.bash_profile
export PATH=$PATH:/usr/local/ffmpeg/bin

可能遇到的问题
ffmpeg默认安装目录为“/usr/local/lib”,有些64位系统下软件目录则为“/usr/lib64”,编译过程中可能会出现“ffmpeg: error while loading shared libraries: libmp3lame.so.0: cannot open shared object file: No such file or directory”等类似的错误,解决办法是建立软链接或者移动库文件到相应的目录:
ln -s /usr/local/lib/libmp3lame.so.0.0.0 /usr/lib64/libmp3lame.so.0
mv /usr/local/lib/libmp3lame.so.0.0.0 /usr/lib64/libmp3lame.so.0

ERROR:libfdk_aac not found
apt install fdk-aac
http://sourceforge.net/projects/opencore-amr/?source=directory
下载fdk-aac-0.1.1.tar.gz
执行
configure
make
make install

git clone git://github.com/mstorsjo/fdk-aac
cd fdk-aac
autoreconf -i
./configure
make install

ERROR: speex not found using pkg-config
apt install pkg-config
apt-get install libspeex-dev

ERROR: libx264 not found
apt/brew/yum install x264。(注:在Linux下应该安装 libx264-dev)

ffmpeg: error while loading shared libraries: libavdevice.so.58: cannot open shared object file: No such file or directory
sudo vi /etc/ld.so.conf
在文件中添加路径:
/usr/local/ffmpeg/lib
更新环境变量:
sudo ldconfig
加入全局环境变量路径:
sudo vi /etc/profile
在文件中加入以下内容:
export PATH=”/usr/local/ffmpeg/bin:$PATH”
然后保存并运行source /etc/profile

查看下需要哪些依赖:
ldd ffmpeg
找下这些文件在哪里
find /usr -name ‘libavdevice.so.58’

FYI 《FFmpeg精讲与实战》常见问题与解答
https://www.imooc.com/article/253497

windows下安装
www.imooc.com/article/247113

基本信息查询命令

FFMPEG 可以使用下面的参数进行基本信息查询。例如,想查询一下现在使用的 FFMPEG 都支持哪些 filter,就可以用 ffmpeg -filters 来查询。详细参数说明如下:
-version 显示版本。 ffmpeg -version
-formats 显示可用的格式(包括设备)。
-demuxers 显示可用的demuxers。
-muxers 显示可用的muxers。
-devices 显示可用的设备。
-codecs 显示libavcodec已知的所有编解码器。
-decoders 显示可用的解码器。
-encoders 显示所有可用的编码器。
-bsfs 显示可用的比特流filter。
-protocols 显示可用的协议。
-filters 显示可用的libavfilter过滤器。
-pix_fmts 显示可用的像素格式。
-sample_fmts 显示可用的采样格式。
-layouts 显示channel名称和标准channel布局。
-colors 显示识别的颜色名称。

接下来介绍的是 FFMPEG 处理音视频时使用的命令格式与参数。
命令基本格式及参数

下面是 FFMPEG 的基本命令格式:

ffmpeg [global_options] {[input_file_options] -i input_url} …
{[output_file_options] output_url} …

ffmpeg 通过 -i 选项读取输任意数量的输入“文件”(可以是常规文件,管道,网络流,抓取设备等,并写入任意数量的输出“文件”。
原则上,每个输入/输出“文件”都可以包含任意数量的不同类型的视频流(视频/音频/字幕/附件/数据)。 流的数量和/或类型是由容器格式来限制。 选择从哪个输入进入到哪个输出将自动完成或使用 -map 选项。
要引用选项中的输入文件,您必须使用它们的索引(从0开始)。 例如。 第一个输入文件是0,第二个输入文件是1,等等。类似地,文件内的流被它们的索引引用。 例如。 2:3是指第三个输入文件中的第四个流。
上面就是 FFMPEG 处理音视频的常用命令,下面是一些常用参数:
主要参数

-f fmt(输入/输出) 强制输入或输出文件格式。 格式通常是自动检测输入文件,并从输出文件的文件扩展名中猜测出来,所以在大多数情况下这个选项是不需要的。
-i url(输入) 输入文件的网址
-y(全局参数) 覆盖输出文件而不询问。
-n(全局参数) 不要覆盖输出文件,如果指定的输出文件已经存在,请立即退出。
-c [:stream_specifier] codec(输入/输出,每个流) 选择一个编码器(当在输出文件之前使用)或解码器(当在输入文件之前使用时)用于一个或多个流。codec 是解码器/编码器的名称或 copy(仅输出)以指示该流不被重新编码。如:ffmpeg -i INPUT -map 0 -c:v libx264 -c:a copy OUTPUT
-codec [:stream_specifier]编解码器(输入/输出,每个流) 同 -c
-t duration(输入/输出) 当用作输入选项(在-i之前)时,限制从输入文件读取的数据的持续时间。当用作输出选项时(在输出url之前),在持续时间到达持续时间之后停止输出。
-ss位置(输入/输出) 当用作输入选项时(在-i之前),在这个输入文件中寻找位置。 请注意,在大多数格式中,不可能精确搜索,因此ffmpeg将在位置之前寻找最近的搜索点。 当转码和-accurate_seek被启用时(默认),搜索点和位置之间的这个额外的分段将被解码和丢弃。 当进行流式复制或使用-noaccurate_seek时,它将被保留。当用作输出选项(在输出url之前)时,解码但丢弃输入,直到时间戳到达位置。
-frames [:stream_specifier] framecount(output,per-stream) 停止在帧计数帧之后写入流。
-filter [:stream_specifier] filtergraph(output,per-stream) 创建由filtergraph指定的过滤器图,并使用它来过滤流。filtergraph是应用于流的filtergraph的描述,并且必须具有相同类型的流的单个输入和单个输出。在过滤器图形中,输入与标签中的标签相关联,标签中的输出与标签相关联。有关filtergraph语法的更多信息,请参阅ffmpeg-filters手册。

视频参数

-vframes num(输出) 设置要输出的视频帧的数量。对于-frames:v,这是一个过时的别名,您应该使用它。
-r [:stream_specifier] fps(输入/输出,每个流) 设置帧率(Hz值,分数或缩写)。作为输入选项,忽略存储在文件中的任何时间戳,根据速率生成新的时间戳。这与用于-framerate选项不同(它在FFmpeg的旧版本中使用的是相同的)。如果有疑问,请使用-framerate而不是输入选项-r。作为输出选项,复制或丢弃输入帧以实现恒定输出帧频fps。
-s [:stream_specifier]大小(输入/输出,每个流) 设置窗口大小。作为输入选项,这是video_size专用选项的快捷方式,由某些分帧器识别,其帧尺寸未被存储在文件中。作为输出选项,这会将缩放视频过滤器插入到相应过滤器图形的末尾。请直接使用比例过滤器将其插入到开头或其他地方。格式是’wxh’(默认 – 与源相同)。
-aspect [:stream_specifier] 宽高比(输出,每个流) 设置方面指定的视频显示宽高比。aspect可以是浮点数字符串,也可以是num:den形式的字符串,其中num和den是宽高比的分子和分母。例如“4:3”,“16:9”,“1.3333”和“1.7777”是有效的参数值。如果与-vcodec副本一起使用,则会影响存储在容器级别的宽高比,但不会影响存储在编码帧中的宽高比(如果存在)。
-vn(输出) 禁用视频录制。
-vcodec编解码器(输出) 设置视频编解码器。这是-codec:v的别名。
-vf filtergraph(输出) 创建由filtergraph指定的过滤器图,并使用它来过滤流。
音频参数

-aframes(输出) 设置要输出的音频帧的数量。这是-frames:a的一个过时的别名。
-ar [:stream_specifier] freq(输入/输出,每个流) 设置音频采样频率。对于输出流,它默认设置为相应输入流的频率。对于输入流,此选项仅适用于音频捕获设备和原始分路器,并映射到相应的分路器选件。
-ac [:stream_specifier]通道(输入/输出,每个流) 设置音频通道的数量。对于输出流,它默认设置为输入音频通道的数量。对于输入流,此选项仅适用于音频捕获设备和原始分路器,并映射到相应的分路器选件。
-an(输出) 禁用录音。
-acodec编解码器(输入/输出) 设置音频编解码器。这是-codec的别名:a。
-sample_fmt [:stream_specifier] sample_fmt(输出,每个流) 设置音频采样格式。使用-sample_fmts获取支持的样本格式列表。
-af filtergraph(输出) 创建由filtergraph指定的过滤器图,并使用它来过滤流。
了解了这些基本信息后,接下来我们看看 FFMPEG 具体都能干些什么吧。
录制

首先通过下面的命令查看一下 mac 上都有哪些设备。
ffmpeg -f avfoundation -list_devices true -i “”

录屏

ffmpeg -f avfoundation -i 1 -r 30 out.yuv
-f 指定使用 avfoundation 采集数据。
-i 指定从哪儿采集数据,它是一个文件索引号。在我的MAC上,1代表桌面(可以通过上面的命令查询设备索引号)。
-r 指定帧率。按ffmpeg官方文档说-r与-framerate作用相同,但实际测试时发现不同。-framerate 用于限制输入,而-r用于限制输出。
注意,桌面的输入对帧率没有要求,所以不用限制桌面的帧率。其实限制了也没用。
录屏+声音

ffmpeg -f avfoundation -i 1:0 -r 29.97 -c:v libx264 -crf 0 -c:a libfdk_aac -profile:a aac_he_v2 -b:a 32k out.flv
-i 1:0 冒号前面的 “1” 代表的屏幕索引号。冒号后面的”0″代表的声音索相号。
-c:v 与参数 -vcodec 一样,表示视频编码器。c 是 codec 的缩写,v 是video的缩写。
-crf 是 x264 的参数。 0 表式无损压缩。
-c:a 与参数 -acodec 一样,表示音频编码器。
-profile 是 fdk_aac 的参数。 aac_he_v2 表式使用 AAC_HE v2 压缩数据。
-b:a 指定音频码率。 b 是 bitrate的缩写, a是 audio的缩与。

录视频

ffmpeg -framerate 30 -f avfoundation -i 0 out.mp4
-framerate 限制视频的采集帧率。这个必须要根据提示要求进行设置,如果不设置就会报错。
-f 指定使用 avfoundation 采集数据。
-i 指定视频设备的索引号。
视频+音频

ffmpeg -framerate 30 -f avfoundation -i 0:0 out.mp4
录音

ffmpeg -f avfoundation -i :0 out.wav
录制音频裸数据

ffmpeg -f avfoundation -i :0 -ar 44100 -f s16le out.pcm

分解与复用

流拷贝是通过将 copy 参数提供给-codec选项来选择流的模式。它使得ffmpeg省略了指定流的解码和编码步骤,所以它只能进行多路分解和多路复用。 这对于更改容器格式或修改容器级元数据很有用。 在这种情况下,上图将简化为:

由于没有解码或编码,速度非常快,没有质量损失。 但是,由于许多因素,在某些情况下可能无法正常工作。 应用过滤器显然也是不可能的,因为过滤器处理未压缩的数据。

抽取音频流

ffmpeg -i input.mp4 -acodec copy -vn out.aac
acodec: 指定音频编码器,copy 指明只拷贝,不做编解码。
vn: v 代表视频,n 代表 no 也就是无视频的意思。
抽取视频流

ffmpeg -i input.mp4 -vcodec copy -an out.h264
vcodec: 指定视频编码器,copy 指明只拷贝,不做编解码。
an: a 代表视频,n 代表 no 也就是无音频的意思。
转格式

ffmpeg -i out.mp4 -vcodec copy -acodec copy out.flv
上面的命令表式的是音频、视频都直接 copy,只是将 mp4 的封装格式转成了flv。

音视频合并
ffmpeg -i out.h264 -i out.aac -vcodec copy -acodec copy out.mp4

处理原始数据
提取YUV数据

ffmpeg -i input.mp4 -an -c:v rawvideo -pixel_format yuv420p out.yuv
ffplay -s wxh out.yuv
-c:v rawvideo 指定将视频转成原始数据
-pixel_format yuv420p 指定转换格式为yuv420p
YUV转H264

ffmpeg -f rawvideo -pix_fmt yuv420p -s 320×240 -r 30 -i out.yuv -c:v libx264 -f rawvideo out.h264
提取PCM数据

ffmpeg -i out.mp4 -vn -ar 44100 -ac 2 -f s16le out.pcm
ffplay -ar 44100 -ac 2 -f s16le -i out.pcm
PCM转WAV

ffmpeg -f s16be -ar 8000 -ac 2 -acodec pcm_s16be -i input.raw output.wav

滤镜

在编码之前,ffmpeg可以使用libavfilter库中的过滤器处理原始音频和视频帧。 几个链式过滤器形成一个过滤器图形。 ffmpeg区分两种类型的过滤器图形:简单和复杂。
简单滤镜

简单的过滤器图是那些只有一个输入和输出,都是相同的类型。 在上面的图中,它们可以通过在解码和编码之间插入一个额外的步骤来表示:

简单的filtergraphs配置了per-stream-filter选项(分别为视频和音频使用-vf和-af别名)。 一个简单的视频filtergraph可以看起来像这样的例子:

请注意,某些滤镜会更改帧属性,但不会改变帧内容。 例如。 上例中的fps过滤器会改变帧数,但不会触及帧内容。 另一个例子是setpts过滤器,它只设置时间戳,否则不改变帧。
复杂滤镜

复杂的过滤器图是那些不能简单描述为应用于一个流的线性处理链的过滤器图。 例如,当图形有多个输入和/或输出,或者当输出流类型与输入不同时,就是这种情况。 他们可以用下图来表示:

复杂的过滤器图使用-filter_complex选项进行配置。 请注意,此选项是全局性的,因为复杂的过滤器图形本质上不能与单个流或文件明确关联。
-lavfi选项等同于-filter_complex。
一个复杂的过滤器图的一个简单的例子是覆盖过滤器,它有两个视频输入和一个视频输出,包含一个视频叠加在另一个上面。 它的音频对应是amix滤波器。
添加水印

ffmpeg -i out.mp4 -vf “movie=logo.png,scale=64:48[watermask];[in][watermask] overlay=30:10 [out]” water.mp4
-vf中的 movie 指定logo位置。scale 指定 logo 大小。overlay 指定 logo 摆放的位置。

删除水印
先通过 ffplay 找到要删除 LOGO 的位置

ffplay -i test.flv -vf delogo=x=806:y=20:w=70:h=80:show=1
使用 delogo 滤镜删除 LOGO

ffmpeg -i test.flv -vf delogo=x=806:y=20:w=70:h=80 output.flv
视频缩小一倍

ffmpeg -i out.mp4 -vf scale=iw/2:-1 scale.mp4
-vf scale 指定使用简单过滤器 scale,iw/2:-1 中的 iw 指定按整型取视频的宽度。 -1 表示高度随宽度一起变化。
视频裁剪

ffmpeg -i VR.mov -vf crop=in_w-200:in_h-200 -c:v libx264 -c:a copy -video_size 1280×720 vr_new.mp4
crop 格式:crop=out_w:out_h❌y
out_w: 输出的宽度。可以使用 in_w 表式输入视频的宽度。
out_h: 输出的高度。可以使用 in_h 表式输入视频的高度。
x : X坐标
y : Y坐标
如果 x和y 设置为 0,说明从左上角开始裁剪。如果不写是从中心点裁剪。

倍速播放

ffmpeg -i out.mp4 -filter_complex “[0:v]setpts=0.5PTS[v];[0:a]atempo=2.0[a]” -map “[v]” -map “[a]” speed2.0.mp4
-filter_complex 复杂滤镜,[0:v]表示第一个(文件索引号是0)文件的视频作为输入。setpts=0.5PTS表示每帧视频的pts时间戳都乘0.5 ,也就是差少一半。[v]表示输出的别名。音频同理就不详述了。
map 可用于处理复杂输出,如可以将指定的多路流输出到一个输出文件,也可以指定输出到多个文件。”[v]” 复杂滤镜输出的别名作为输出文件的一路流。上面 map的用法是将复杂滤镜输出的视频和音频输出到指定文件中。

对称视频

ffmpeg -i out.mp4 -filter_complex “[0:v]pad=w=2*iw[a];[0:v]hflip[b];[a][b]overlay=x=w” duicheng.mp4
hflip 水平翻转

如果要修改为垂直翻转可以用vflip。
画中画
ffmpeg -i out.mp4 -i out1.mp4 -filter_complex “[1:v]scale=w=176:h=144:force_original_aspect_ratio=decrease[ckout];[0:v][ckout]overlay=x=W-w-10:y=0[out]” -map “[out]” -movflags faststart new.mp4
录制画中画
ffmpeg -f avfoundation -i “1” -framerate 30 -f avfoundation -i “0:0”
-r 30 -c:v libx264 -preset ultrafast
-c:a libfdk_aac -profile:a aac_he_v2 -ar 44100 -ac 2
-filter_complex “[1:v]scale=w=176:h=144:force_original_aspect_ratio=decrease[a];[0:v][a]overlay=x=W-w-10:y=0[out]”
-map “[out]” -movflags faststart -map 1:a b.mp4
多路视频拼接
ffmpeg -f avfoundation -i “1” -framerate 30 -f avfoundation -i “0:0” -r 30 -c:v libx264 -preset ultrafast -c:a libfdk_aac -profile:a aac_he_v2 -ar 44100 -ac 2 -filter_complex “[0:v]scale=320:240[a];[a]pad=640:240[b];[b][1:v]overlay=320:0[out]” -map “[out]” -movflags faststart -map 1:a c.mp4
音视频的拼接与裁剪
裁剪
ffmpeg -i out.mp4 -ss 00:00:00 -t 10 out1.mp4
-ss 指定裁剪的开始时间,精确到秒
-t 被裁剪后的时长。
合并
首先创建一个 inputs.txt 文件,文件内容如下:
file ‘1.flv’
file ‘2.flv’
file ‘3.flv’
然后执行下面的命令:
ffmpeg -f concat -i inputs.txt -c copy output.flv
hls切片
ffmpeg -i out.mp4 -c:v libx264 -c:a libfdk_aac -strict -2 -f hls out.m3u8
-strict -2 指明音频使有AAC。
-f hls 转成 m3u8 格式。
视频图片互转
视频转JPEG
ffmpeg -i test.flv -r 1 -f image2 image-%3d.jpeg
视频转gif
ffmpeg -i out.mp4 -ss 00:00:00 -t 10 out.gif
图片转视频
ffmpeg -f image2 -i image-%3d.jpeg images.mp4
直播相关
推流
ffmpeg -re -i out.mp4 -c copy -f flv rtmp://server/live/streamName
拉流保存
ffmpeg -i rtmp://server/live/streamName -c copy dump.flv
转流
ffmpeg -i rtmp://server/live/originalStream -c:a copy -c:v copy -f flv rtmp://server/live/h264Stream
实时推流
ffmpeg -framerate 15 -f avfoundation -i “1” -s 1280×720 -c:v libx264 -f flv rtmp://localhost:1935/live/room
ffplay
播放YUV 数据
ffplay -pix_fmt nv12 -s 192×144 1.yuv
播放YUV中的 Y平面
ffplay -pix_fmt nv21 -s 640×480 -vf extractplanes=‘y’ 1.yuv

处理流程
输入文件->demuxer->编码数据包->decoder->解码后数据帧->encoder->编码数据包->muxer->输出文件

OpenVidu

https://openvidu.io/

1) 克隆教程:

git clone https://github.com/OpenVidu/openvidu-tutorials.git

2) 您需要在开发计算机中安装http Web server 才能执行本教程。 如果安装了node.js,则可以使用 http-server 来提供应用程序文件。 通过如下命令安装:

npm install -g http-server

3) 运行教程:

openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem

http-server -S -C cert.pem openvidu-tutorials/openvidu-hello-world/web

4) openvidu-server 和 Kurento Media Server 必须在您开发计算机中启动并运行。 最简单的方法是使用Docker容器运行(也可以参考前一篇文字的安装方式):

docker run -p 4443:4443 –rm -e openvidu.secret=MY_SECRET openvidu/openvidu-server-kms

5)一旦服务器运行,我们可以使用浏览器打开https://localhost:8080来测试应用程序。 第一次使用docker容器并加入视频通话时,会提示您接受openvidu-server的自签名证书。

如果您使用的是Windows,请阅读此常见问题解答以正确运行本教程

要了解使用OpenVidu开发的一些技巧,请查看此常见问题解答

二、源码解析

这个应用程序非常简单。 它只有4个文件:

openvidu-browser-VERSION.js:openvidu-browser 库文件, 您不必操纵此文件。app.js:示例应用程序主JavaScritp文件,它使用了 openvidu-browser-VERSION.js。style.css:一些用于样式index.html的CSS类。index.html:表单的HTML代码,用于连接视频通话。 它链接到两个JavaScript文件:
<script src="openvidu-browser-VERSION.js"></script>
<scriptsrc="app.js"></script>

让我们看看在 app.js 中如何使用openvidu-browser-VERSION.js:

第一行声明了代码中不同会话点所需的变量

var OV;
var session;

OV 是我们的 OpenVidu 对象(libray的入口点)。 session是我们视频通话的连接。 作为joinSession() 方法中的第一句,将识别我们视频调用的变量进行初始化,该视频调用从HTML输入中检索值。

var mySessionId = document.getElementById(“sessionId”).value;

初始化 new session 及其 event

OV = new OpenVidu();
session = OV.initSession();

session.on(‘streamCreated’, function (event) {
session.subscribe(event.stream, ‘subscriber’);
});

正如您在代码中看到的,该过程非常简单:获取OpenVidu对象并使用它初始化Session对象。

然后,您可以订阅会话所需的所有事件。 在这种情况下,我们只想订阅会话中正在创建的每个流:在streamCreated上,我们订阅特定的流,在event.stream属性中可用。

您可以查看参考文档中的所有事件

从OpenVidu Server获取token

注意:这就是本教程是一个不安全的应用程序的原因。 我们需要向OpenVidu Server请求用户令牌才能连接到我们的会话。 这个过程应该完全在我们的服务器端进行,而不是在我们的客户端。 但是由于本教程中缺少应用程序后端,JavaScript代码本身将对OpenVidu Server执行POST操作

getToken(mySessionId).then(token => {
// See next point to see how to connect to the session using ‘token’
});

现在我们需要一个来自OpenVidu Server的token。 在生产环境中,我们使用 REST API,OpenVidu Java Client 或 OpenVidu Node Client 在应用程序后端执行此操作。 在这里我们已经在一个getToken()方法中实现了向 OpenVidu Server 发送POST请求,这个 getToken 返回一个带有token的Promise。 没有太多细节,这个方法对 OpenVidu Server 执行两个ajax请求,传递 OpenVidu Server secret来验证它们:

首先,ajax执行POST请求到 /api /sessions(我们发送一个customSessionId字段来命名会话,并使用从HTML输入中检索到的mySessionId值)第二个Ajax将POST请求传递给 /api/token(我们发送一个sessionId字段来将该token分配给同一个会话)

您可以在GitHub仓库中详细检查此方法。

使用token连接到session

getToken(mySessionId).then(token => {

session.connect(token)
.then(() => {
document.getElementById(“session-header”).innerText = mySessionId;
document.getElementById(“join”).style.display = “none”;
document.getElementById(“session”).style.display = “block”;

  var publisher = OV.initPublisher("publisher");
  session.publish(publisher);
})
.catch(error => {
  console.log("There was an error connecting to the session:", error.code, error.message);
});

});

我们只需要调用session.connect 并向 OpenVidu Server 传递最近获取到的token。 此方法返回您可以订阅的Promise。

如果订阅成功,我们首先将视图设置为活动视频会话。 然后继续发布我们的摄像头。 为此,我们需要使用 OpenVidu.initPublisher 方法生成发布者,一个显示我们网络摄像头的新HTML视频将被添加到ID为“publisher”的元素内的页面中。

最后比较重要的一点,我们发布这个 publisher 对象需要使用 session.publish。 此时,连接到此会话的其他用户将触发其自己的streamCreated事件,并可以开始观看我们的网络摄像头。

断开会话

session.disconnect();

无论何时我们想要用户断开会话,我们只需要调用session.disconnect方法。 在这里它会调用内部的 leaveSession 函数,当用户点击“LEAVE”按钮时触发。 该功能也将页面返回到“Join session”视图。

Kurento

主页: http://www.kurento.org/
github: https://github.com/kurento
服务端采用C++实现: https://github.com/Kurento/kurento-media-server
Docker https://hub.docker.com/r/kurento/kurento-media-server
说明文档 https://doc-kurento.readthedocs.io/en/stable/

Kurento模块分为三类:
主要模块与Kurento Media Server开箱即用合并:
kms-core:Kurento Media Server的主要组件。
kms-elements:Kurento Media Elements的实现(WebRtcEndpoint,PlayerEndpoint等)
kms-filters:
Kurento过滤器的实现(FaceOverlayFilter,ZBarFilter等)
内置模块Kurento团队开发的额外模块,用于增强Kurento Media Server的基本功能。到目前为止,有四个内置模块,分别是:
kms-platedetector:用于检测视频流中的车牌的过滤器。
kms-pointerdetector:基于颜色跟踪检测视频流中指针的过滤器。
kms-chroma:过滤器,它在顶层使用颜色范围并使之透明,从而在后面显示另一个图像。
kms-crowddetector:用于检测视频流中人聚集的过滤器。
定制模块

 docker run --name kms -d -p 8888:8888 \
  -e KMS_STUN_IP= \
  -e KMS_STUN_PORT=3478 \
  -e KMS_TURN_URL=user@password:urlorIp:3478 \
   kurento/kurento-media-server:latest

  -e KMS_NETWORK_INTERFACES= \
  -e KMS_EXTERNAL_ADDRESS= \
  -e KMS_MIN_PORT= \
  -e KMS_MAX_PORT= \
  -e KMS_MTU=1200 \
    kurento/kurento-media-server:latest
配制文件位置
/etc/kurento/modules/kurento/WebRtcEndpoint.conf.ini
/etc/kurento/modules/kurento/BaseRtpEndpoint.conf.ini
#查看KMS日志
docker logs kms
#实时查看:
docker logs -f kms
日志输出
    docker logs --follow kms >"kms-$(date '+%Y%m%dT%H%M%S').log" 2>&1

    检查KMS是否已启动并正在侦听连接,请使用以下命令:
curl \
    --include \
    --header "Connection: Upgrade" \
    --header "Upgrade: websocket" \
    --header "Host: 127.0.0.1:8888" \
    --header "Origin: 127.0.0.1" \
    http://127.0.0.1:8888/kurento
应该得到类似于以下内容的响应:
HTTP/1.1 500 Internal Server Error
Server: WebSocket++/0.7.0

#进入镜像
docker exec -it kms /bin/bash
#安装vim
apt-get update
apt-get install vim
#进入配置文件夹
cd /etc/kurento/modules/kurento/
#编辑配置文件
vim WebRtcEndpoint.conf.ini
stunServerAddress=xx.xx.xx.xx
stunServerPort=pp
turnURL=username:userpwd@xx.xx.xx.xx:pp?transport=tcp
or
修改docker运行的kms配置文件方法
1.复制容器中的 配置文件到 宿主机中
docker cp  kms:/etc/kurento/modules/kurento/WebRtcEndpoint.conf.ini  ~/WebRtcEndpoint.conf.ini
2.修改配置文件
vim ~/WebRtcEndpoint.conf.ini
3.宿主机配置文件 复制到 kms容器中
 docker cp ~/WebRtcEndpoint.conf.ini kms:/etc/kurento/modules/kurento/WebRtcEndpoint.conf.ini

#查看当前启动的容器
docker ps
docker restart  {kurento容器ID}

kurento相关配置
1、Kurento Media Server 日志
Kurento Media Server日志文件存储在 /var/log/kurento-media-server/文件夹中。 此文件夹的内容如下:
    media-server_.<log_number>.<kms_pid>.log: Kurento Media Server的当前日志
    media-server_error.log: 第三方错误
    logs: 包含KMS已旋转日志的文件夹
2、Kurento Media Server 配置
    /etc/default/kurento-media-server 默认配置
    /etc/kurento/kurento.conf.json 通用配置
    /etc/kurento/modules/kurento/MediaElement.conf.ini Media Elements 通用参数。
    /etc/kurento/modules/kurento/SdpEndpoint.conf.ini SdpEndpoints的音频/视频参数( 例如: WebRtcEndpoint and RtpEndpoint)。
    /etc/kurento/modules/kurento/WebRtcEndpoint.conf.ini WebRtcEndpoint 特定参数。
    /etc/kurento/modules/kurento/HttpEndpoint.conf.ini HttpEndpoint 特定参数。

#### kurento-hello-world
““
git clone https://github.com/Kurento/kurento-tutorial-java.git
cd kurento-tutorial-java/kurento-hello-world
vim src/main/resources/static/js/index.js
在函数function uiStart()里,增加一个叫iceservers的变量,格式如下:
var iceservers={
“iceServers”:[
{
urls:”stun:xx.xx.xx.xx:3478″
},
{
urls:[“turn:xx.xx.xx.xx:3478″]
username:”xxxx”,
credential: “xxxx”
}
]
}
再修改底下的options变量:
const options = {
localVideo: uiLocalVideo,
remoteVideo: uiRemoteVideo,
mediaConstraints: { audio: true, video: true },
onicecandidate: (candidate) => sendMessage({
id: ‘ADD_ICE_CANDIDATE’,
candidate: candidate,
}),
configuration: iceservers //修改在这里,增加了一个configuration的key
};

启动项目
mvn -U clean spring-boot:run -Dkms.url=ws://xx.xx.xx.xx:8888/kurento

用 idea 打开 kurento-hello-world 项目,下载 maven 依赖完成后,在 run configurations 中添加 new configurations。
选择 maven , command line 中输入:
spring-boot:run -Dkms.url=ws://:8888/kurento # 这里的 public-ip 添你安装 kms 的服务器公网 ip 地址

启动完之后用谷歌或者火狐浏览器打开demo页面https://localhost:8443/
点击start启动

运行 hello world 示例时,会遇到 使用websocket传输内容过长的问题
//项目中创建 package org.kurento.tutorial.helloworld.config
//config 包下创建类
package org.kurento.tutorial.helloworld.config;

@Configuration
//@ComponentScan
//@EnableAutoConfiguration
public class WebAppRootContext implements ServletContextInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println(“org.apache.tomcat.websocket.textBufferSize”);
servletContext.addListener(WebAppRootListener.class);
servletContext.setInitParameter(“org.apache.tomcat.websocket.textBufferSize”,”1024000″);
}

}
““

API Java SDK for Kurento Media Server

https://github.com/Kurento/kurento-java

Kurento tutorials for Node JS

https://github.com/Kurento/kurento-tutorial-node

信令服务器与web客户端 kurento-room

https://github.com/Kurento/kurento-room

NUBOMEDIA

NUBOMEDIA Autonomous Installer
https://nubomedia.readthedocs.io/en/latest/tools/autonomous-installer/

Android客户端开源库

https://github.com/nubomedia-vtt/nubo-test

iOS客户端开源库

https://github.com/nubomediaTI/Kurento-iOS

ffmpeg转发模块

利用ffmpeg可执行文件将本地RTP流封装转发成RTMP推送出去,具体可以参考这个开源工程:
kurento-rtmp https://github.com/godka/kurento-rtmp

gstreamer转发模块

RTP解包 —— 音视频转换 —— FLV封包 —— RTMP推流

coTrun

https://github.com/coturn/coturn
https://github.com/bprodoehl/docker-turnserver
带有Coturn STUN和TURN server (https://github.com/coturn/coturn)的Docker容器,
ocker run -d -e EXTERNAL_IP=1.2.3.4 –name=turnserver –restart=”on-failure:10″ –net=host -p 3478:3478 -p 3478:3478/udp bprodoehl/turnserver
环境参数
SKIP_AUTO_IP-绑定到地址,当与–net =host一起运行时,对IPv4和IPv6双协议栈很有用,EXTERNAL_IP –可选手动指定的外部IP地址端口–监听端口用于STUN和TURNLISTEN_ON_PUBLIC_IP –绑定到外部IPUSE_IPV4 –在确定外部IP时强制IPv4

下载安装

一、ubuntu安装软件包
# apt install coturn

二、配置coturnls
1、复制DTLS、TLS支持的证书文件:
# cp/usr/share/coturn/examples/etc/turn_server_cert.pem /etc/turn_server_cert.pem
# cp/usr/share/coturn/examples/etc/turn_server_pkey.pem /etc/turn_server_pkey.pem
2、编辑/etc/turnserver.conf文件:
listening-port=3478
tls-listening-port=5349
listening-ip=192.168.1.230
external-ip=47.104.19.243(NAT必须)
lt-cred-mech
user=tcp:123456(必须)
realm=example.com(必须)
3、编辑/etc/default/coturn文件:
TURNSERVER_ENABLED=1(必须)

三、完成安装
1、重启coturn
# service coturn restart

2、测试验证
# turnadmin -a -u test -r leifeng-elec.com-p test
# turnutils_uclient 192.168.1.230 -u test -wtest

可以先安装环境
sudo apt-get install libssl-dev
sudo apt-get install libevent-dev
sudo apt-get install libpq-dev
sudo apt-get install mysql-client
sudo apt-get install libmysqlclient-dev
sudo apt-get install libhiredis-dev
sudo apt-get install git

git clone https://github.com/coturn/coturn
./configure --prefix=/usr/local/coturn
make && make install

1) If your system supports automatic start-up system daemon services, 
then to enable the turnserver as a system service that is automatically
started, you have to:

    a) Create and edit /etc/turnserver.conf or 
    /usr/local/etc/turnserver.conf . 
    Use /usr/local/etc/turnserver.conf.default as an example.

    b) For user accounts settings: set up SQLite or PostgreSQL or 
    MySQL or MongoDB or Redis database for user accounts.
    Use /usr/local/share/turnserver/schema.sql as SQL database schema,
    or use /usr/local/share/turnserver/schema.userdb.redis as Redis
    database schema description and/or 
    /usr/local/share/turnserver/schema.stats.redis
    as Redis status & statistics database schema description.

    If you are using SQLite, the default database location is in 
    /var/db/turndb or in /usr/local/var/db/turndb or in /var/lib/turn/turndb.

    c) add whatever is necessary to enable start-up daemon for the 
    /usr/local/bin/turnserver.

2) If you do not want the turnserver to be a system service, 
   then you can start/stop it "manually", using the "turnserver" 
   executable with appropriate options (see the documentation).

3) To create database schema, use schema in file 
/usr/local/share/turnserver/schema.sql.

4) For additional information, run:

   $ man turnserver
   $ man turnadmin
   $ man turnutils

ERROR: OpenSSL Crypto development libraries are not installed properly in required location.
apt-get install libssl-dev

生成md5码:turnadmin -k –u 用户名 -r nanjing -p 密码

生成证书
sudo openssl req -x509 -newkey rsa:2048 -keyout /etc/turn_server_pkey.pem -out /etc/turn_server_cert.pem -days 99999 -nodes

创建turnuserdb.conf文件
vi /etc/turnuserdb.conf
在其中填入之前生成的用户名和key

配制参数 编辑文件/etc/turnserver.conf
listening-port=3478
external-ip=公网IP
user=username:password
realm=域名
以上常用,以下按需
listening-device=填写自己的网卡类型
relay-device=填写自己的网卡类型
listening-ip=ip地址
listening-port=3478
tls-listening-port=5349
listening-ip与relay-ip采用内网ip,external-ip是外网的ip
relay-ip=ip地址
external-ip=ip地址
relay-threads=50
lt-cred-mech
static-auth-secret=用户名
user=用户名:密钥
userdb=/etc/turnuserdb.conf
#max-bps=102400
pidfile="/var/run/turnserver.pid"
no-loopback-peers
no-multicast-peers
sha256
mobility
no-cli
cert=/etc/turn_server_cert.pem
pkey=/etc/turn_server_pkey.pem
stale-nonce
use-auth-secret
Verbose
fingerprint

运行:
sudo turnserver -L 23.83.233.168 -o -a -b /etc/turnuserdb.conf -f -r nanjing

修改信令连接
 var pcConfig = {
            'iceServers': [
                {
                    'url': 'stun:xx.xx.xx.xxip:3478',
                },
                {
                    'url': 'stun:stun.xx.xx.xx.xxip:3478',
                },
                {
                'urls': 'turn:xxx.cn:3478',
                'credential': "mypasswd", or 密钥
                'username': "myname"
            }]
        };

可以根据需要调整其他参数。有关更多信息,请查看Coturn帮助页面:
https://github.com/coturn/coturn/wiki/turnserver
https://github.com/coturn/coturn/wiki/CoturnConfig
编辑文件/etc/default/coturn并取消注释TURNSERVER_ENABLED=1,以便TURN服务器自动启动为系统服务守护程序。

在防火墙中打开以下端口:
3478 TCP和UDP。
49152 – 65535 UDP:按照 RFC 5766,这些是TURN服务器用于交换媒体的端口。可以使用TURN服务器上的min-port和max-port参数更改这些端口。
注意
虽然RFC指定了TURN使用的端口,但如果使用STUN,则需要打开所有UDP端口,因为STUN不会限制可能使用的端口范围。

测试地址
https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/

socket.io for java

Server

<dependency>
      <groupId>com.corundumstudio.socketio</groupId>
      <artifactId>netty-socketio</artifactId>
      <version>1.7.16</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-nop</artifactId>
      <version>1.7.2</version>
    </dependency>

public class ServerStart {

    public static void main(String [] args){
        Configuration config = new Configuration();
        config.setHostname("localhost");
        config.setPort(9999);
        SocketIOServer server = new SocketIOServer(config);
        server.addConnectListener(new ConnectListener() {
            // 添加客户端连接监听器
            public void onConnect(SocketIOClient client) {
                //logger.info(client.getRemoteAddress() + " web客户端接入");
                //不知道如何与客户端对应,好的办法是自己去写对应的函数
                client.sendEvent("connected", "hello");
            }
        });

        //监听客户端事件,client_info为事件名称,-自定义事件
         server.addEventListener("client_info", String.class, new DataListener<String>(){
            public void onData(SocketIOClient client, String data, AckRequest ackRequest) throws ClassNotFoundException {
                //客户端推送advert_info事件时,onData接受数据,这里是string类型的json数据,还可以为Byte[],object其他类型
                String sa = client.getRemoteAddress().toString();
                String clientIp = sa.substring(1,sa.indexOf(":"));//获取客户端连接的ip
                Map params = client.getHandshakeData().getUrlParams();//获取客户端url参数
                System.out.println(clientIp+":客户端:************"+data);
            }
         });

        //添加客户端断开连接事件
        server.addDisconnectListener(new DisconnectListener(){
            public void onDisconnect(SocketIOClient client) {
                String sa = client.getRemoteAddress().toString();
                String clientIp = sa.substring(1,sa.indexOf(":"));//获取设备ip
                System.out.println(clientIp+"-------------------------"+"客户端已断开连接");
                //给客户端发送消息
                client.sendEvent("advert_info",clientIp+"客户端你好,我是服务端,期待下次和你见面");
            }
        });
        server.start();

        while (true){
            try {
                Thread.sleep(1500);
                //广播消息
                server.getBroadcastOperations().sendEvent("borcast","are you live?");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Client

        <dependency>
            <groupId>io.socket</groupId>
            <artifactId>socket.io-client</artifactId>
            <version>1.0.0</version>
        </dependency>

public class AppStart {

    public static void main(String [] args){
        String url ="http://localhost:9999";
        try{
            IO.Options options = new IO.Options();
            options.transports = new String[]{"websocket"};
            options.reconnectionAttempts = 2;
            options.reconnectionDelay = 1000;//失败重连的时间间隔
            options.timeout = 500;//连接超时时间(ms)
            //par1 是任意参数
            final Socket socket = IO.socket(url+"?par1=1234", options);

            socket.on(Socket.EVENT_CONNECT, new Emitter.Listener() {

                public void call(Object... args) {
                    socket.send("hello");
                }
            });

            //自定义事件
            socket.on("borcast", new Emitter.Listener() {
                public void call(Object... objects) {
                    System.out.println("receive borcast data:" + objects[0].toString());
                }
            });

            socket.on("connected", new Emitter.Listener() {
                public void call(Object... objects) {
                    System.out.println("receive connected data:" + objects[0].toString());
                }
            });

            socket.connect();
            //循环发送数据
            while (true){
                socket.emit("client_info"," 客户端在发送数据");
                Thread.sleep(2000);
            }
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }
}

VUE
https://www.npmjs.com/package/vue-socket.io

import VueSocketIO from 'vue-socket.io'
import SocketIO from 'socket.io-client'

Vue.use(new VueSocketIO({
  debug: true,
  connection: SocketIO('http://192.168.88.108:443', {
    path: '', // path千万不要写“/”
    transports: ['websocket', 'xhr-polling', 'jsonp-polling']
  }) // options object is Optional
}))

Other https://github.com/probil/vue-socket.io-extended

import Vue from 'vue'
import VueSocketIOExt from 'vue-socket.io-extended'
import SocketIO from 'socket.io-client';

const socket = SocketIO('https://localhost');
export default ({ store }) => {
  Vue.use(VueSocketIOExt, socket, { store });
}

sockets: {
      // 创建连接
      connect() {
        console.log('连接成功啦')
      },
      // 监听断开连接,函数
      disconnect() {
        console.log('断开服务器连接');
        this.connectdisabled = false;
        this.leavedisabled = true;
        this.senddisabled = true;
        this.inputdisabled = true;
      },
      reconnect() {
        console.log("重新链接");
      },
      // 监听自定义事件,需与服务端emit事件名一致
      joined(room, id) {
        console.log('joined',room, id)
        this.connectdisabled = true;
        this.leavedisabled = false;
        this.senddisabled = false;
        this.inputdisabled = false;
      },

//发送加入信令
this.$socket.client.emit('join', this.room)

WebRtc

https://appr.tc/

WebRTC adapter.js
github地址为:https://github.com/webrtc/adapter.
FYI: http://www.vue5.com/webrtc/webrtc.html
主要功能是隐藏像webkitRTCPeerConnection和mozRTCPeerConnection这样的前缀差异,并提供函数将MediaStream附加到HTML的或元素。
– 直接在代码中加入https://webrtc.github.io/adapter/adapter-latest.js
– npm install webrtc-adapter
使用时在JavaScript文件中加入一个模块
const adapter = require(‘webrtc-adapter’);
加载完成后既可以使用,例如可使用
console.log(adapter.browserDetails.browser);
or
import adapter from ‘webrtc-adapter’;
“child_process”: “^1.0.2”,
“fs”: “0.0.1-security”,
“webrtc-adapter”: “^7.5.1”,
“window”: “^4.2.7”
“net”: “^1.0.2”,
“tls”: “0.0.1”,

vue socket.io的github地址:https://github.com/MetinSeylan/Vue-Socket.io

npm install vue-socket.io --save
npm install socket.io-client --save

chrome里无法正常触发vue-socket.io中的connect回调,vue-socket.io使用到了大量es6的语法,考虑到原生es6在浏览器中的兼容性,导入babel-loader(nuxt cli中没有自带),
{src: ‘@/plugins/vue-socket.io’, ssr: false}

webSocket连接的地址是固定的

在main.js中直接这样写
    Vue.use(new VueSocketIO({
      debug: true,
      connection: 'socket地址:端口号',
      vuex: {       // 不需要用到vuex这个可以不加
            store,
            actionPrefix: 'SOCKET_',
            mutationPrefix: 'SOCKET_'
      }
}))

socket可以与vuex结合使用

import store from'./yourstore'
Vue.use(VueSocketio,socketio('http://socketserver.com:1923'), store);

socket地址是静态的写法

mounted(){
this.$socket.emit('connect', 1)
}

npm install –save-dev @gauseen/nuxt-proxy
// nuxt.config.js

modules: [
// 请求代理配置,解决跨域
‘@gauseen/nuxt-proxy’,
],
proxyTable: {
‘/api’: { target: ‘http://example.com’, ws: false }
},
注:
/api ———————— 每个接口特定标识字段 [String]
target —————— 目标代理服务 [String]
ws —————————— 是否支持 websocket 代理 [Boolean]

express

apt install node-express-generator
npm install -g express-generator
npm -g install express
express -V

错误:在项目目录执行 npm install express 或 npm link express

npm -g install serve-index
npm -g install socket.io
npm install -g forever

运维
npm i pm2 -g
pm2 init
pm2 start ecosystem.config.js

pm2 list
pm2 restart xxxx
pm2 log

mediasoup安装

下载

git clone https://github.com/versatica/mediasoup-demo.git

cd mediasoup-demo

git checkout v3

 app客户端
broadcasters广播推流
server服务器端,媒体服务(管道通信)与信令服务

配置服务器

cd server

npm install

cp config.example.js config.js

// Listening hostname (just for gulp live task).
domain : process.env.DOMAIN || ‘localhost‘,
// Signaling settings (protoo WebSocket server and HTTP API server).
https :
{
listenIp : ‘0.0.0.0’,
// NOTE: Don’t change listenPort (client app assumes 4443).
listenPort : process.env.PROTOO_LISTEN_PORT || 4443,
// NOTE: Set your own valid certificate files.
tls :
{
cert : process.env.HTTPS_CERT_FULLCHAIN || ${__dirname}/certs/<strong><span style="color: #ff0000;">fullchain.pem</span></strong>,//证书
key : process.env.HTTPS_CERT_PRIVKEY || ${__dirname}/certs/<span style="color: #ff0000;"><strong>privkey.key</strong></span> //证书
}
},

// See https://mediasoup.org/documentation/v3/mediasoup/api/#WebRtcTransportOptions
webRtcTransportOptions :
{
listenIps :
[
{
ip : process.env.MEDIASOUP_LISTEN_IP || ‘1.2.3.4′,  //服务器IP或0.0.0.0,浏览器的传输
announcedIp : process.env.MEDIASOUP_ANNOUNCED_IP
}
],
initialAvailableOutgoingBitrate : 1000000,
minimumAvailableOutgoingBitrate : 600000,
maxSctpMessageSize : 262144,
// Additional options that are not part of WebRtcTransportOptions.
maxIncomingBitrate : 1500000
},

// See https://mediasoup.org/documentation/v3/mediasoup/api/#PlainRtpTransportOptions
plainRtpTransportOptions :
{
listenIp :
{
ip : process.env.MEDIASOUP_LISTEN_IP || ‘1.2.3.4‘, //普通的传输
announcedIp : process.env.MEDIASOUP_ANNOUNCED_IP
},
maxSctpMessageSize : 262144
}

/启动server
npm start

//客户端安装 app目录
npm install
npm install -g gulp-cli
gulp live //客户端
gulp dist //生成发布目录/server/public

启动nodejs服务
node app.js
nohup node app.js &
forever start app.js 推荐此方式

调试
node –inspect-brk server.js
chrome://inspect
再根据需要设置断点