Lithiumee

Lithiumee

Go wild

深入探討 Accsoon Cineeye

背景#

2019 年,一代致迅影眸圖傳發布。自媒體增長迅猛的時代,相機的各種附件也井噴式湧現,三軸穩定器、圖傳、麥克風… 大大小小廠商都在搞,自然不乏許多催熟而來的粗糙產品。影眸圖傳作為致迅的第一代圖傳產品,確實能看出一些小作坊氣息,不過更多的則是好的方面,設計並不突出但很實在,而且使用體驗做得非常不錯:延遲夠低可以看著圖傳跟焦,不外接供電也能使用 3-4 小時。結合發售時 799 元的售價和後期不到 300 元的二手價格,至今仍不失實用性。

從圖傳方案上講,影眸屬於 Wi-Fi 圖傳,並且沒有配備專門的接收器。相機等 HDMI 信號源接入圖傳發射器,接收需要使用移動設備 Wi-Fi 連接圖傳後,通過配套 App 遠程查看圖像。採用 Wi-Fi 是低價圖傳的合適方案,通用的 Wi-Fi 網卡相比專用的無線方案,開發難度和成本都低很多。除 Wi-Fi 外,要廉價地實現滿足 1080P 視頻流傳輸的 5Mbps 及以上速率,方案就很少了。對於追求低價的圖傳來說,不配套專門接收器意味著節省了幾乎一半的硬件成本,還可以利用手機、平板的現有屏幕,無需額外購買顯示設備,降低了購買和使用門檻,使得影眸非常適合個人和小團隊使用,代價是失去了連接大監和導播台的機會。

研究影眸,起初是想了解它如何實現一個體驗不錯的 Wi-Fi 圖傳。在能夠正確解析影眸發送的數據流之後,又開始試圖為它 “定制” 接收端,嘗試解決它沒有接收端而無法連接大屏或用於直播的問題,實現了基於全志 D1 的接收器方案和 OBS Studio 插件接收兩種方案的原型。研究過程中發現選擇影眸下手也算是挑中了軟柿子:海思的硬件方案、Wi-Fi 的傳輸方式、簡陋的初版接收 App,可用手段非常豐富,正好可以學習練手,下文便是對整個過程的記錄。

硬件#

拆下正面的螺絲後即可打開影眸外殼,看到硬件方案。以下是主要芯片和基本信息:

951A1855

951A1854

  • 主芯片:海思 Hi3516ARBCV100 SoC
  • 接口芯片:聯陽 IT6801FN HDMI - BT.1120
  • RAM:海力士 H5TC2G63GFR DDR3L
  • Flash:旺宏 MX25L12835F 16MB SPI Flash
  • 網卡:歐飛信 8121N-UH 模組,高通 Atheros AR1021X 芯片,2x2 802.11 a/n 5G,USB 2.0 接口
  • MCU:意法 STM32F030C8T6

主芯片海思 Hi3516A,處理器為單核 Cortex-A7,官方提供基於 Linux 3.4 內核的 SDK,重點是具備 H.264/H.265 視頻硬件編解碼器。官方定位為 “集成新一代 ISP 的專業 HD IP 攝像頭 SoC”,在監控領域確實被廣泛使用,並且一下揭露了低成本圖傳的本質 —— 在監控方案的基礎上,把圖像傳感器輸入換成 HDMI 輸入,也就成了圖傳。監控方案出貨量非常大,這款 SoC 也去掉了監控不需要的顯示等外設,成本可以做得很低。稍微有些特殊的就是如何將 HDMI 輸入轉換為 SoC 上廣泛具備的 MIPI CSI、BT.1120 等接口,但至少已有現成的 IC 可以選用。類似思路的產品還有這類 HDMI 編碼器,同樣利用海思方案。相比需要外置 Wi-Fi 網卡的 Wi-Fi 圖傳,它們可以利用 SoC 內置的 GMAC,只需外接一個以太網 PHY 芯片即可實現有線網絡連接,能夠採集 HDMI 並穩定地進行直播推流。

image

當初影眸的硬件方案讓我最感震撼的還是僅有 16MB 的 Flash:一個運行 Linux 的設備,僅僅需要 16MB 的空間。不過冷靜分析,很多運行 OpenWRT 的路由器也只需要 16MB 甚至 4MB 的 Flash,視頻處理對空間的需求主要在 RAM 上。只要願意捨弃 Debian 等發行版豐富的軟件包並到處裁剪,為專門任務工作的 Linux 也可以占用很小的空間。

