基于D1开发板和腾讯云nginx服务器构建家庭视频监控方案

腾讯云服务器使用nginx搭建rtmp服务器

什么是nginx?

nginx是一款优秀的反向代理工具,通过nginx可以实现搭建高可用的轻量级web服务器,除此之外,通过Nginx自带的rtmp模块,也可以实现rtmp服务器的搭建。

安装nginx

安装编译 nginx 所需要的库
 sudo apt-get install build-essential libpcre3 libpcre3-dev libssl-dev
下载 nginx-1.21.6.tar.gz
wget http://nginx.org/download/nginx-1.21.6.tar.gz
下载 nginx-rtmp-module
wget https://github.com/arut/nginx-rtmp-module/archive/master.zip
解压nginx文件
tar -zxvf nginx-1.21.6.tar.gz
解压rtmp模块
unzip master.zip
编译
./configure --with-http_ssl_module --add-module=../nginx-rtmp-module-master
安装
make
sudo make install
启动nginx,检测nginx是否能成功运行
sudo /usr/local/nginx/sbin/nginx
配置nginx使用RTMP, /usr/local/nginx/conf/nginx.conf
rtmp {
        server {
                listen 1935;
                chunk_size 4096;
 
                application live {
                        live on;
                        record off;
                        #hls on;
                        #hls_fragment 5s;
                }
        }
}
开放1935端口
  • iptables -I INPUT -p tcp --dport 1935 -j ACCEPT
  • 在云服务器设置防火墙中打开端口
重启nginx服务器
sudo /usr/local/nginx/sbin/nginx -s stop
sudo /usr/local/nginx/sbin/nginx
确认端口是否开启成功
netstat -npl | grep 1935
netstat -antp | grep 1935
Gstreamer推流
gst-launch-1.0 videotestsrc ! openh264enc ! h264parse ! flvmux ! rtmp2sink location="rtmp://114.132.63.31:1935/live/test"
VLC拉流
rtmp://114.132.63.31:1935/live/test

D1开发板环境搭建

环境准备

  • 先准备D1基本环境,确保能正常编译/烧写/运行
  • 默认未将gstreamer编译进来
  • 通过make menuconfig将gstreamer的相关组件编译进来

奇怪异常临时处理

异常1
/home/fuqiang/workspace/tina-d1-h/out/d1-h-nezha/compile_dir/target/alsa-plugins-1.1.4/ipkg-install/usr/share/alsa/alsa.conf.d/
找不到这个目录的一些文件

解决方案:

