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

news2024/11/16 18:09:41

腾讯云服务器使用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

 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1663019.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

常见排序算法——希尔排序

基本原理 希尔排序在插入排序的基础之上&#xff0c;将待排序序列分成组&#xff0c;分成 gap 个组&#xff0c;组的数量通过 length / 2 获得&#xff0c;比如6个元素的序列&#xff0c;那么就是 3 个组&#xff0c;每个组两个元素&#xff0c;然后将每个组的元素进行插入排…

Ardupilot Rpanion iperf网络性能测试

Ardupilot Rpanion iperf网络性能测试 1. 源由2. 分析3. 安装4. 测试4.1 第一次测试4.1.1 iperf测试参数A4.1.1.1 测试链路14.1.1.2 测试链路24.1.1.3 测试链路3 4.1.2 iperf测试参数B - 测试链路34.1.2.1 测试数据4.1.2.2 数据简单分析4.1.2.3 数据深入分析4.1.2.4 模拟测试网…

霍金《时间简史 A Brief History of Time》书后索引(E--H)

A–D部分见&#xff1a;霍金《时间简史 A Brief History of Time》书后索引&#xff08;A–D&#xff09; 图源&#xff1a;Wikipedia INDEX E Earth: circumference, motion, shape Eclipses Eddington, Arthur Einstein, Albert: biography, see also Relativity; Special…

解决数据丢失烦恼,Tenorshare 4DDiG 数据恢复工具助您一键找回珍贵文件

在数字化时代&#xff0c;我们的生活和工作几乎完全依赖于电脑和移动设备。然而&#xff0c;意外情况时常发生&#xff0c;误删除、格式化、系统崩溃等问题可能会导致重要数据丢失&#xff0c;给我们带来不便和困扰。如何有效地解决数据丢失问题&#xff1f;不用担心&#xff0…

QT---day5,通信

1、思维导图 2、TCp 服务器 #ifndef MYWIDGET_H #define MYWIDGET_H #include <QWidget> #include <QTcpServer> #include <QList> #include <QTcpSocket> #include <QMessageBox> #include <QDebug> #include <QTcpServer> QT_B…

如何更好地使用Kafka? - 故障时解决

要确保Kafka在使用过程中的稳定性&#xff0c;需要从kafka在业务中的使用周期进行依次保障。主要可以分为&#xff1a;事先预防&#xff08;通过规范的使用、开发&#xff0c;预防问题产生&#xff09;、运行时监控&#xff08;保障集群稳定&#xff0c;出问题能及时发现&#…

大文件传输的好帮手Libarchive:功能强大的开源归档文件处理库

在数字化时代&#xff0c;文件的存储和传输对于企业的日常运作至关重要。但是&#xff0c;服务器中的压缩文件往往无法直接查看或预览&#xff0c;这给用户带来了不便。为了解决这一问题&#xff0c;在线解压功能的开发变得尤为重要。接下来&#xff0c;小编将介绍一个能够实现…

【Web后端】Tomcat简介_安装_解决乱码_idea配置

1.1 简介 tomcat是在oracle公司的ISWDK(lavaServer Web DelevopmentKit)的基础上发展起来的一个优秀的开源的servlet容器tomcat使用java语言编写。运行稳定、可靠、效率高&#xff0c;可以和目前 主流web服务器一起工作(如IIS、Apache、 Nginx)tomcat是Apache软件基金会(Apach…

初识指针(4)<C语言>

前言 前面的文章&#xff0c;已经对指针的基础概念以及运用有了初步了解&#xff0c;我们可以进一步探究指针比较深入的知识&#xff0c;下文将主要介绍&#xff1a;使用指针数组模拟二维数组、字符指针变量、数组指针、二维数组传参的本质、函数指针、typedef关键字等。 目录…

计算机网络课设---校园组网