板端環境#

引導#

採用海思芯片,那麼基本上會根據官方 SDK 開發。從板子上明示串口 R、T、G 的三個焊點上接出線來,上電之後便出現了 U-Boot 和 HiLinux 的引導 Log,果然是純正的海思。

image

image

在 U-Boot 下執行 printenv 可以獲取到啟動內核的命令和傳給內核的參數。從輸出可以得知 SPI Flash 的佈局是 1M(boot),3M(kernel),12M(rootfs),rootfs 是 12MB 的 jffs2 文件系統。引導過程為首先通過 SPI Flash(sf)設備探測(probe)獲取 Flash 的信息,然後從 Flash 中偏移 0x100000(1MB)讀取 0x300000(3MB)大小的內核到內存地址 0x82000000 處,最後使用 bootm 命令從內存啟動內核。

bootfile="uImage"
bootcmd=sf probe 0;sf read 0x82000000 0x100000 0x300000;bootm 0x82000000
bootargs=mem=128M console=ttyAMA0,115200 root=/dev/mtdblock2 rootfstype=jffs2 mtdparts=hi_sfc:1M(boot),3M(kernel),12M(rootfs

在系統啟動後#

進入系統後,如何找到板端運行的圖傳程序呢?根據海思的開發環境用戶指南文檔,系統啟動後需要自動運行的程序可以添加進 /etc/init.d/rcS。因此,打開 /etc/init.d/rcS 查看。

image

rcS 中主要內容如下:

首先修改了內核網絡緩衝區,寫緩衝區設置為 0x200000(2MB),讀緩衝區設置為 0x80000(512KB):

#sys conf
sysctl -w net.core.wmem_max=2097152
sysctl -w net.core.wmem_default=2097152
sysctl -w net.core.rmem_max=524288
sysctl -w net.core.rmem_default=524288

加載無線網卡驅動

insmod /ko/wifi/ath6kl/compat.ko
insmod /ko/wifi/ath6kl/cfg80211.ko
insmod /ko/wifi/ath6kl/ath6kl_usb.ko reg_domain=0x8349

ip/dhcp 配置

ifconfig wlan0 10.0.0.1 netmask 255.255.255.0 up
echo udhcpd
udhcpd /etc/wifi/udhcpd.conf &
#echo hostapd
#hostapd /etc/wifi/hostap.conf &

加載 MPP 驅動,與 SDK 文檔一致

cd /ko
./load3516a -i -sensor bt1120 -osmem 128 -online

image

加載 MPP 時主要進行初始化,加載了許多內核模塊,輸出 log 如下

Hisilicon Media Memory Zone Manager
Module himedia: init ok
hi3516a_base: module license 'Proprietary' taints kernel.
Disabling lock debugging due to kernel taint
load sys.ko for Hi3516A...OK!
load tde.ko ...OK!
load region.ko ....OK!
load vgs.ko for Hi3516A...OK!
ISP Mod init!
load viu.ko for Hi3516A...OK!
load vpss.ko ....OK!
load vou.ko ....OK!
load hifb.ko OK!
load rc.ko for Hi3516A...OK!
load venc.ko for Hi3516A...OK!
load chnl.ko for Hi3516A...OK!
load h264e.ko for Hi3516A...OK!
load h265e.ko for Hi3516A...OK!
load jpege.ko for Hi3516A...OK!
load vda.ko ....OK!
load ive.ko for Hi3516A...OK!
==== Your input Sensor type is bt1120 ====
acodec inited!
insert audio
==== Your input Sensor type is bt1120 ====
mipi_init
init phy power successful!
load hi_mipi driver successful!

在此之後,便會運行一個名為 RtMonitor 的程序,所有圖傳業務邏輯均在其中實現。

image

對板端環境的探索可以說過於順利了,並沒有出現任何的阻攔,甚至腳本中還有一些調試時留下的註釋信息。實際上,海思的 SDK 文檔中提供了各個環節加密的手段,如關閉串口、設置 root 賬戶密碼等,若是應用上任何一種都會製造不小的麻煩。

image

傳輸#

抓包#

首先嘗試通過 Wi-Fi 抓包來看看傳輸的都是什么。由於 ARM 架構的 Mac 上可以安裝 iOS 的 App,運行 Accsoon App 後,打開 Wireshark 就能開始抓包了。可以發現有三種包:

  1. 圖傳→接收端 UDP:數據量大,推測是圖傳數據流;
  2. 接收端→圖傳 UDP:很短,推測為數據應答包;
  3. 接收端→圖傳 TCP:打開圖傳界面時發送,觸發上述 UDP 傳輸,之後大約每 0.5-1 秒一個包,推測為心跳保活,內容有 "ACCSOON" 字樣。

image

順帶進行一下監聽模式抓包。可以發現當有多台設備連接時,由於 Wi-Fi 沒有高速的組播 / 廣播機制,需要將數據分別發送給每個設備,成倍地增加了信道壓力。

image 1

Wi-Fi 圖傳的另一個劣勢在於,如果遵守 802.11 協議,沒有修改幀間間隔(interframe space)以及退避(backoff)隨機數等使自身在信道競爭中取得不正當優勢,那麼這個圖傳相比其他 Wi-Fi 設備並沒有更高的傳輸優先級。當信道上其他 Wi-Fi 設備大量活躍時,便會不可避免地導致圖傳卡頓。不過好在 5GHz 信道的擁擠程度一般還是好於 2.4GHz。

反編譯安卓 Apk#

通過抓包還是難以看清數據包的具體內容,尤其是頭部各字段的含義。於是嘗試分析致迅安卓 App 內的邏輯。由於更新的版本為支持其他設備新加入了更多代碼,還是老版本更利於分析。從 apkpure 下載支持影眸圖傳的較老版本(Accsoon 1.2.5 安卓版 APK)。使用 Jadx 對 apk 進行反編譯,主要尋找以下內容:

  • UDP 視頻流數據包組成,用於正確解析視頻流;
  • TCP 控制指令內容和發送邏輯,用於正確觸發設備開始發送功能。

Untitled 7

對 Java 代碼關鍵邏輯的分析:

  • MediaCodecUtil 類
    • 封裝了對 Android 原生編解碼接口 MediaCodec 的操作。

    • 構造函數中對 MediaCodec 初始化,通過初始化時的參數可以得知,所用解碼器為 "video/avc",意味著傳輸的視頻流是 H.264 編碼。

    • 初始化 MediaCodec 時,MediaCodec.configure 方法中傳入一個 SurfaceMediaCodec 將解碼後的視頻幀直接輸出到該 SurfaceBufferQueue 並回調 onFrameAvailable()

    • putDataToInputBuffer 方法,與 MediaCodec 的輸入緩衝區對應。會向緩衝區隊列申請空緩衝區,將需要解碼的數據拷貝進去,然後放入輸入緩衝區隊列。

    • renderOutputBuffer 方法,與 MediaCodec 的輸出緩衝區對應。會從輸出緩衝區隊列獲取已解碼的數據,然後釋放該緩衝區。

image

image

image

  • MediaServerClass 類
    • Start() 方法,調用 MediaRtms.Start()TcpLinkClass.StartMediaStream(),分別啟動 UDP 和 TCP。H264FrameReceiveHandle 作為回調函數在 MediaRtms 實例化時傳入。當 H264FrameReceiveHandle 被調用時,會最終調用到 MediaCodecUtil 中的 putDataToInputBufferrenderOutputBuffer
  • MediaRtms 類
    • rtmsBase 類的簡單封裝。
    • Start() 方法,會創建一個 DatagramSocket,並啟動一個 udpRxThread 線程。在該線程中,不停接收數據,收到一定長度數據後,解析包頭,如果是視頻則調用 H264FrameReceiveHandle 回調。
  • TcpLinkClass 類
    • 調用 StartMediaStream() 後會啟動一個 KeepAliveThread 線程。在該線程中,以 1 秒間隔調用 TcpLinkClass 類中的一個名為 StaOp 的方法,裡面實現了 TCP 連接、發送心跳包、斷開連接的過程。
  • SurfaceRender 類
    • 視頻顯示在 GLSurfaceView 控件上。在 VideoMainActivity 中,調用 setRenderer 方法,將 SurfaceRender 設置為 GLSurfaceView 的渲染器。
    • onSurfaceCreated 方法,創建一個綁定到 OpenGL 紋理(mTextureId)的 SurfaceTexturemSurfaceTexture),用來接收 MediaCodec 解碼後的視頻幀。並創建離屏渲染所需的幀緩衝對象(FrameBuffer)和紋理(Texture),為效果處理做準備。
    • onDrawFrame 方法,繪製當前幀。調用 updateTexImage 方法將 SurfaceTexture 中最新的圖像幀更新到綁定的 OpenGL 紋理。此時切換到離屏渲染,利用著色器程序將視頻幀紋理和 LUT 紋理疊加實現 3D LUT 應用;切換回正常渲染,基於離屏渲染得到的紋理,通過著色器程序實現斑馬線、黑白等效果,並顯示;中心線、比例框等疊加元素最後單獨繪製。

