CentOS8 搭建海思 Hi3518E 开发环境

最近入了一块ARM开发板,海思Hi3518E芯片,带摄像头和WIFI模块,后期打算把4G和GPS模块也加上,初步看好EC20,可能需要自己编译驱动,此贴记录一路踩坑的情况……

在这里插入图片描述

环境准备

CentOS8 x64作为主机,采用光盘镜像安装,在进行开发环境搭建前,需要额外安装一些组件

CentOS系统包安装

dnf config-manager --enable PowerTools
sudo yum install grub2-tools glibc.i686 libstdc++.i686 automake make gcc zlib.i686 glibc-static xz xz-devel

交叉编译工具安装

将 Hi3518E_SDK_V1.0.4.0.tgz 复制到 home目录下解压,进入解压目录执行

[myctime@localhost Hi3518E_SDK_V1.0.4.0]$ ./sdk.unpack 
Unpacking SDK
WARN: Be sure you have installed the cross-compiler. if not, install it first!
WARN: ALL THE SOUCE FILES WILL BE OVERWRITED, FILES YOU MOTIFIED WILL BE LOST !!!

unpacking osdrv
run_command_progress_float: 'tar -xvzf package/osdrv.tgz'
[100%]##################################################|
unpacking kernel
run_command_progress_float: 'tar -xvzf osdrv/opensource/kernel/linux-3.4.y.tgz -C osdrv/opensource/kernel/'
[100%]##################################################|
unpacking mpp
mkdir: 已创建目录 'mpp'
run_command_progress_float: 'tar -xvzf package/mpp.tgz'
[100%]##################################################|
unpacking drv
mkdir: 已创建目录 'drv'
run_command_progress_float: 'tar -xvzf package/drv.tgz'
[100%]##################################################|

会在当前目录下生成drv,mpp,osdrv几个目录
交叉编译工具就存放在osdrv目录下,分为arm-hisiv300-linux和arm-hisiv400-linux两个版本,分别是基于uclib和glibc的,我这块板子FLASH太小了,只能使用uclib。现在运行脚本安装编译工具:

[myctime@localhost arm-hisiv300-linux]$ pwd
/home/myctime/Hi3518E_SDK_V1.0.4.0/osdrv/opensource/toolchain/arm-hisiv300-linux
[myctime@localhost arm-hisiv300-linux]$ ./cross.install.v300 
CROSS_COMPILER_PATH=/opt/hisi-linux/x86-arm/arm-hisiv300-linux/target/bin
Do not have version file
mkdir: 无法创建目录 “/opt/hisi-linux”: 权限不够
sorry, you must have super privilege!
1) I have root passwd	  3) Try again		    5) Aboart
2) I have sudo privilege  4) Ignore
#? 1
密码:
mkdir: 已创建目录 '/opt/hisi-linux'
mkdir: 已创建目录 '/opt/hisi-linux/x86-arm'
mkdir: 已创建目录 '/opt/hisi-linux/x86-arm/arm-hisiv300-linux'
Extract cross tools ...
'/opt/hisi-linux/x86-arm/arm-hisiv300-linux/target/bin/arm-hisiv300-linux-addr2line' -> '/opt/hisi-linux/x86-arm/arm-hisiv300-linux/bin/arm-hisiv300-linux-uclibcgnueabi-addr2line'

工具安装在了 /opt/hisi-linux/x86-arm/arm-hisiv300-linux/target/bin/ 目录下,我们需要把这个路径添加到PATH环境目录下,同时还得添加一下目录的访问权限

[myctime@localhost arm-hisiv300-linux]$ export PATH=/opt/hisi-linux/x86-arm/arm-hisiv300-linux/target/bin/:$PATH

如果之前的系统包安装全了的话,编译工具就可以使用了

[myctime@localhost bin]$ arm-hisiv300-linux-gcc --version
arm-hisiv300-linux-gcc (Hisilicon_v300) 4.8.3 20131202 (prerelease)
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

uboot,内核及相关工具编译

