Total Pageviews

Wednesday, 6 May 2020

nginx-rtmp-module

NGINX-based Media Streaming Server 

NGINX-based Media Streaming Server

nginx-rtmp-module

Project blog

Wiki manual

Google group

Donation page (Paypal etc)

Features

  • RTMP/HLS/MPEG-DASH live streaming
  • RTMP Video on demand FLV/MP4, playing from local filesystem or HTTP
  • Stream relay support for distributed streaming: push & pull models
  • Recording streams in multiple FLVs
  • H264/AAC support
  • Online transcoding with FFmpeg
  • HTTP callbacks (publish/play/record/update etc)
  • Running external programs on certain events (exec)
  • HTTP control module for recording audio/video and dropping clients
  • Advanced buffering techniques to keep memory allocations at a minimum level for faster streaming and low memory footprint
  • Proved to work with Wirecast, FMS, Wowza, JWPlayer, FlowPlayer, StrobeMediaPlayback, ffmpeg, avconv, rtmpdump, flvstreamer and many more
  • Statistics in XML/XSL in machine- & human- readable form
  • Linux/FreeBSD/MacOS/Windows

Build

cd to NGINX source directory & run this:
./configure --add-module=/path/to/nginx-rtmp-module
make
make install
Several versions of nginx (1.3.14 - 1.5.0) require http_ssl_module to be added as well:
./configure --add-module=/path/to/nginx-rtmp-module --with-http_ssl_module
For building debug version of nginx add --with-debug
./configure --add-module=/path/to-nginx/rtmp-module --with-debug

Windows limitations

Windows support is limited. These features are not supported
  • execs
  • static pulls
  • auto_push

RTMP URL format

rtmp://rtmp.example.com/app[/name]
app - should match one of application {} blocks in config
name - interpreted by each application can be empty

Multi-worker live streaming

Module supports multi-worker live streaming through automatic stream pushing to nginx workers. This option is toggled with rtmp_auto_push directive.

Example nginx.conf

rtmp {

    server {

        listen 1935;

        chunk_size 4000;

        # TV mode: one publisher, many subscribers
        application mytv {

            # enable live streaming
            live on;

            # record first 1K of stream
            record all;
            record_path /tmp/av;
            record_max_size 1K;

            # append current timestamp to each flv
            record_unique on;

            # publish only from localhost
            allow publish 127.0.0.1;
            deny publish all;

            #allow play all;
        }

        # Transcoding (ffmpeg needed)
        application big {
            live on;

            # On every pusblished stream run this command (ffmpeg)
            # with substitutions: $app/${app}, $name/${name} for application & stream name.
            #
            # This ffmpeg call receives stream from this application &
            # reduces the resolution down to 32x32. The stream is the published to
            # 'small' application (see below) under the same name.
            #
            # ffmpeg can do anything with the stream like video/audio
            # transcoding, resizing, altering container/codec params etc
            #
            # Multiple exec lines can be specified.

            exec ffmpeg -re -i rtmp://localhost:1935/$app/$name -vcodec flv -acodec copy -s 32x32
                        -f flv rtmp://localhost:1935/small/${name};
        }

        application small {
            live on;
            # Video with reduced resolution comes here from ffmpeg
        }

        application webcam {
            live on;

            # Stream from local webcam
            exec_static ffmpeg -f video4linux2 -i /dev/video0 -c:v libx264 -an
                               -f flv rtmp://localhost:1935/webcam/mystream;
        }

        application mypush {
            live on;

            # Every stream published here
            # is automatically pushed to
            # these two machines
            push rtmp1.example.com;
            push rtmp2.example.com:1934;
        }

        application mypull {
            live on;

            # Pull all streams from remote machine
            # and play locally
            pull rtmp://rtmp3.example.com pageUrl=www.example.com/index.html;
        }

        application mystaticpull {
            live on;

            # Static pull is started at nginx start
            pull rtmp://rtmp4.example.com pageUrl=www.example.com/index.html name=mystream static;
        }

        # video on demand
        application vod {
            play /var/flvs;
        }

        application vod2 {
            play /var/mp4s;
        }

        # Many publishers, many subscribers
        # no checks, no recording
        application videochat {

            live on;

            # The following notifications receive all
            # the session variables as well as
            # particular call arguments in HTTP POST
            # request

            # Make HTTP request & use HTTP retcode
            # to decide whether to allow publishing
            # from this connection or not
            on_publish http://localhost:8080/publish;

            # Same with playing
            on_play http://localhost:8080/play;

            # Publish/play end (repeats on disconnect)
            on_done http://localhost:8080/done;

            # All above mentioned notifications receive
            # standard connect() arguments as well as
            # play/publish ones. If any arguments are sent
            # with GET-style syntax to play & publish
            # these are also included.
            # Example URL:
            #   rtmp://localhost/myapp/mystream?a=b&c=d

            # record 10 video keyframes (no audio) every 2 minutes
            record keyframes;
            record_path /tmp/vc;
            record_max_frames 10;
            record_interval 2m;

            # Async notify about an flv recorded
            on_record_done http://localhost:8080/record_done;

        }


        # HLS

        # For HLS to work please create a directory in tmpfs (/tmp/hls here)
        # for the fragments. The directory contents is served via HTTP (see
        # http{} section in config)
        #
        # Incoming stream must be in H264/AAC. For iPhones use baseline H264
        # profile (see ffmpeg example).
        # This example creates RTMP stream from movie ready for HLS:
        #
        # ffmpeg -loglevel verbose -re -i movie.avi  -vcodec libx264
        #    -vprofile baseline -acodec libmp3lame -ar 44100 -ac 1
        #    -f flv rtmp://localhost:1935/hls/movie
        #
        # If you need to transcode live stream use 'exec' feature.
        #
        application hls {
            live on;
            hls on;
            hls_path /tmp/hls;
        }

        # MPEG-DASH is similar to HLS

        application dash {
            live on;
            dash on;
            dash_path /tmp/dash;
        }
    }
}