具體看一下數據包頭有多長,有什麼信息。

TCP Frame:

image

image

UDP Frame:

每個 Message 包含一幀的碼流,每個 Message 前有一個 Message Header:

image

每個 Message 被分成若干段 Frame 發送,每個 Frame 前有一個 Frame Header:

image

image

image

H.264 碼流提取#

知道了數據包的結構,便可以開始解析。根據 Frame 分段重組 Message 後,發現 Message 的內容以 0x000001 的固定前綴開頭,具有 NALU(Network Abstraction Layer Unit)的特徵,包含一個一字節的 NALU Header,重點是 nal_unit_type,用於判斷 Payload 中的內容類型。理論上到這裡,只需要把 Message 的內容逐一送進解碼器就可以解碼視頻流了。不過需要注意的是,解碼器需要依靠 SPS 和 PPS 中保存的 profile、level、寬高、deblock 滤波器等參數才能正確解碼,需要在 I 幀前告訴解碼器。因此代碼中最好能根據 nal_unit_type 進行判斷,等待 SPS 和 PPS,使它們最先被送入解碼器。

Untitled 13

NAL Header:

image

NALU Type:

image

接收端設計#

接收方案 1 - 電腦接收#

數據包結構了然之後,只要正確接收數據包,取出其中的 H.264 碼流送入解碼器即可。為便於高效地開發和調試,先在電腦上進行。利用 FFmpeg(libav)或 GStreamer(libgst)等多媒體框架,可以方便地實現解碼。首先嘗試使用 FFmpeg,主要需要經過以下過程:

  1. 解碼器初始化:使用 avcodec_find_decoder() 查找 H.264 解碼器,使用 avcodec_alloc_context3() 創建上下文,使用 avcodec_open2() 打開解碼器。
  2. 數據解碼:使用 av_packet_from_data()將數據存儲到 AVPacket 中,然後使用 avcodec_send_packet() 送入解碼器,使用 avcodec_receive_frame()AVFrame 中取出已解碼的數據。