参照解压出来osdrv目录下的readme文件,可以通过一下命令对整个SDK,包含内核,rootfs以及各类工具进行编译。

CentOS8缺少mkimage这个命令,这个会在编译SDK的时候用到。幸好SDK里带了源码,我们可以自己编译安装

[root@localhost mkimage_tool]# pwd
/home/myctime/Hi3518E_SDK_V1.0.4.0/osdrv/tools/pc/mkimage_tool
[root@localhost mkimage_tool]# make
[root@localhost mkimage_tool]# cp mkimage /usr/bin/

开始编译整个SDK

make OSDRV_CROSS=arm-hisiv300-linux CHIP=hi3518ev200 all

编译到pc工具时会出很多错,大体原因还是因为sdk中源代码还是基于32位系统写的,使用的glibc版本也比较低。比如以下错误,major和minor这两个用于获取主次设备号的宏在比较新的glibc版本中已经迁移到sys/sysmacros.h头文件中,据说是为了与C++标准兼容……

/opt/his/Hi3518E_SDK_V1.0.4.0/osdrv/tools/pc/jffs2_tool/tmp/mtd-utils-1.5.0/lib/libmtd.c:444:对‘major’未定义的引用
/opt/his/Hi3518E_SDK_V1.0.4.0/osdrv/tools/pc/jffs2_tool/tmp/mtd-utils-1.5.0/lib/libmtd.c:445:对‘minor’未定义的引用

换成32位系统可能就没有这么多事了,但用CentOS8 x64作为开发环境是我自己选的,标题也是这么写的,自己挖的坑硬着头皮也要走完,此处一系列问题解决办法记录如下:

mtd-utils 编译出错

  • lib/libmtd.c,libmtd_legacy.c,mkfs.jffs2.c,jffs2reader.c,mkfs.ubifs/devtable.c,ubi-utils/libubi.c,mkfs.ubifs/mkfs.ubifs.c 这些文件头部加入头文件sys/sysmacros.h
  • mkfs.jffs2.c 会用到libacl-devel包里的头文件,需要提前 yum install libacl-devel
  • compr_zlib.c 会用到zlib-devel包里的头文件 compr_lzo.c 会用到lzo-devel, 需要提前 yum install lzo-devel
  • serve_image.c 把头部的#define _POSIX_C_SOURCE 199309给注释咯
  • mkfs.ubifs/hashtable/hashtable_itr.h 里居然还有一个重定义错误,把hashtable_iterator_key和hashtable_iterator_value这俩函数的实现注释咯
  • mkfs.ubifs/mkfs.ubifs.h 加一个宏定义 #define off64_t off_t,我们pc系统是64位的
  • mkfs.ubifs/mkfs.ubifs.c 1280行处把O_LARGEFILE标志位去了,理由同上

misc-utils编译出错

  • lslocks.c 头部引入 #include <sys/sysmacros.h>

squashfs4.2

mksquashfs.c unsquashfs.c头部引入 #include <sys/sysmacros.h>

开发包全部编译通过后终端上会有finished等字样

使用HiTool烧录uboot,kernel和rootfs

在这里插入图片描述
买来时,板子上已经有系统了,但我还是想试着自己从头开始烧一个完整的系统到板子上
看文档,编译出来的uboot不能直接烧录,先要按下面方式操作一下,似乎是因为uboot引导时需要用到芯片管脚配置之类的数据

cd /opt/his/Hi3518E_SDK_V1.0.4.0/osdrv/tools/pc/uboot_tools
cp /opt/his/Hi3518E_SDK_V1.0.4.0/osdrv/pub/image_uclibc/u-boot-hi3518ev200.bin ./u-boot.bin
./mkboot.sh reg_info_hi3518ev200.bin u-boot-ok.bin