# HTTP can be used for accessing RTMP stats
http {

    server {

        listen      8080;

        # This URL provides RTMP statistics in XML
        location /stat {
            rtmp_stat all;

            # Use this stylesheet to view XML as web page
            # in browser
            rtmp_stat_stylesheet stat.xsl;
        }

        location /stat.xsl {
            # XML stylesheet to view RTMP stats.
            # Copy stat.xsl wherever you want
            # and put the full directory path here
            root /path/to/stat.xsl/;
        }

        location /hls {
            # Serve HLS fragments
            types {
                application/vnd.apple.mpegurl m3u8;
                video/mp2t ts;
            }
            root /tmp;
            add_header Cache-Control no-cache;
        }

        location /dash {
            # Serve DASH fragments
            root /tmp;
            add_header Cache-Control no-cache;
        }
    }
}

Multi-worker streaming example

rtmp_auto_push on;

rtmp {
    server {
        listen 1935;

        application mytv {
            live on;
        }
    }
}

from https://github.com/arut/nginx-rtmp-module
(https://github.com/AlexWoo/nginx-rtmp-module)
-----------------

使用Nginx 和 RTMP 模块搭建视频直播系统

原文: Setting Up Adaptive Streaming with Nginx by Licson

最近我在为一个组织搭建视频直播系统。对于视频直播来说我是新手,经过一番调研,最终还是觉得Nginx + RTMP module是一个好的选择。
搭建这个系统还是很困难的。经过了好几天的测试和摸索,我得到了一种比较好的配置,值得给大家分享。

Recently, I’m working out a system to smoothly stream live events for an organization. That is pretty new to me and, after a bunch of research, found that Nginx with the RTMP module seems to be a good choice. There are many difficulties when setting all this up and after several days of testing, I found a good setting that is worth a post.

Setup Nginx and RTMP module

First, let’s get Nginx set up. In order to use the RTMP module, we need to compile that as an Nginx module. It would look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apt-get install git gcc make libaio1 libpcre3-dev openssl libssl-dev ffmpeg -y
# Installing the same thing in RHEL/CentOS
yum install git gcc make libaio libaio-devel openssl libssl-devel pcre-devel ffmpeg -y
# Download nginx and nginx-rtmp-module
wget http://nginx.org/download/nginx-1.9.4.tar.gz
git clone https://github.com/arut/nginx-rtmp-module.git
# Compile nginx with nginx-rtmp and libaio
tar zxvf nginx-1.9.4.tar.gz
./configure --prefix=/usr/local/nginx --with-file-aio --add-module=/path/to/nginx-rtmp/
make
make install
# Link nginx
ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx
nginx # Start Nginx
nginx -s stop # Stop Nginx

After all things are done, check whether nginx is compiled properly.
If you can see that Nginx RTMP is included, you can go to the next step. Before we proceed to configuring Nginx for live streaming, we should confirm what kind of resolution we should provide for live streams and how much hardware power you have.

Prerequisites

For converting live streams into several streams for adaptive streaming, you need to make sure your server have enough CPU for such workload. Otherwise, the live stream will suffer from continuous delays and/or server becomes unresponsive. I have spawn some EC2 c3.large and c3.xlarge instances, test with them and I found out their optimized CPU can handle such workload with ease. Something that also worth mention is about the I/O limits of the disks. If possible, store the HLS fragments generated to an high-speed SSD helps maintain smooth streaming experiences.

Then, you also need to think about what kind of resolutions you will be offering for adaptive streaming. Generally about 4-5 variants are good enough to provide great loading speeds for different network speeds and devices. Here’s my recommended list of variants used for live streaming:

  1. 240p Low Definition stream at 288kbps
  2. 480p Standard Definition stream at 448kbps
  3. 540p Standard Definition stream at 1152kbps
  4. 720p High Definition stream at 2048kbps
  5. Source resolution, source bitrate

Configuring nginx for live streaming

Here is my own nginx.conf with comments that you can have references on.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
worker_processes auto;
events {
# Allows up to 1024 connections, can be adjusted
worker_connections 1024;
}
# RTMP configuration
rtmp {
server {
listen 1935; # Listen on standard RTMP port
chunk_size 4000;
# This application is to accept incoming stream
application live {
live on; # Allows live input
# Once receive stream, transcode for adaptive streaming
# This single ffmpeg command takes the input and transforms
# the source into 4 different streams with different bitrate
# and quality. P.S. The scaling done here respects the aspect
# ratio of the input.
exec ffmpeg -i rtmp://localhost/$app/$name -async 1 -vsync -1
-c:v libx264 -c:a libvo_aacenc -b:v 256k -b:a 32k -vf "scale=480:trunc(ow/a/2)*2" -tune zerolatency -preset veryfast -crf 23 -f flv rtmp://localhost/show/$name_low
-c:v libx264 -c:a libvo_aacenc -b:v 768k -b:a 96k -vf "scale=720:trunc(ow/a/2)*2" -tune zerolatency -preset veryfast -crf 23 -f flv rtmp://localhost/show/$name_mid
-c:v libx264 -c:a libvo_aacenc -b:v 1024k -b:a 128k -vf "scale=960:trunc(ow/a/2)*2" -tune zerolatency -preset veryfast -crf 23 -f flv rtmp://localhost/show/$name_high
-c:v libx264 -c:a libvo_aacenc -b:v 1920k -b:a 128k -vf "scale=1280:trunc(ow/a/2)*2" -tune zerolatency -preset veryfast -crf 23 -f flv rtmp://localhost/show/$name_hd720
-c copy -f flv rtmp://localhost/show/$name_src;
}
# This application is for splitting the stream into HLS fragments
application show {
live on; # Allows live input from above
hls on; # Enable HTTP Live Streaming
# Pointing this to an SSD is better as this involves lots of IO
hls_path /mnt/hls/;
# Instruct clients to adjust resolution according to bandwidth
hls_variant _low BANDWIDTH=288000; # Low bitrate, sub-SD resolution
hls_variant _mid BANDWIDTH=448000; # Medium bitrate, SD resolution
hls_variant _high BANDWIDTH=1152000; # High bitrate, higher-than-SD resolution
hls_variant _hd720 BANDWIDTH=2048000; # High bitrate, HD 720p resolution
hls_variant _src BANDWIDTH=4096000; # Source bitrate, source resolution
}
}
}
http {
# See http://licson.net/post/optimizing-nginx-for-large-file-delivery/ for more detail
# This optimizes the server for HLS fragment delivery
sendfile off;
tcp_nopush on;
aio on;
directio 512;
# HTTP server required to serve the player and HLS fragments
server {
listen 80;
location / {
root /path/to/web_player/;
}
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
}
root /mnt/;
add_header Cache-Control no-cache; # Prevent caching of HLS fragments
add_header Access-Control-Allow-Origin *; # Allow web player to access our playlist
}
}
}