cp ./out/d1-h-nezha/compile_dir/target/alsa-plugins-1.1.4/pulse/*  /home/fuqiang/workspace/tina-d1-h/out/d1-h-nezha/compile_dir/target/alsa-plugins-1.1.4/ipkg-install/usr/share/alsa/alsa.conf.d/

在另一个目录中找到,cp过去
异常2
( cd /home/fuqiang/workspace/tina-d1-h/out/d1-h-nezha/compile_dir/target/gst-plugins-bad-1.16.3/ipkg-install; cp -fpR ./usr/lib/libgstwayland-1.0.so.* /home/fuqiang/workspace/tina-d1-h/out/d1-h-nezha/compile_dir/target/gst-plugins-bad-1.16.3/ipkg-sunxi/libgst1wayland/usr/lib/ )

cp: cannot stat './usr/lib/libgstwayland-1.0.so.*': No such file or directory

解决方案:

复制代码

环境验证

连接wifi
wifi_scan_results_test
wifi_connect_ap_test deecomp deecomp20210701
ping www.baidu.com

FLV

Header 部分记录了FLV的类型、版本、流信息、Header 长度等。一般整个Header占用9个字节,大于9个字节则表示头部信息在这基础之上还存在扩展数据。FLV Header 如下所示:

Body

Body 是由一个个 PreviousTag+Tag 单元组成的。每个Tag下面有一块4个字节的空间,用来记录这个Tag 的长度。PreviousTagSize用于逆向读取处理,表示的是前面的Tag的大小,第一个PreviousTagSize为0,因为他前面没有tag。FLV Body 的信息排布如下:

Tag

每个Tag 也是由两部分组成的:Tag Header 和 Tag Data

Tag Header

Tag Header 存放了当前Tag的类型,数据长度、时间戳、时间戳扩展、StreamsID等信息,然后再接着数据区Tag Data。Tag的排布如下:

Tag Data
Audio Tag Data
Video Tag Data

H.264/AVC:

RTMP

  • 1.RTMP(实时消息传输协议)是Adobe 公司开发的一个基于TCP的应用层协议
  • 2.RTMP协议中基本的数据单元称为消息(Message)
  • 3.当RTMP协议在互联网中传输数据的时候,消息会被拆分成更小的单元,称为消息块(Chunk)
  • 基于TCP
  • 包括RTMP基本协议及RTMPT/RTMPS/RTMPE等多种变种

librtmp(rtmpdump:RTMPDump (mplayerhq.hu)

librtmp库实现了rtmp协议的客户端功能,以及少数服务端功能,是学习rtmp的一个良好的工具。

librtmp API的使用流程如下:

  • 分配并初始化一个RTMP实例
  • 设置RTMP实例的参数
  • 调用RTMP_Connect函数连接到服务器
  • 调用RTMP_ConnectStream函数创建一个rtmp流
  • 使用RTMP_Read或RTMP_Write进行读写,或者对rtmp流进行控制(如暂停、停止等)
  • 读写完成后,关闭并释放RTMP

rtmpsink

Gstreamer的rtmpsink是基于librtmp实现的

rtmp2sink(gst-plugins-bad/gst/rtmp2)

fuqiang@ubuntu:~/workspace/gstreamer/subprojects/gst-plugins-bad/gst/rtmp2$ tree
.
├── gstrtmp2.c  //plugin注册
├── gstrtmp2element.c  //element注册
├── gstrtmp2elements.h
├── gstrtmp2locationhandler.c  //对location参数的解析处理
├── gstrtmp2locationhandler.h
├── gstrtmp2sink.c  //sink端实现
├── gstrtmp2sink.h
├── gstrtmp2src.c  //src端实现
├── gstrtmp2src.h
├── meson.build
├── rtmp  //rtmp协议具体实现
│   ├── amf.c
│   ├── amf.h
│   ├── rtmpchunkstream.c
│   ├── rtmpchunkstream.h
│   ├── rtmpclient.c
│   ├── rtmpclient.h
│   ├── rtmpconnection.c
│   ├── rtmpconnection.h
│   ├── rtmphandshake.c
│   ├── rtmphandshake.h
│   ├── rtmpmessage.c
│   ├── rtmpmessage.h
│   ├── rtmputils.c
│   └── rtmputils.h
└── TODO

1 directory, 25 files

rtmp2sink的源码分析:

typedef struct
{
  GstBaseSink parent_instance;

  /* properties */
  GstRtmpLocation location;  //推流地址
  gboolean async_connect;
  guint peak_kbps;
  guint32 chunk_size;
  GstRtmpStopCommands stop_commands;
  GstStructure *stats;

  /* If both self->lock and OBJECT_LOCK are needed,
   * self->lock must be taken first */
  GMutex lock;
  GCond cond;

  gboolean running, flushing;

  GstTask *task;
  GRecMutex task_lock;

  GMainLoop *loop;
  GMainContext *context;

  GCancellable *cancellable;
  GstRtmpConnection *connection;
  guint32 stream_id;

  GPtrArray *headers;
  guint64 last_ts, base_ts;     /* timestamp fixup */
} GstRtmp2Sink;
...
static GstStaticPadTemplate gst_rtmp2_sink_sink_template =  //接收数据格式只能为flv
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/x-flv")
    );
...
static void
gst_rtmp2_sink_init (GstRtmp2Sink * self)  //实例初始化
{
  self->location.flash_ver = g_strdup ("FMLE/3.0 (compatible; FMSc/1.0)");
  self->location.publish = TRUE;
  self->async_connect = TRUE;
  self->chunk_size = GST_RTMP_DEFAULT_CHUNK_SIZE;
  self->stop_commands = GST_RTMP_DEFAULT_STOP_COMMANDS;

  g_mutex_init (&self->lock);
  g_cond_init (&self->cond);

  self->task = gst_task_new (gst_rtmp2_sink_task_func, self, NULL);  //task线程
  g_rec_mutex_init (&self->task_lock);
  gst_task_set_lock (self->task, &self->task_lock);

  self->headers = g_ptr_array_new_with_free_func
      ((GDestroyNotify) gst_mini_object_unref);
}

static gboolean
gst_rtmp2_sink_start (GstBaseSink * sink)
{
  GstRtmp2Sink *self = GST_RTMP2_SINK (sink);
  gboolean async;

  GST_OBJECT_LOCK (self);
  async = self->async_connect;
  GST_OBJECT_UNLOCK (self);

  GST_INFO_OBJECT (self, "Starting (%s)", async ? "async" : "delayed");

  g_clear_object (&self->cancellable);

  self->running = TRUE;
  self->cancellable = g_cancellable_new ();
  self->stream_id = 0;
  self->last_ts = 0;
  self->base_ts = 0;

  if (async) {
    gst_task_start (self->task);
 //task线程开启,这里面主要做connect的工作
  }

  return TRUE;
}