整個程序大致邏輯為:

  1. 主線程:初始化 FFmpeg 解碼器和 SDL 顯示,啟動 UDP 和 TCP 線程。隨後循環等待可用數據的信號量,將數據解碼並顯示。
  2. UDP 線程:接收包,收集各 msg_id 對應的全部片段,組合完整後內容放入共享內存,信號量通知主線程。
  3. TCP 線程:定時發送心跳包。

連接圖傳的 Wi-Fi,運行軟件。圖傳連接 RX0 小相機,用相機拍攝手機秒表,進行粗略的時延測試,左側顯示畫面經過了手機屏幕顯示→ RX0 拍攝屏幕並從 HDMI 輸出→ HDMI 輸入圖傳→ 電腦無線接收並從顯示器顯示。端到端時延基本在 200ms 左右。

3N5A3075

接收方案 2 - 開發板#

有了在電腦上運行的程序,那麼把它搬上嵌入式硬件也有了希望。我曾經屯了一塊全志 D1 的芒果派 MQ-Pro D1,有 HDMI 輸出,有 H.264 硬件解碼器,還開放了完整的 SDK 和文檔,能夠滿足製作接收端的大部分需求。美中不足的是 Wi-Fi 網卡只支持 2.4GHz,更換支持 5GHz 頻段的 RTL8821CS 網卡並編譯配套驅動後,才能連接影眸的熱點。

image

全志為 D1 提供了 Tina Linux SDK。Tina 的亮點是基於 Linux 內核 + OpenWRT 構建系統,可以使智能音箱為首的 AIoT 產品更加輕量,畢竟 OpenWRT 更廣為人知的用途是內存和存儲同樣非常有限的路由器。宣傳中稱原來需要 1GB DDR + 8GB eMMC 才能支撐的系統,使用 Tina Linux 系統只需要 64MB DDR + 128MB NAND Flash 即可。