Then, configure your live encoder to use these settings to stream into the server:

  • RTMP Endpoint: rtmp://yourserver/live/
  • RTMP Stream Name: [Whatever name you like]

Finally, configure your player for live playback. The HLS URL would look like this:
http://yourserver/hls/[The stream name above].m3u8

If you can adjust the encoder, the following settings can help to gain better experiences.

  • Full HD Resolution (1920×1080) is recommended
  • H.264 Main profile, with target bitrate of 4000Kbps, maximum 6000Kbps
  • 25fps, 2 second keyframe interval
  • AAC audio at 128Kbps, 44.1kHz sample rate

And that’s all! I hope you can enjoy doing live events with these techniques.

-------------------------------------------------------

PingOS

nginx-rtmp-module/rtmp/http-flv/hls/hls+/http-ts/pull/push/relay/upstream/rtsp/nginx 
website Build Status License
PingOS依赖NGINX构建,并且继承arutAlexWoo的nginx-rtmp-module模块。修复arut和AlexWoo版本存在的部分问题外,PingOS在编码和直播协议以及其他方面做了多项功能扩展。

服务器功能

  •  直播协议: RTMP、HTTP(S)-FLV、HTTP(S)-TS、HLS(支持HTTPS)、HLS+(支持HTTPS)、DASH(支持HTTPS)。
  •  音视频编码: H264、H265、MP3、AAC。
  •  直播录像: FLV文件格式和TS文件格式。
  •  GOP缓存: 实现秒开和内存复用。
  •  application支持通配符: “ * ”号通配符实现自动匹配推拉流时使用的application名字,无需累赘的配置。
  •  VHOST功能: 支持配置多个server域名。
  •  控制台接口: 通过HTTP API接口控制推流、拉流以及录像过程。
  •  配置动态加载: 修改配置文件后无需对nginx做任何操作就可读取最新配置。
  •  流量计费: 通过配置自定义流量日志。
  •  变量参数配置: 配置文件中使用变量。
  •  进程间回源: 进程间相互拉流,解决了原生nginx-rtmp-module模块多进程拉流失败的问题。
  •  集群化功能: 服务器间推拉流功能(http-flv、rtmp协议)。
  •  html5网页播放器: pingos-player播放器将持续兼容各浏览器平台,以及多种直播协议。