需要word与.pkt文件的添加微信,备注"计网课设",搜索:_Z-Nuyoah 一、设计的目的和任务 通过课程设计,使学生理论联系实际,在实践中进一步了解计算机网络体系结构,深入理解TCP/IP参考模型,掌握各种网络工程技术和网络规划与设计,初步掌握高速局域网技术、广域…

成为计算机视觉(CV)需要掌握哪些技术知识(综述)

在CV领域&#xff0c;深度学习和机器学习技术发挥着至关重要的作用&#xff0c;它们为图像识别、目标检测、图像分割等任务提供了强大的工具和方法。本文将综述CV中需要学习的深度学习和机器学习技术。 一、深度学习技术 卷积神经网络&#xff08;Convolutional Neural Netwo…

PHP 提取数组中的特定的值

需求&#xff1a; 前端展示&#xff1a; &#xff08;1&#xff09;之前的页面&#xff1a; &#xff08;2&#xff09;修改后的页面&#xff1a; 之前接口返回的数据 &#xff1a; 解决办法&#xff1a;提取tags 中的 ’约 的数组 添加到一个新的数组中去 1&#xff1a;一开…

Unity值类型和引用类型

我们都知道C#编程语言中&#xff0c;数据类型被分为了两种&#xff1a; 值类型引用类型 那么什么是值类型&#xff1f;什么是引用类型呢&#xff1f;它们的区别又是什么&#xff1f; 为了搞清楚这些问题&#xff0c;我们先列举一下我们开发中会碰到的值类型和引用类型。 常…

Canvas绘制图片和区域(前端使用Canvas绘制图片,并在图片上绘制区域)

简介&#xff1a;在Web开发中&#xff0c;有时候我们需要在图片上进行一些交互式操作&#xff0c;比如绘制区域、标记等。这种场景下&#xff0c;我们可以使用HTML5的<canvas>元素来实现。Canvas 是 HTML5 提供的一种图形绘制接口&#xff0c;可以通过 JavaScript 在网页…

AI应用案例:供应链平台健康状况和发展趋势分析

某供应链平台在2019年就遍布了中国320个城市&#xff0c;为2600多家企业提供超40万个品类的供应链服务。它是通过直供城市终端销售门店&#xff0c;甚至是消费者&#xff0c;最大限度保证品牌和终端的销售利益。 但是平台交易市值较大、涉及的行业较多&#xff0c;而且打破了传…

linux grep命令搜索指定路径

在Linux开发的过程中grep这个搜索命令&#xff0c;是必不可少的存在。它可以快速的搜索出来我们需要的关键字所在的位置。 有助于我们快速分析定位问题。 下面&#xff0c;分享一个简单实用的小技巧。 原始grep 最终grep grep过滤掉二进制的文件 -I选项 结论 这样子是不…

队列的实现(使用C语言)

完整代码链接&#xff1a;DataStructure: 基本数据结构的实现。 (gitee.com) 目录 一、队列的概念&#xff1a; 二、队列的实现&#xff1a; 使用链表实现队列&#xff1a; 1.结构体设计&#xff1a; 2.初始化&#xff1a; 3.销毁&#xff1a; 4.入队&#xff1a; 5.…

深入了解 Flask Request

文章目录 获取请求数据获取请求信息文件上传总结 Flask 是一个轻量级的 Python Web 框架&#xff0c;其简洁的设计和灵活的扩展性使其成为了许多开发者的首选。在 Flask 中&#xff0c;处理 HTTP 请求是至关重要的&#xff0c;而 Flask 提供了丰富而强大的 request 对象来处理…

【Linux网络编程】I/O多路转接之select

select 1.初识select2.了解select基本概念和接口介绍3.select服务器4.select特点及优缺点总结 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f603;…

企业微信主体能不能修改?

企业微信变更主体有什么作用&#xff1f;当我们的企业因为各种原因需要注销或已经注销&#xff0c;或者运营变更等情况&#xff0c;企业微信无法继续使用原主体继续使用时&#xff0c;可以申请企业主体变更&#xff0c;变更为新的主体。企业微信变更主体的条件有哪些&#xff1…