这样就生成了一个新的u-boot-ok.bin,用这个文件来烧录。
这个板子只有一个spi flash,16M,真的是很服气啊。
HiTools这个工具挺方便,在HiBurn视图下,如下图配置好,点击烧写,然后给板子上电,大概40秒uboot就烧好了。
在这里插入图片描述
打开终端,会发现我们已经在uboot界面了
在这里插入图片描述
接下来烧写linux内核,自从可以加载uboot以后我们可以采用网络的方式烧写数据了,先在HiBurn视图里配置一下板端的IP和主机IP
在这里插入图片描述
查看手册,可以了解到16M SPI Flash推荐的内存分布应该是这样

1M3M12M
bootkernelrootfs

我们按照这个地址分布,在分区表里加上内核和rootfs镜像
在这里插入图片描述
点击烧写,重新给板子上电,等待一会儿就烧录好了
连接终端,重启板子会发现,内核还是加载失败,我们需要给uboot设置一下启动参数,在uboot界面下输入

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

这下板子就可以正常启动了。

/ # df -h
Filesystem                Size      Used Available Use% Mounted on
/dev/root                12.0M      5.2M      6.8M  43% /
tmpfs                    29.1M      4.0K     29.1M   0% /dev

可怜的存储空间,还是插块SD卡用吧,先挂载到/mnt上

/mnt # fdisk -l
……
Disk /dev/mmcblk0: 15.9 GB, 15931539456 bytes
255 heads, 63 sectors/track, 1936 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

        Device Boot      Start         End      Blocks  Id System
/dev/mmcblk0p1               1        1937    15556608   c Win95 FAT32 (LBA)
/mnt # mount /dev/mmcblk0p1 /mnt

然后在主机上配置好nfs服务

CentOS8上搭建 NFS并在板端挂载

dnf install nfs-utils
systemctl start nfs-server.service

编辑/etc/exports文件内容如下,我的板端IP为192.168.2.244

/opt	192.168.2.244/24(rw,sync)

重载nfs配置

exportfs  -arv

添加防火墙规则

firewall-cmd --permanent --add-service=nfs
firewall-cmd --permanent --add-service=rpc-bind
firewall-cmd --permanent --add-service=mountd
firewall-cmd --reload

我的CentOS系统IP为192.168.2.131
在板端运行

mount -t nfs -o nolock -o tcp -o rsize=32768,wsize=32768 192.168.2.131:/opt  /nfsroot

将主机的opt目录挂载到了本地的nfsroot

好了,目前系统已经能够成功启动起来,但是外设除了一个网卡基本没有。

驱动板载无线网卡

这块开发板上板载了一块MT7601无线网卡,想办法把这块无线网卡驱动起来,搜了一下驱动,源码在这里

驱动源码的编译

先复制驱动源码到/opt/mt7601u目录下,修改Makefile中的KDIR,以及添加ARCH和CROSS_COMPILE

KDIR = /opt/his/Hi3518E_SDK_V1.0.4.0/osdrv/opensource/kernel/linux-3.4.y

default:
        $(MAKE) ARCH=arm CROSS_COMPILE=arm-hisiv300-linux- -C $(KDIR) M=$$PWD
clean:
        $(MAKE) -C $(KDIR) M=$$PWD clean
install:
        $(MAKE) -C $(KDIR) M=$$PWD modules_install

make出错

make ARCH=arm CROSS_COMPILE=arm-hisiv300-linux- -C /opt/his/Hi3518E_SDK_V1.0.4.0/osdrv/opensource/kernel/linux-3.4.y M=$PWD
make[1]: 进入目录“/opt/his/Hi3518E_SDK_V1.0.4.0/osdrv/opensource/kernel/linux-3.4.y”
  CC [M]  /opt/mt7601u/usb.o