引导

快速安装

  • 直接安装到系统
    # 快速安装
    git clone https://github.com/pingostack/pingos.git
    
    cd pingos
    
    ./release.sh -i
    
    # 启动服务
    cd /usr/local/pingos/
    ./sbin/nginx

操作说明

推流

推流地址:rtmp://ip/live/流名

播放地址

  • rtmp 播放:rtmp://ip/live/流名
  • http(s)-flv 播放:http(s)://ip/flv/流名
  • hls 播放:http(s)://ip/hls/流名.m3u8
  • hls+ 播放:http(s)://ip/hls2/流名.m3u8
  • http(s)-ts 播放:http(s)://ip/ts/流名

直播流监控后台

访问地址:http://ip/rtmp_stat 通过该页面可以查看当前正在发生的推流和播放记录。

html5播放器

访问地址: http://ip/h5player/flv 这个播放器是基于flv.js的网页播放器,可以实现无插件播放http-flv直播流。 一旦你能够访问这个页面,说明你的直播服务器已经成功搭建起来了。
from  https://github.com/pingostack/pingos
----

A Dockerfile for nginx-rtmp-module + FFmpeg from source with basic settings for streaming HLS. Built on Alpine Linux. 

ar414-nginx-rtmp-ffmpeg

👬前言

最近帮朋友的公司部署了一套分流+水印的直播系统
顺手打包成docker镜像,方便大家需要用到的时候开箱即用,不需要百度一些零碎的文章 也可做简单的直播服务,只需调整配置文件便可达到你的需求.
需求:将直播流分流到两个云厂商的直播云,一个有水印,一个无水印。使用hls播放
朋友需求的拓扑示意图:
ar414-nginx-rtmp
当前拓扑示意图(阿里云和腾讯云不方便放出推流和拉流地址,有兴趣的同学可以去申请玩一下)
ar414-nginx-service

🐳docker-nginx-rtmp-ffmpeg