D1 芯片有 H.264 的硬件解碼器,而 Tina 系統支持了 libcedar 的 OpenMAX 接口,使得 GStreamer 可以用 omxh264dec 插件調用 libcedar 進行視頻硬解碼,再加上 Tina 提供了 sunxifbsink 插件,可以調用 DE 實現 YV12 → RGB。因此利用 GStreamer 進行解碼和顯示成了最佳選擇。根據這篇文章配置好 SDK,排除各種編譯問題後,得到了具備上述插件的 GStreamer,隨後可以進行應用開發。

image

雖然在做方案一時沒有想到方案二而採用了 FFmpeg,但是 TCP 控制指令和 UDP 數據獲取部分都可以復用。使用 GStreamer 的核心在於由元素(element)依次構成一條管道(pipeline)。為了將從 UDP 獲取的幀數據送入管道,可以使用 GStreamer 的 appsrcappsrc 提供了將數據送入 GStreamer 管道的 API。appsrc 有兩種模式:push 模式和 pull 模式。在 pull 模式下,appsrc 會在需要數據時,通過指定接口從應用程序中獲取相應數據。在 push 模式下,則由應用程序主動將數據推送到管道中。若採用 push 的方式,我們就可以在 UDP 接收線程裡,主動地把數據 “發送” 到 appsrc 裡。因此我們通過以下流程創建一個管道:

  1. 創建元素

    appsrc = gst_element_factory_make("appsrc", "source");
    parse = gst_element_factory_make("h264parse", "parse");
    decoder = gst_element_factory_make("omxh264dec", "decoder");
    sink = gst_element_factory_make("sunxifbsink", "videosink");
    

    每個元素通過 g_object_set() 設置屬性,其中 caps 定義了數據流的格式和屬性,以便元素正確處理,以及元素之間的協商。在這個應用中,appsrccaps 是最重要的,否則後續元素不知道收到的內容是什麼格式。appsrccaps 配置如下:

    GstCaps *caps = gst_caps_new_simple("video/x-h264",
                                        "width", G_TYPE_INT, 1920,
                                        "height", G_TYPE_INT, 1080,
                                        "framerate", GST_TYPE_FRACTION, 30, 1,
                                        "alignment", G_TYPE_STRING, "nal",
                                        "stream-format", G_TYPE_STRING, "byte-stream",
                                        NULL);
    g_object_set(appsrc, "caps", caps, NULL);
    
  2. 創建管道並添加和鏈接元素

    pipeline = gst_pipeline_new("test-pipeline");
    gst_bin_add_many(GST_BIN(pipeline), appsrc, parse, decoder, sink, NULL);
    gst_element_link_many(appsrc, parse, decoder, sink, NULL)
    

    這樣就形成了一條 appsrc→h264parse→omxh264dec→sunxifbsink 的管道。

在 UDP 線程中,我們還是循環接收,收集各 msg_id 對應的全部片段,組合完整後內容放入 gst_buffer,並通過 g_signal_emit_by_name(appsrc, "push-buffer", gst_buffer, &ret) 將緩衝區 gst_buffer 推送到 appsrc 中。在 gst_buffer 中,除了幀數據本身,dtsptsduration 是需要傳遞的重要時間參數。將 appsrcdo-timestamp 屬性設置為 TRUE 後,appsrc 會在接收到緩衝區時自動為其設置時間戳,但是對於 duration(持續時間)必須根據幀率設置。如果不設置,實測發現會有難以描述的 “卡頓感”,究其原因可能是缺少 duration 設置導致播放速度不穩定。雖然可能引入額外的時延,但是為保證觀感,還是設置為妙。

為了將完成的代碼編譯,可以編寫一個 Makefile,使得我們的代碼作為 OpenWRT 的一個軟件包,在構建 rootfs 時一起被編譯進去。

image 8

在板端運行軟件,圖傳連接 RX0 小相機拍攝屏幕秒表進行粗略的時延測試,具體流程為左側屏幕顯示→ RX0 拍攝屏幕並從 HDMI 輸出→ HDMI 輸入圖傳→ 開發板無線接收並從 HDMI 輸出→ HDMI 輸入右側顯示器顯示。端到端時延基本在 200-300ms 之間,並不低。好的方面是透過圖傳畫面觀看屏幕上播放的視頻,觀感還算流暢。