In file included from /opt/mt7601u/usb.c:18:0:
/opt/mt7601u/mt7601u.h:18:28: error: expected identifier before ‘(’ token
 #define RX_FLAG_SHORTPRE   (1<<8 )
                            ^
include/net/mac80211.h:676:2: note: in expansion of macro ‘RX_FLAG_SHORTPRE’
  RX_FLAG_SHORTPRE = 1<<8,
  ^
In file included from /opt/mt7601u/usb.c:18:0:
/opt/mt7601u/mt7601u.h:173:27: error: field ‘chandef’ has incomplete type
  struct cfg80211_chan_def chandef;
                           ^
/opt/mt7601u/mt7601u.h:280:14: error: ‘IEEE80211_NUM_TIDS’ undeclared here (not in a function)
  u16 agg_ssn[IEEE80211_NUM_TIDS];

不难发现cfg80211_chan_def 这个结构是在比较高版本的内核才引入的,而且驱动的readme也说了,必须在3.19以上版本的内核下编译。

我对无线网卡驱动开发没有兴趣,就算有源码我也没有动力backporting驱动到3.4的内核上,好在开发板厂商提供了二进制的驱动文件。就勉强直接用上吧。

无线网卡AP模式

安装系统库后,检查一下内核有没有开启无线网卡支持,

[myctime@localhost linux-3.4.y]$ sudo yum install ncurses-devel
[myctime@localhost linux-3.4.y]$ pwd
/home/myctime/Hi3518E_SDK_V1.0.4.0/osdrv/opensource/kernel/linux-3.4.y
[myctime@localhost linux-3.4.y]$ make menuconfig

在这里插入图片描述
很好,就不用重新编译内核了。

mt7601支持STA和AP模式,根据需要执行对应的脚本。
我们使用AP模式,先要对dhcp进行配置

#/etc/udhcpd.conf
start 192.168.10.10
end 192.168.10.100
interface ra0
option subnet 255.255.255.0
option dns 192.168.10.1
option router 192.168.10.1
lease_file /var/udhcpd.leases

再对AP进行配置
/etc/Wireless/RT2870AP/RT2870AP.dat

EC20 4G/GPS 驱动移植

淘宝下单了一块EC20,用来4G通信和获取GPS数据
在这里插入图片描述
神奇的是EC20上电之后,系统居然直接识别成了EC25,EC20有很多版本,我买的这个是EC20R2.1,他的VID和PID就是和EC25一样,官方文档没有提这个梗。系统识别出来了但设备工作不正常,最后还是得自己来修改内核。

/ # lsusb
Bus 001 Device 002: ID 1a40:0801
Bus 001 Device 001: ID 1d6b:0002
Bus 002 Device 001: ID 1d6b:0001
Bus 001 Device 003: ID 148f:7601
Bus 001 Device 028: ID 2c7c:0125   <----- EC25

内核Patch

我自己根据手册对3.4.y内核做了一个patch,在menuconfig中把下面的配置项编译进内核
Device Drivers -> Network device support -> USB Network Adapters -> Multi-purpose USB Networking Framework -> QMI WWAN driver for Qualcomm MSM based 3G and LTE modems

make ARCH=arm CROSS_COMPILE=arm-hisiv300-linux- uImage -j 20

编译完成后烧录,重新上电后,板子已经能够识别EC20了,并且多出了一个wwan0网卡和一个/dev/cdc-wdm0设备

网络连接控制程序

将WCDMA&LTE_QConnectManager_Linux&Android_V1.1.34.zip复制到CentOS系统,解压后编译得到控制程序 quectel-CM

[myctime@localhost quectel-CM]$ make ARCH=arm CROSS_COMPILE=arm-hisiv300-linux-
rm -rf quectel-CM *~
arm-hisiv300-linux-gcc -Wall -s QmiWwanCM.c GobiNetCM.c main.c MPQMUX.c QMIThread.c util.c udhcpc.c -o quectel-CM -lpthread -ldl

通过nfs将quectel-CM复制到板上,另外quectel-CM联网成功后会使用udhcpd配置网卡地址,所以还需要将busybox里的udhcp脚本复制到板上。

/ # cp /nfsroot/quectel-CM/quectel-CM /
/ # cp /nfsroot/Hi3518E_SDK_V1.0.4.0/osdrv/opensource/busybox/busybox-1.20.2/examples/udhcp/simple.script  /usr/share/udhcpc/default.script

最后启动quectel-CM

/ # /quectel-CM &
[01-01_02:15:20:845] WCDMA&LTE_QConnectManager_Linux&Android_V1.1.34
[01-01_02:15:20:846] /quectel-CM profile[1] = (null)/(null)/(null)/0, pincode = (null)
[01-01_02:15:20:851] Find /sys/bus/usb/devices/1-1.3 idVendor=2c7c idProduct=0125
……
[01-01_02:15:21:223] requestGetSIMStatus SIMStatus: SIM_READY
[01-01_02:15:21:255] requestGetProfile[1] cmnet///0
[01-01_02:15:21:287] requestRegistrationState2 MCC: 460, MNC: 0, PS: Attached, DataCap: LTE
[01-01_02:15:21:318] requestQueryDataCall IPv4ConnectionStatus: DISCONNECTED
[01-01_02:15:21:383] requestRegistrationState2 MCC: 460, MNC: 0, PS: Attached, DataCap: LTE
……

这时我们可以看到wwan网卡已经启用,并分配好了IP地址,默认路由也配置好了

/ # ifconfig wwan0
wwan0     Link encap:Ethernet  HWaddr A6:96:F8:8C:70:B2  
          inet addr:10.26.82.215  Bcast:10.26.82.223  Mask:255.255.255.240
……
/ # route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         10.29.12.129    0.0.0.0         UG    0      0        0 wwan0
10.29.12.128    *               255.255.255.252 U     0      0        0 wwan0
……

Ping一下看看

/ # ping www.sina.com.cn
PING www.sina.com.cn (112.25.53.229): 56 data bytes
64 bytes from 112.25.53.229: seq=0 ttl=56 time=288.847 ms
64 bytes from 112.25.53.229: seq=1 ttl=56 time=217.916 ms
64 bytes from 112.25.53.229: seq=2 ttl=56 time=232.323 ms
64 bytes from 112.25.53.229: seq=3 ttl=56 time=102.792 ms

延时有点高,可能我没装天线的缘故,不管如何,到这里,4G终于通了!

一个小坑

不得不说这里有个坑了我好久的问题,之前启动quectel-CM的时候一直提示没有检测到SIM卡,也就是有一行的提示是:

[01-01_02:15:21:223] requestGetSIMStatus SIMStatus: SIM_ABSENT

我简单分析了一下:
SIM卡损坏:SIM卡是好的,没插反,换了移动电信两张卡都是SIM_ABSENT
驱动没改好:按官方手册来的,不应该
电路不兼容:考虑到开发板厂家给的默认方案是用的合宇Air720HI的4G芯片,我因为要使用GPS的缘故换成了EC20,并且还仔细查看了EC20和Air720HI的管脚定义,发现是完全一致的。
最后觉得是SIM卡座损坏的可能性比较大,还寄回原厂检查了。检查结果SIM卡座是好的,板子在人家那里可以顺利联网……
板子寄回来后阴差阳错的被我插在了USB3接口上,发现LED灯的状态和以前不一样了,这才反应过来——原来是USB供电不足啊……

MQTT客户端移植

客户端这边我选择的是Mosquitto。官网拿到源码,我这里用的版本是1.6.9,修改CMakeList.txt,在文件头部加入下面两行

set(CMAKE_CXX_COMPILER arm-hisiv300-linux-g++)
set(CMAKE_C_COMPILER   arm-hisiv300-linux-gcc)

因为我们只需要客户端,调整一下编译选项,取消一些不需要的组件

option(WITH_SOCKS "Include SOCKS5 support?" OFF)
option(WITH_TLS
        "Include SSL/TLS support?" OFF)
option(WITH_TLS_PSK
        "Include TLS-PSK support (requires WITH_TLS)?" OFF)
option(WITH_EC
        "Include Elliptic Curve support (requires WITH_TLS)?" OFF)
option(WITH_THREADING "Include client library threading support?" OFF)

开始编译

[myctime@localhost mosquitto-1.6.9]$ mkdir build
[myctime@localhost mosquitto-1.6.9]$ cd build/
[myctime@localhost build]$ cmake ../
[myctime@localhost build]$ make

编译出错,提示没有找到utlist.h头文件,看来mosquitto用了uthash这个库

[ 14%] Building C object lib/CMakeFiles/libmosquitto.dir/messages_mosq.c.o
/home/myctime/mosquitto-1.6.9/lib/messages_mosq.c:22:20: fatal error: utlist.h: 没有那个文件或目录
 #include <utlist.h>

要先移植uthash了

缺少uthash

好在uthash整个工程都只是头文件,不需要编译,我们先找个地方把uthash克隆下来

[myctime@localhost ~]$ git clone https://github.com/troydhanson/uthash.git

然后把头文件复制到之前交叉编译工具的安装目录

[myctime@localhost ~]$ cp uthash/src/utlist.h opt/hisi-linux/x86-arm/arm-hisiv300-linux/include

在CMakeList.txt中加入引用目录

set(CMAKE_C_FLAGS -I/opt/hisi-linux/x86-arm/arm-hisiv300-linux/include)

还要在mosquitto/src/CMakeList.txt中关掉这个选项,uclib不支持。

option(INC_MEMTRACK
        "Include memory tracking support?" OFF)

最后清空build目录,重新编译一次,client目录下就生成了我们需要的mosquitto_pub。

[myctime@localhost build]$ ls client/ 
CMakeFiles  cmake_install.cmake  Makefile  mosquitto_pub  mosquitto_rr  mosquitto_sub

其实,在src目录下,mosquitto的服务端也正确生成了。

[myctime@localhost build]$ ls src/
CMakeFiles  cmake_install.cmake  Makefile  mosquitto

基于live555的RTSP流媒体服务器搭建

开发板厂商附赠了一个RTSP服务端的实现,但不知为何用VLC播放到半路的时候总是卡住,可能是跟MPP部分的实现有关,也可能是RTSP服务端程序自身的问题,不管如何,最终打算还是自己重新来做一个,权当学习了。
媒体服务器采用live555的开源实现,首先将他移植到我们的开发板上。

live555的官网在这里

看看介绍,我这里应该主要使用的是里面的mediaServer模块。

先把源码下下来复制到交叉编译环境里。直接编译会出现各种错误,要修改一下config.armeb-uclibc这个文件,主要是注意以下几点

  • -DNO_OPENSSL 不使用OPENSSL,因为我们没有移植openssl,不然会出现找不到openssl.h的错误
  • -DLOCALE_NOT_USED 不启用locale,不然会出现 undefined reference to `uselocale’
  • LINK要设置为C++,不然链接阶段会出现各种符号错误
  • 链接库列表里去掉-lssl -lcrypto
  • 去掉-DBSD=1
CROSS_COMPILE=         arm-hisiv300-linux-
COMPILE_OPTS =          $(INCLUDES) -I/usr/local/include -I. -Os -DSOCKLEN_T=socklen_t -DNO_OPENSSL -DLOCALE_NOT_USED
#COMPILE_OPTS =          $(INCLUDES) -I/usr/local/include -I. -Os -DSOCKLEN_T=socklen_t -DNO_SSTREAM=1 -D
LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64
C =                    c
C_COMPILER =           $(CROSS_COMPILE)gcc
C_FLAGS =              $(COMPILE_OPTS)
CPP =                  cpp
CPLUSPLUS_COMPILER =   $(CROSS_COMPILE)g++
CPLUSPLUS_FLAGS =      $(COMPILE_OPTS) -Wall
#CPLUSPLUS_FLAGS =      $(COMPILE_OPTS) -Wall -DBSD=1
OBJ =                  o
LINK =                 $(CROSS_COMPILE)g++ -o
LINK_OPTS =            -L.
CONSOLE_LINK_OPTS =    $(LINK_OPTS)
LIBRARY_LINK =         $(CROSS_COMPILE)ar cr
LIBRARY_LINK_OPTS =
LIB_SUFFIX =                   a
LIBS_FOR_CONSOLE_APPLICATION =
#LIBS_FOR_CONSOLE_APPLICATION = -lssl -lcrypto
LIBS_FOR_GUI_APPLICATION =
EXE =

之后make,没什么意外的话live555MediaServer就编译好了。

[myctime@localhost live]$ ./genMakefiles armeb-uclibc
[myctime@localhost live]$ make
……
[myctime@localhost live]$ ls mediaServer/live555MediaServer -l
-rwxrwxr-x. 1 myctime myctime 701830 5月  10 21:15 mediaServer/live555MediaServer
[myctime@localhost live]$ 

可以在板端试着运行一下:

/nfsroot/live/mediaServer # ./live555MediaServer
LIVE555 Media Server
        version 1.00 (LIVE555 Streaming Media library version 2020.04.24).
Play streams from this server using the URL
        rtsp://192.168.2.244:8554/<filename>
where <filename> is a file present in the current directory.
Each file's type is inferred from its name suffix:
        ".264" => a H.264 Video Elementary Stream file
        ".265" => a H.265 Video Elementary Stream file
        ".aac" => an AAC Audio (ADTS format) file
        ".ac3" => an AC-3 Audio file
        ".amr" => an AMR Audio file
        ".dv" => a DV Video file
        ".m4e" => a MPEG-4 Video Elementary Stream file
        ".mkv" => a Matroska audio+video+(optional)subtitles file
        ".mp3" => a MPEG-1 or 2 Audio file
        ".mpg" => a MPEG-1 or 2 Program Stream (audio+video) file
        ".ogg" or ".ogv" or ".opus" => an Ogg audio and/or video file
        ".ts" => a MPEG Transport Stream file
                (a ".tsx" index file - if present - provides server 'trick play' support)
        ".vob" => a VOB (MPEG-2 video with AC-3 audio) file
        ".wav" => a WAV Audio file
        ".webm" => a WebM audio(Vorbis)+video(VP8) file
See http://www.live555.com/mediaServer/ for additional documentation.
(We use port 80 for optional RTSP-over-HTTP tunneling, or for HTTP live streaming (for indexed Transport Stream files only).)

作为一个RTSP服务端,live555MediaServer读取的是本地音视频文件,下一步我们可以试着将live555与海思MPP进行集成,进行实时推流了,研究思路我估摸着大概应该是这样:

  • 对RTSP协议进行一些了解
  • 熟悉live555的实现,特别是MediaSource
  • 熟悉海思MPP从摄像头采集数据并进行H264编码
  • 将二者集成

live555的改造思路

live555提供了一个DynamicRTSPServer基本满足了我们搭建RTSP服务器的需求,但这个服务器的实现是从本地文件进行推流的,而我们要从摄像头获取流媒体数据,所以,我决定基于此进行研究改造。

先学习官方文档,文档简陋但很实用,类的层次和关系一目了然。

大致研究了一下,不难发现,RTSP客户端向服务端发起SETUP请求的时候,这个DynamicRTSPServer会根据请求URL中的streamName在fServerMediaSessions(用来存储媒体源的hashtable)里查找有没有这个媒体,如果本地有同名的媒体文件,但hashtable里没有,那就说明是这个文件第一次被客户端请求播放,随后新建一个关联到本地媒体文件的ServerMediaSession。

于是我不仅大胆猜测,如果我们可以在服务端预设一个streamName,当客户端请求这个streamName时,让DynamicRTSPServer不是从本地读取多媒体文件,而是返回一个关联到海思MPP的ServerMediaSession,是不是就可以实现推流的功能了?同时还能保留播放本地多媒体文件的功能。

我们暂定这个streamName为test.h264好了

ServerMediaSession 其实是下图左1这么一个玩意儿,内部还存储了一个ServerMediaSubsession的链表,真正跟数据流处理相关的是这个ServerMediaSubsession。ServerMediaSubsession基本上是一个接口类,我们不难找到,DynamicRTSPServer里跟H264数据源处理有关的是它的一个具体实现——H264VideoFileServerMediaSubsession,下图右1。

在这里插入图片描述
从继承关系上来看,如果我们要实现海思MPP数据源的接入,可以从继承OnDemandServerMediaSubsession这个类作为入口,同时借鉴H264VideoFileServerMediaSubsession的实现。OnDemandServerMediaSubsession封装了大部分多媒体数据流的处理逻辑,并以接口的方式使下层可以对其逻辑进行扩展,比如它提供了以下两个接口——当客户端请求SDP数据时,OnDemandServerMediaSubsession会通过使用下面两个接口来获取媒体数据:

  virtual FramedSource* createNewStreamSource(unsigned clientSessionId,
					      unsigned& estBitrate) = 0;
  virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock,
				    unsigned char rtpPayloadTypeIfDynamic,
				    FramedSource* inputSource) = 0;

FramedSource指的是多媒体数据源,而RTPSink是RTP协议传输的实现。

在H264VideoFileServerMediaSubsession的实现里,createNewStreamSource接口返回的是H264VideoStreamFramer, createNewRTPSink接口返回的是H264VideoRTPSink。因为我们不会去修改RTP传输,所以H264VideoRTPSink应该是不用修改的。接下来需要研究的就是这个H264VideoStreamFramer。
在这里插入图片描述
从关系图中不难看出,H264VideoStreamFramer继承自FramedFilter,并且还有一个成员fInputSource指向了另一个FramedSource对象,结合H264VideoFileServerMediaSubsession里createNewStreamSource方法的实现代码,可以看出这是一个代理模式——H264VideoStreamFramer(将H264视频流解析分解为NAL单元)是通过H264or5VideoStreamParser(H264视频流解析器)对ByteStreamFileSource输出的数据进行解析、转换,而ByteStreamFileSource则是负责读取H264文件的实现。

FramedSource* H264VideoFileServerMediaSubsession::createNewStreamSource(unsigned /*clientSessionId*/, unsigned& estBitrate) {
  estBitrate = 500; // kbps, estimate

  // Create the video source:
  ByteStreamFileSource* fileSource = ByteStreamFileSource::createNew(envir(), fFileName);
  if (fileSource == NULL) return NULL;
  fFileSize = fileSource->fileSize();

  // Create a framer for the Video Elementary Stream:
  return H264VideoStreamFramer::createNew(envir(), fileSource);
}

查看海思的手册,MPP读取是基于文件描述符的,并且,读出的数据直接就是经过H264编码后的数据,所以我们的工作可以从实现一个自己的MediaSubsession开始,这个Subsession创建一个自定义的H264VideoStreamFramer,重新写一个VideoSource为H264VideoRTPSink提供数据源。文件描述符的事件响应可以利用live555自身提供的Schedule机制。

再看看海思MPP的开发手册

海思媒体处理平台API

在这里插入图片描述
根据文档了解到,我们这个场景对应的数据流向应该是AD->VI->VPSS->VENC->H264码流,海思MPP提供了一系列HI_MPI接口让用户对这些模块进行绑定。从板载摄像头读取H264码流的流程如下:
在这里插入图片描述
因为有Sample程序,这块实现起来并不算困难。

改造后的live555源码和海思MPP我打包放在一起。可以在这里下载

配置好编译环境,根据Sensor的不同对Makefile.param以及MPP初始化部分的代码做一定修改,最后在MPP目录下一个make就全部编译好了。如果跟我一样用的是OV9712,那就什么都不用改了。

live555功能还是很强大的,可以一边直播摄像头一边播放视频文件,缺点就是稍微有些费内存。测试了一下稳定性比厂商提供的好多了,从没出现过卡顿和僵尸的情况。同时播放3路720P视频,板端CPU占用也就10%不到。

在这里插入图片描述
至此,开发板基础功能的调试工作可以先告一段落了

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值