基于docker-nginx-rtmp进行配置部署,这篇文章的意义是实现直播分流及直播画面水印.
  • Nginx 1.16.1(从源代码编译的稳定版本)
  • nginx-rtmp-module 1.2.1(从源代码编译)
  • ffmpeg 4.2.1(从源代码编译)
  • 已配置好的nginx.conf
    • 只支持1920*1080(如需支持其他分辨率可参考nginx.conf
    • 实现两路分流
      • 本机
      • 直播云(例:阿里云、腾讯云、ucloud)
    • 实现直播水印效果
      • 水印图片存放位置(容器内):/opt/images/logo.png

💻部署运行

服务器

  • 安装docker(Centos7,其他系统请发挥你的搜索功能)
$ yum -y install docker #安装docker
$ systemctl enable docker #配置开机启动
$ systemctl start docker #启动docker服务
  • 拉取docker镜像并运行
#如果速度慢可使用阿里云:docker pull registry.cn-shenzhen.aliyuncs.com/ar414/nginx-rtmp-ffmpeg:v1
$ docker pull ar414/nginx-rtmp-ffmpeg
$ docker run -it -d -p 1935:1935 -p 8080:80 --rm ar414/nginx-rtmp-ffmpeg
  • 推流地址(Stream live content to):
rtmp://:1935/stream/$STREAM_NAME
  • SSL证书
将证书复制到容器内,并在容器内修改nginx.conf配置文件,然后重新commit(操作容器内的文件都需要重新commit才会生效)
#/etc/nginx/nginx.conf
listen 443 ssl;
ssl_certificate     /opt/certs/example.com.crt;
ssl_certificate_key /opt/certs/example.com.key;

OBS配置

  • Stream Type: Custom Streaming Server
  • URL: rtmp://:1935/stream
  • Stream Key:ar414 obs-config

观看测试

HLS播放测试工具:http://player.alicdn.com/aliplayer/setting/setting.html (如果配置了证书则使用https)
  • HLS播放地址
    • 有水印:http://:8080/hls/ar414_wm.m3u8 
    • 无水印:http://:8080/hls/ar414.m3u8 
RTMP测试工具:PotPlayer
  • RTMP播放地址
    • 无水印:rtmp://:1935/stream/ar414 
    • 有水印:需要分流到其他服务器上

📄配置文件简解(分流、水印及水印位置)

  • RTMP配置
rtmp {
    server {
        listen 1935; #端口
        chunk_size 4000;
        #RTMP 直播流配置
        application stream {
            live on;
            #添加水印及分流,这次方便测试直接分流到当前服务器hls
            #实际生产一般都分流到直播云(腾讯云、阿里云、ucloud)
            #只需把需要分流的地址替换即可
            #有水印:rtmp://localhost:1935/hls/$name_wm
            #无水印:rtmp://localhost:1935/hls/$name
            exec ffmpeg -i rtmp://localhost:1935/stream/$name -i /opt/images/ar414.png
              -filter_complex "overlay=10:10,split=1[ar414]"
              -map '[ar414]' -map 0:a -s 1920x1080 -c:v libx264 -c:a aac -g 30 -r 30 -tune zerolatency -preset veryfast -crf 23 -f flv rtmp://localhost:1935/hls/$name_wm
              -c:a libfdk_aac -b:a 128k -c:v libx264 -b:v 2500k -f flv -g 30 -r 30 -s 1920x1080 -preset superfast -profile:v baseline rtmp://localhost:1935/hls/$name;
        }

        application hls {
            live on;
            hls on;
            hls_fragment 5;
            hls_path /opt/data/hls;
        }
    }
}
  • 如果需要推多个直播云则复制多个 exec ffmpeg即可 如下:
application stream {
    live on;
    #分流至本机hls           
    exec ffmpeg -i rtmp://localhost:1935/stream/$name -i /opt/images/ar414.png
      -filter_complex "overlay=10:10,split=1[ar414]"
      -map '[ar414]' -map 0:a -s 1920x1080 -c:v libx264 -c:a aac -g 30 -r 30 -tune zerolatency -preset veryfast -crf 23 -f flv rtmp://localhost:1935/hls/$name_wm
      -c:a libfdk_aac -b:a 128k -c:v libx264 -b:v 2500k -f flv -g 30 -r 30 -s 1920x1080 -preset superfast -profile:v baseline rtmp://localhost:1935/hls/$name;
    
    #分流至腾讯云
    exec ffmpeg -i rtmp://localhost:1935/stream/$name -i /opt/images/ar414.png
      -filter_complex "overlay=10:10,split=1[ar414]"
      -map '[ar414]' -map 0:a -s 1920x1080 -c:v libx264 -c:a aac -g 30 -r 30 -tune zerolatency -preset veryfast -crf 23 -f flv rtmp://live-push.tencent.com/stream/$name_wm
      -c:a libfdk_aac -b:a 128k -c:v libx264 -b:v 2500k -f flv -g 30 -r 30 -s 1920x1080 -preset superfast -profile:v baseline rtmp://live-push.tencent.com/stream/$name;

    #分流至阿里云
    exec ffmpeg -i rtmp://localhost:1935/stream/$name -i /opt/images/ar414.png
      -filter_complex "overlay=10:10,split=1[ar414]"
      -map '[ar414]' -map 0:a -s 1920x1080 -c:v libx264 -c:a aac -g 30 -r 30 -tune zerolatency -preset veryfast -crf 23 -f flv rtmp://live-push.aliyun.com/stream/$name_wm
      -c:a libfdk_aac -b:a 128k -c:v libx264 -b:v 2500k -f flv -g 30 -r 30 -s 1920x1080 -preset superfast -profile:v baseline rtmp://live-push.aliyun.com/stream/$name;
}
  • 水印位置
    • 水印位置
      水印图片位置overlay值
      左上角10:10
      右上角main_w-overlay_w-10:10
      左下角10:main_h-overlay_h-10
      右下角main_w-overlay_w-10 : main_h-overlay_h-10
    • overlay参数
      参数说明
      main_w视频单帧图像宽度(当前配置文件1920)
      main_h视频单帧图像高度(当前配置文件1080)
      overlay_w水印图片的宽度
      overlay_h水印图片的高度

from https://github.com/ar414-com/nginx-rtmp-ffmpeg-conf
------

https://zjdoc-stream.readthedocs.io/zh_CN/latest/
--------

深度解析RTMP直播协议:从保姆级入门到高级优化

涉及到RTMP直播协议,了解其工作原理和优化技巧对于提升直播质量和用户体验至关重要。在这篇详细剖析的文章中,我们将深入探讨RTMP直播协议的各个方面,以及实际开发中遇到的一些情况,并分享一些实用的优化方法,帮助你打造更出色的直播内容。

我们将介绍RTMP协议的基本原理和流程,包括建立连接、数据传输和结束会话等步骤。我们将深入了解RTMP协议的工作机制,包括信令传输、数据分块和流控制等关键概念,以帮助你更好地理解其内部运作。文章最后对 enhanced rtmp 进行了介绍, 推展了RTMP对H265, AV1视频编码的支持。

1. 简介

RTMP协议是Real Time Message Protocol(实时信息传输协议)的缩写,它是由Adobe公司提出的一种应用层的协议,用来解决多媒体数据传输流的多路复用(Multiplexing)和分包(packetizing)的问题。随着VR技术的发展,视频直播等领域逐渐活跃起来,RTMP作为业内广泛使用的协议也重新被相关开发者重视起来。

RTMP协议基本特点

  1. 基于TCP协议的应用层协议

  2. 默认通信端口1935

RTMP URL格式

rtmp://ip:[port]/appName/streamName
例如: rtmp://192.168.178.218:1935/live/stream

详细参考:https://blog.csdn.net/ai2000ai/article/details/72771461

2. RTMP 握手

RTMP 握手分为简单握手和复杂握手,现在Adobe公司使用RTMP协议的产品用复杂握手的较多,不做介绍。

握手包格式


 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|     version   |
+-+-+-+-+-+-+-+-+
 C0 and S0 bits

C0和S0:1个字节,包含了RTMP版本, 当前RTMP协议的版本为 3


 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           time (4 bytes)                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           zero (4 bytes)                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           random bytes                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           random bytes                        |
|                               (cont)                          |
|                               ....                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                        C1 and S1 bits

C1和S1:4字节时间戳,4字节的01528字节的随机数


  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                          time (4 bytes)                       |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                          time2 (4 bytes)                      |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                          random echo                          |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                          random echo                          |
 |                             (cont)                            |
 |                              ....                             |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                            C2 and S2 bits

C2和S2:4字节时间戳,4字节从对端读到的时间戳,1528字节随机数

RTMP握手基本过程


+-------------+                            +-------------+
|   Client    |      TCP/IP Network        |     Server  |
+-------------+             |              +-------------+
       |                    |                     |
Uninitialized               |                Uninitialized
       |        C0          |                     |
       |------------------->|           C0        |
       |                    |-------------------->|
       |        C1          |                     |
       |------------------->|           S0        |
       |                    |<--------------------|
       |                    |           S1        |
  Version sent              |<--------------------|
       |        S0          |                     |
       |<-------------------|                     |
       |        S1          |                     |
       |<-------------------|               Version sent
       |                    |           C1        |
       |                    |-------------------->|
       |        C2          |                     |
       |------------------->|           S2        |
       |                    |<--------------------|
    Ack sent                |                   Ack Sent
       |        S2          |                     |
       |<-------------------|                     |
       |                    |           C2        |
       |                    |-------------------->|
Handshake Done              |               Handshake Done
      |                     |                     |
          Pictorial Representation of Handshake
  1. 握手开始于客户端发送C0C1块。服务器收到C0C1后发送S0S1
  2. 当客户端收齐S0S1后,开始发送C2。当服务器收齐C0C1后,开始发送S2
  3. 当客户端和服务器分别收到S2C2后,握手完成。

注意事项:

在实际工程应用中,一般是客户端先将C0, C1块同时发出,服务器在收到C1 之后同时将S0, S1, S2发给客户端。S2的内容就是收到的C1块的内容。之后客户端收到S1块,并原样返回给服务器,简单握手完成。按照RTMP协议个要求,客户端需要校验C1块的内容和S2块的内容是否相同,相同的话才彻底完成握手过程,实际编写程序用一般都不去做校验。

RTMP握手的这个过程就是完成了两件事:

  1. 校验客户端和服务器端RTMP协议版本号
  2. 是发了一堆随机数据,校验网络状况。

3. RTMP 消息

RTMP消息格式:


  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | Message Type |               Payload length                   |
 |   (1 byte)   |                   (3 bytes)                    |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                          Timestamp                            |
 |                          (4 bytes)                            |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                          Stream ID            |
 |                          (3 bytes)            |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                         Message Header
  1. 1字节消息类型

  2. 3字节负载消息长度

  3. 4字节时间戳

  4. 3字节 Stream ID,区分消息流

注意事项:

实际RTMP通信中并未按照上述格式去发送RTMP消息,而是将RTMP 消息分块发送,之后将介绍RTMP消息分块。

RTMP 消息分块(chunking)

而对于基于TCPRTMP协议而言,协议显得特别繁琐,但是有没有更好的替代方案。同时创建RTMP消息分块是比较复杂的地方,涉及到了AFM(也是Adobe家的东西)格式数据的数据。

RTMP消息块格式


 +--------------+----------------+--------------------+--------------+
 | Basic Header | Message Header | Extended Timestamp |  Chunk Data  |
 +--------------+----------------+--------------------+--------------+
 |                                                    |
 |<------------------- Chunk Header ----------------->|
                            Chunk Format

RTMP消息块构成:

  1. Basic Header
  2. Message Header
  3. Extended Timestamp
  4. Chunk Data

Chunk Basic header格式有3种: 格式1:


   0 1 2 3 4 5 6 7
  +-+-+-+-+-+-+-+-+
  |fmt|   cs id   |
  +-+-+-+-+-+-+-+-+
 Chunk basic header 1

格式2:


  0                      1
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |fmt|      0    |  cs id - 64   |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      Chunk basic header 2

格式3:


  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |fmt|         1 |          cs id - 64           |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             Chunk basic header 3

注意事项:

fmt: 用于指定Chunk Header 里面 Message Header的类型,后面会介绍到 cs id: 是chunk stream id的缩写,同一个RTMP消息拆成的 chunk 块拥有相同的 cs id, 用于区分chunk所属的RTMP消息, chunk basic header 的类型cs id占用的字节数来确定

Message Header格式

Message Header的类型通过上文chunk basic header中的fmt指定,共4种:

格式0:


  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                          timestamp            | message length|
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |      message length (cont)    |message type id| msg stream id |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |          message stream id (cont)             |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                Chunk Message Header - Type 0

Message Header占用11个字节, 在chunk stream的开始的第一个chunk的时候必须采用这种格式。

  • timestamp3个字节,因此它最多能表示到16777215=0xFFFFFF=2^24-1, 当它的值超过这个最大值时,这三个字节都置为1,实际的timestamp会转存到Extended Timestamp字段中,接受端在判断timestamp字段24个位都为1时就会去Extended timestamp中解析实际的时间戳。

  • message length3个字节,表示实际发送的消息的数据如音频帧、视频帧等数据的长度,单位是字节。注意这里是Message的长度,也就是chunk属于的Message的总数据长度,而不是chunk本身Data的数据的长度。

  • message type id1个字节,表示实际发送的数据的类型,如8代表音频数据、9代表视频数据。

  • msg stream id:4个字节,表示该chunk所在的流的ID,和Basic HeaderCSID一样,它采用小端存储的方式

格式1:


  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                          timestamp            | message length|
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |      message length (cont)    |message type id|  
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
                Chunk Message Header - Type 1 

Message Header占用7个字节,省去了表示msg stream id的4个字节,表示此chunk和上一次发的chunk所在的流相同。

  • timestamp delta:3个字节,注意这里和格式0时不同,存储的是和上一个chunk的时间差。类似上面提到的timestamp,当它的值超过3个字节所能表示的最大值时,三个字节都置为1,实际的时间戳差值就会转存到Extended Timestamp字段中,接受端在判断timestamp delta字段24个位都为1时就会去Extended timestamp中解析时机的与上次时间戳的差值。

格式2:


  0                   1                   2     
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
 |                   timestamp                   |  
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 
          Chunk Message Header - Type 2 

Message Header占用3个字节,相对于格式1,又省去了表示消息长度的3个字节和表示消息类型的1个字节,表示此chunk和上一次发送的chunk所在的流、消息的长度和消息的类型都相同。余下的这三个字节表示timestamp delta,使用同格式1。

格式3:

0字节,它表示这个chunk的Message Header和上一个是完全相同的,无需再次传送

Extended Timestamp(扩展时间戳)

chunk中会有时间戳timestamp和时间戳差timestamp delta, 只有这两者之一大于3个字节能表示的最大数值0xFFFFFF=16777215时,才会用这个字段来表示真正的时间戳,否则这个字段不传(感谢评论区 @hijiang指出错误)。

扩展时间戳占4个字节,能表示的最大数值就是0xFFFFFFFF=4294967295。当扩展时间戳启用时,timestamp字段或者timestamp delta要全置为1,表示应该去扩展时间戳字段来提取真正的时间戳或者时间戳差。注意扩展时间戳存储的是完整值,而不是减去时间戳或者时间戳差的值。

Chunk Data(块数据): 用户层面上真正想要发送的与协议无关的数据,长度在(0,chunkSize]之间, chunk size默认为128字节。

RTMP 消息分块注意事项

  • Chunk Size

RTMP是按照chunk size进行分块,chunk size 指的是 chunkpayload部分的大小,不包括chunk basic headerchunk message header长度。客户端和服务器端各自维护了两个chunk size, 分别是自身分块的chunk size 和 对端 的chunk size, 默认的这两个chunk size都是128字节。通过向对端发送set chunk size 消息可以告知对方更改了 chunk size的大小。

  • Chunk Type

RTMP消息分成的Chunk4种类型,可以通过 chunk basic header的高两位(fmt)指定,一般在拆包的时候会把一个RTMP消息拆成以格式0开始的chunk,之后的包拆成格式3 类型的chunk,我查看了有不少代码也是这样实现的,这样也是最简单的实现。

如果第二个message和第一个messagemessage stream ID 相同,并且第二个message的长度也大于了chunk size,那么该如何拆包?当时查了很多资料,都没有介绍。后来看了一些源码,如 SRSFFMPEG中的实现,发现第二个message可以拆成Type_1类型一个chunkmessage剩余的部分拆成Type_3类型的chunkFFMPEG中就是这么做的。

RTMP 交互消息

推流RTMP消息交互流程:

关于推流的过程,RTMP的协议文档上给了上图示例,说一下推流注意事项:

Connect 消息

RTMP 命令消息格式:


 +----------------+---------+---------------------------------------+
 |  Field Name    |   Type  |               Description             |
 +--------------- +---------+---------------------------------------+
 |   Command Name | String  | Name of the command. Set to "connect".|
 +----------------+---------+---------------------------------------+
 | Transaction ID | Number  |            Always set to 1.           |
 +----------------+---------+---------------------------------------+
 | Command Object | Object  |  Command information object which has |
 |                |         |           the name-value pairs.       |
 +----------------+---------+---------------------------------------+
 | Optional User  | Object  |       Any optional information        |
 |   Arguments    |         |                                       |
 +----------------+---------+---------------------------------------+

RTMP握手之后先发送一个connect命令消息,命令里面包含什么东西,协议中没有具体规定,实际通信中要携带 rtmp url 中的 appName 字段,并且指定一些编解码的信息,并以AMF格式发送, 下面是用wireshake抓取connect命令需要包含的参数信息:

这些信息协议中并没有特别详细说明, 在librtmpsrs-librtmp这些源码中,以及用wireshark 抓包的时候可以看到。

服务器返回的是一个_result命令类型消息,这个消息的payload length一般不会大于128字节,但是在最新的nginx-rtmp中返回的消息长度会大于128字节。

消息的transactionID是用来标识command类型的消息的,服务器返回的_result消息可以通过transactionID来区分是对哪个命令的回应,connect 命令发完之后还要发送其他命令消息,要保证他们的transactionID不相同。

发送完connect命令之后一般会发一个 set chunk size消息来设置chunk size的大小,也可以不发。

Window Acknowledgement Size 是设置接收端消息窗口大小,一般是2500000字节,即告诉对端在收到设置的窗口大小长度的数据之后要返回一个ACK消息。在实际做推流的时候推流端要接收很少的服务器数据,远远到达不了窗口大小,所以这个消息可以不发。而对于服务器返回的ACK消息一般也不做处理,默认服务器都已经收到了所有消息了。

之后要等待服务器对于connect消息的回应的,一般是把服务器返回的chunk都读完,组包成完整的RTMP消息,没有错误就可以进行下一步了。

Create Stream 消息

创建完RTMP连接之后就可以创建RTMP流,客户端要想服务器发送一个releaseStream命令消息,之后是FCPublish命令消息,在之后是createStream命令消息。

当发送完createStream消息之后,解析服务器返回的消息会得到一个stream ID

这个ID也就是以后和服务器通信的 message stream ID, 一般返回的是1,不固定。

Publish Stream

推流准备工作的最后一步是Publish Stream,即向服务器发一个publish命令消息,消息中会带有流名称字段,即rtmp url中的 streamName,这个命令的message stream ID 就是上面 create stream 之后服务器返回的stream ID,发完这个命令一般不用等待服务器返回的回应,直接发送音视频类型的RTMP数据包即可。有些rtmp库还会发setMetaData消息,这个消息可以发也可以不发,里面包含了一些音视频meta data的信息,如视频的分辨率等等。

整个推流过程rtmp 消息抓包

4. 推送音视频

当以上工作都完成的时候,就可以发送音视频了。音视频RTMP消息的Payload(消息体)中都放的是按照FLV-TAG格式封的音视频包,具体可以参照FLV封装的协议文档。格式必须封装正确,否则会造成播放端不能正常拿到音视频数据,无法播放音视频。

5. 关于RTMP的时间戳

RTMP的时间戳单位是毫秒ms,在发送音视频之前一直为零,发送音视频消息包后时候必须保证时间戳是单调递增的, 按照视频帧间隔累计增加,时间戳必须打准确,否则播放端可能出现音视频不同步的情况。Srs-librtmp的源码中,如果推的是视频文件的话,发现他们是用H264的dts作为时间戳的。实时音视频传输的时候是先获取了下某一时刻系统时间作为基准,然后每次相机采集到的视频包,与起始的基准时间相减,得到时间戳,这样可以保证时间戳的正确性。

6. 关于Chunk Stream ID

RTMP 的Chunk Steam ID是用来区分某一个chunk是属于哪一个message的 ,0和1是保留的。每次在发送一个不同类型的RTMP消息时都要有不用的chunk stream ID, 如上一个Message 是command类型的,之后要发送视频类型的消息,视频消息的chunk stream ID 要保证和上面 command类型的消息不同。每一种消息类型的起始chunk 的类型必须是 Type_0 类型的,表明新的消息的起始。

7. Enhanced Rtmp

Enhanced Rtmp 是对RTMP协议的增强, 2023年03月开始支持 HEVC(H265), VP9, AV1等视频编码,具体参考 Enhanced Rtmp 项目。

OBS(29.1+版本)已经可以支持RTMP推流 H265, 去下载

FFmpeg已经计划支持 Enhanced Rtmp,参考国内ffmpeg 开发者刘歧的分享:FFmpeg直播和低延迟方向的进展

测试发现目前还没有播放器可以直接播放H265 RTMP流,测试的播放器包括:VLC,IINA,ffplay等, 可以自己编译 ffplay 来播放h265码流,参考施维大神的项目:ffmpeg_rtmp_h265, 以及这篇 编译ffplay使其支持播放rtmp h265

支持H265的RTMP服务器还是推荐国产, 杨成立大神的SRS项目,详细推流介绍:SRS HEVC

8. 协议总结

RTMP协议是个比较啰嗦的协议,实现起来也比较复杂,但通信过程过程相对简单。在直播的实际工程应用中,协议上很多地方都没有详细说明,注意了以上提到几点,基本能够保证RTMP音视频的通信正常。以上就是对RTMP协议的简介和一些注意事项,希望能帮到有需要的朋友,另外本文难免有错误或说的不够详细的地方,欢迎指正,一起交流探讨。

9. 参考项目

rtmp-publish-kit:纯Java重写了RTMP协议,做了个Android 推流项目,包含安卓相机采集,编码和RTMP推流
项目地址:https://github.com/pixpark/rtmp-publish-kit

10. 参考文章:

  1. 带你吃透RTMP
  2. 基于libRTMP的流媒体直播之 AAC、H264 推送

 

 

No comments:

Post a Comment