951A1816

測試相機直接連接顯示器,流程為左側屏幕顯示→ RX0 拍攝屏幕並從 HDMI 輸出→ HDMI 輸入右側顯示器顯示,時延基本在 70ms 左右,因此圖傳本身時延基本在 130ms-230ms 之間。

3N5A3029

借助這個接收端,可以實現通過 HDMI 連接大大小小的監視器,使影眸不局限於使用手機、平板監看。

接收方案 3 - OBS Studio 插件#

前兩種接收方案使得使用影眸時可以通過電腦以及 HDMI 顯示設備監看,但仍不能滿足低延遲直播推流的需求。如果在方案一的基礎上稍加改動接收程序,通過 localhost 的 UDP 將 H.264 碼流發送給 OBS Studio,則會發現啟用緩衝時流暢但延遲大,而不啟用緩衝時延遲低但常有監看時沒有的卡頓;方案二雖然可以連接採集卡採集 HDMI 輸出,但會增加開發板上解碼、輸出和採集卡的延遲。要減少延時,直接開發 OBS 插件幾乎是最好選擇。

OBS Studio 支持通過插件擴展功能Plugins — OBS Studio 30.0.0 documentation (obsproject.com)。根據介紹,開發一個 Source 類型的插件便可將視頻源接入 OBS。OBS Studio 的 Source 類插件開發中有同步視頻源(Synchronous Video Source)和異步視頻源(Asynchronous Video Source)。同步視頻源如 Image Source 與 OBS 的渲染循環同步,由 OBS 主動調用視頻源的渲染函數獲取幀數據,適合圖形繪製或特效處理;異步視頻源可以運行在獨立的工作線程中,與 OBS 的渲染循環異步,視頻源主動推送幀數據到 OBS。對於網絡流、攝像頭輸入,異步更加合適。

根據提供的插件模板 obs-plugintemplate 建立工程、準備環境,並參考 OBS 現有的 image_source 插件源碼完成邏輯。要做的改動非常少,大部分代碼均可以復用方案二的代碼,不同之處在於解碼得到的幀內容不能直接送給一個顯示元素,需要通過 appsink 獲取解碼後的內容並調用 obs_source_output_video() 交給 OBS Studio。

編譯成功後,將 build 目錄下的 .so 文件複製到 OBS Studio 的插件目錄(如 /usr/local/lib/obs-plugins/),啟動 OBS Studio 即可開始測試。同樣進行時延測試,具體流程為左側屏幕顯示→ RX0 拍攝屏幕並從 HDMI 輸出→ HDMI 輸入圖傳→ 電腦 OBS 插件無線接收並顯示。端到端時延基本在 200ms 左右,透過圖傳畫面觀看屏幕上播放的視頻,觀感也連貫流暢。

IMG_20241003_224607-Enhanced-NR-1_1

obs-studio 插件時延測試:左側屏幕顯示→ RX0 拍攝屏幕並從 HDMI 輸出→ HDMI 輸入圖傳→ 電腦 OBS 插件無線接收並顯示

同時連接三種方案,可以感知到卡頓的增加,但是都仍保持了尚可接收的時延。

3N5A3116

總結#

某種程度上來說,在強大的編解碼器和成熟的無線技術的加持下,實現實時視頻傳輸並不那麼困難,軟件邏輯可以非常簡單直接。而海思帶硬件編碼器的芯片大量用於監控、5GHz Wi-Fi 的普及,又幾乎無意中使得 Wi-Fi 無線圖傳這類產品能夠以極低成本實現體驗不錯的實時視頻傳輸。配合蓬勃發展的大屏設備使用,門檻進一步降低。可惜的是它們的上限相當受限:享受了 Wi-Fi 的生態,便要忍受 Wi-Fi 的擁擠;享受了成熟的硬件編解碼器,也基本失去了改動的自由度。當然,這並不妨礙影眸本身是一個相當 “功能完備” 的產品,它能很好地完成既定任務,沒有嚴重的短板,儘管有大量新產品問世,但依然能夠有效地滿足基本的圖傳需求。致迅曾在一場直播中介紹影眸的製造歷程,他們能夠早早抓準用戶痛點、用心做出一款完成度頗高的產品,仍然令人佩服。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。