static GstFlowReturn
gst_rtmp2_sink_render(GstBaseSink * sink, GstBuffer * buffer)  //父类gstbasesink的chain调下来,用于传输buffer
{
  GstRtmp2Sink *self = GST_RTMP2_SINK (sink);
  GstBuffer *message;
  GstFlowReturn ret;

  if (G_UNLIKELY (should_drop_header (self, buffer))) {
    GST_DEBUG_OBJECT (self, "Skipping header %" GST_PTR_FORMAT, buffer);
    return GST_FLOW_OK;
  }

  GST_LOG_OBJECT (self, "render %" GST_PTR_FORMAT, buffer);

  if (G_UNLIKELY (!buffer_to_message (self, buffer, &message))) {  //FLV--->RTMP
    GST_ELEMENT_ERROR (self, STREAM, FAILED, ("Failed to convert FLV to RTMP"),
        ("Failed to convert %" GST_PTR_FORMAT, message));
    return GST_FLOW_ERROR;
  }

  if (G_UNLIKELY (!message)) {
    GST_DEBUG_OBJECT (self, "Skipping %" GST_PTR_FORMAT, buffer);
    return GST_FLOW_OK;
  }

  g_mutex_lock (&self->lock);

  if (G_UNLIKELY (is_running (self) && self->cancellable &&
          gst_task_get_state (self->task) != GST_TASK_STARTED)) {
    GST_DEBUG_OBJECT (self, "Starting connect");
    gst_task_start (self->task);
  }

  while (G_UNLIKELY (is_running (self) && !self->connection)) {
    GST_DEBUG_OBJECT (self, "Waiting for connection");
    g_cond_wait (&self->cond, &self->lock);
  }

  while (G_UNLIKELY (is_running (self) && self->connection &&
          gst_rtmp_connection_get_num_queued (self->connection) > 3)) {
    GST_LOG_OBJECT (self, "Waiting for queue");
    g_cond_wait (&self->cond, &self->lock);
  }

  if (G_UNLIKELY (!is_running (self))) {
    gst_buffer_unref (message);
    ret = GST_FLOW_FLUSHING;
  } else if (G_UNLIKELY (!self->connection)) {
    gst_buffer_unref (message);
    /* send_connect_error has sent an ERROR message */
    ret = GST_FLOW_ERROR;
  } else {
    send_streamheader (self);
send_message (self, message);  //传输数据
    ret = GST_FLOW_OK;
  }

  g_mutex_unlock (&self->lock);
  return ret;
}

static void
send_message (GstRtmp2Sink * self, GstBuffer * message)
{
  GstRtmpMeta *meta = gst_buffer_get_rtmp_meta (message);

  g_return_if_fail (meta != NULL);
  g_return_if_fail (self->stream_id != 0);

  meta->mstream = self->stream_id;

  if (gst_rtmp_message_is_metadata (message)) {
    gst_rtmp_connection_set_data_frame (self->connection, message);  //传输数据
  } else {
    gst_rtmp_connection_queue_message (self->connection, message);
  }
}

FLV转为RTMP主要实现如下:

static gboolean
buffer_to_message(GstRtmp2Sink * self, GstBuffer * buffer, GstBuffer ** outbuf)
{
  GstBuffer *message;
  GstRtmpFlvTagHeader header;
  guint64 timestamp;
  guint32 cstream;

  {
    GstMapInfo info;

    if (G_UNLIKELY (!gst_buffer_map (buffer, &info, GST_MAP_READ))) {
      GST_ERROR_OBJECT (self, "map failed: %" GST_PTR_FORMAT, buffer);
      return FALSE;
    }

    /* FIXME: This is ugly and only works behind flvmux.
     *        Implement true RTMP muxing. */

    if (G_UNLIKELY (info.size >= 4 && memcmp (info.data, "FLV", 3) == 0)) {  //确实是否为FLV格式的数据输入
      /* drop the header, we don't need it */
      GST_DEBUG_OBJECT (self, "ignoring FLV header: %" GST_PTR_FORMAT, buffer);
      gst_buffer_unmap (buffer, &info);
      *outbuf = NULL;
      return TRUE;
    }

    if (!gst_rtmp_flv_tag_parse_header (&header, info.data, info.size)) {  //解析tag header
      GST_ERROR_OBJECT (self, "too small for tag header: %" GST_PTR_FORMAT,
          buffer);
      gst_buffer_unmap (buffer, &info);
      return FALSE;
    }

    if (info.size < header.total_size) {
      GST_ERROR_OBJECT (self, "too small for tag body: buffer %" G_GSIZE_FORMAT
          ", tag %" G_GSIZE_FORMAT, info.size, header.total_size);
      gst_buffer_unmap (buffer, &info);
      return FALSE;
    }

    /* flvmux timestamps roll over after about 49 days */  //时间戳调整
    timestamp = header.timestamp;
    if (timestamp + self->base_ts + G_MAXINT32 < self->last_ts) {
      GST_WARNING_OBJECT (self, "Timestamp regression %" G_GUINT64_FORMAT
          " -> %" G_GUINT64_FORMAT "; assuming overflow", self->last_ts,
          timestamp + self->base_ts);
      self->base_ts += G_MAXUINT32;
      self->base_ts += 1;
    } else if (timestamp + self->base_ts > self->last_ts + G_MAXINT32) {
      GST_WARNING_OBJECT (self, "Timestamp jump %" G_GUINT64_FORMAT
          " -> %" G_GUINT64_FORMAT "; assuming underflow", self->last_ts,
          timestamp + self->base_ts);
      if (self->base_ts > 0) {
        self->base_ts -= G_MAXUINT32;
        self->base_ts -= 1;
      } else {
        GST_WARNING_OBJECT (self, "Cannot regress further;"
            " forcing timestamp to zero");
        timestamp = 0;
      }
    }
    timestamp += self->base_ts;
    self->last_ts = timestamp;

    gst_buffer_unmap (buffer, &info);
  }

  switch (header.type) {  //获取stream的类型
    case GST_RTMP_MESSAGE_TYPE_DATA_AMF0:
      cstream = 4;
      break;

    case GST_RTMP_MESSAGE_TYPE_AUDIO:
      cstream = 5;
      break;

    case GST_RTMP_MESSAGE_TYPE_VIDEO:
      cstream = 6;
      break;

    default:
      GST_ERROR_OBJECT (self, "unknown tag type %d", header.type);
      return FALSE;
  }

  /* May not know stream ID yet; set later */
  message = gst_rtmp_message_new (header.type, cstream, 0);  //创建新buffer
  message = gst_buffer_append_region (message, gst_buffer_ref (buffer),
GST_RTMP_FLV_TAG_HEADER_SIZE, header.payload_size);  //根据payload_size的大小来分配内存

  GST_BUFFER_DTS (message) = timestamp * GST_MSECOND;

  *outbuf = message;
  return TRUE;
}

gboolean
gst_rtmp_flv_tag_parse_header(GstRtmpFlvTagHeader * header,  //解析tag header
    const guint8 * data, gsize size)
{
  g_return_val_if_fail (header, FALSE);
  g_return_val_if_fail (data, FALSE);

  /* Parse FLVTAG header as described in
   * video_file_format_spec_v10.pdf page 5 (page 9 of the PDF) */

  if (size < GST_RTMP_FLV_TAG_HEADER_SIZE) {
    return FALSE;
  }

  /* TagType UI8 */
  header->type = GST_READ_UINT8 (data); //1字节

  /* DataSize UI24 */
  header->payload_size = GST_READ_UINT24_BE (data + 1);  //3字节

  /* 4 bytes for the PreviousTagSize UI32 following every tag */
  header->total_size = GST_RTMP_FLV_TAG_HEADER_SIZE + header->payload_size + 4;

  /* Timestamp UI24 + TimestampExtended UI8 */
  header->timestamp = GST_READ_UINT24_BE (data + 4);
header->timestamp |= (guint32) GST_READ_UINT8 (data + 7) << 24;

  /* Skip StreamID UI24. It's "always 0" for FLV files and for aggregated RTMP
* messages we're supposed to use the Stream ID from the AGGREGATE. */

  return TRUE;
}
相关推荐
陈译8 小时前
Grafana——如何迁移Grafana到一台新服务器
运维·服务器·grafana
wangjun51598 小时前
linux redis ipv6、ipv4 只接收本地访问、接收本地和远程访问
linux·运维·服务器
x66ccff8 小时前
【nvidia】NCCL禁用P2P后果权衡
服务器·网络协议·p2p
信阳农夫8 小时前
linux中yum是干啥的?
linux·运维·服务器
黑客老李9 小时前
新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)
java·运维·服务器·前端·xss
huosenbulusi9 小时前
Linux多版本管理工具介绍
linux·运维·服务器
猪萌萌9 小时前
关于如何利用群晖Docker搭建Project Zomboid(僵尸毁灭工程)私人服务器-保姆级教程
服务器·docker·容器·僵尸世界大战·游戏服务器搭建
黑子哥呢?9 小时前
linux----docker配置nginx详细教程
linux·运维·nginx·docker
vdigital10 小时前
本地主机(localhost)11434端口 HTTP 连接10061原因及解决
java·服务器·数据库
147SEO10 小时前
解决DeepSeek服务器繁忙的有效方法
运维·服务器