HEXA娱乐开发日志技术点003——下位机成功推流

HEXA开发日志目录
上一篇 HEXA娱乐开发日志技术点002——下位机成功获取弹幕


成果

目前,我手中的HEXA机器人可以用RTMP协议推流flv文件了,即用flv作为视频源做直播,并且此功能已在奥点云和B站试验成功。如果要推实时视频,还需要一些后续工作,这次只说flv文件推流的事。
源码版本812171d9eb56768c18d525b6e3c61b1ae7c315ee
目前的控制端网页长这样,在推流测试的地址中填入rtmp服务器地址后,点击“推”即可启动demo,并开始推流

HEXA娱乐开发日志技术点003——下位机成功推流_第1张图片
控制端网页

这是目前这个skill的目录结构,本次主要添加了一个rtmp.go和deps目录下的一堆依赖文件。

DanmuDriveMe
├── manifest.json
├── remote
│   └── index.html==>控制端网页
├── robot
│   ├── assets
│   │   └── test.flv==>用于测试的flv文件
│   ├── deps
│   │   ├── include==>C文件,包括librtmp库头文件和我对librtmp库的封装
│   │   │   ├── amf.h
│   │   │   ├── log.h
│   │   │   ├── rtmp.h
│   │   │   └── rtmp_sample_api.c==>我对librtmp库的简单封装
│   │   └── lib==>依赖库,包括librtmp所依赖的openssl、zlib库
│   │       ├── libcrypto.so -> libcrypto.so.1.0.0
│   │       ├── libcrypto.so.1.0.0
│   │       ├── librtmp.so -> librtmp.so.1
│   │       ├── librtmp.so.1
│   │       ├── libssl.so -> libssl.so.1.0.0
│   │       ├── libssl.so.1.0.0
│   │       ├── libz.so -> libz.so.1
│   │       ├── libz.so.1 -> libz.so.1.2.11
│   │       └── libz.so.1.2.11
│   └── src==>go代码
│       ├── Danmu.go==>弹幕相关
│       ├── DanmuDriveMe.go
│       └── rtmp.go==>推流相关

从文件角度,目前的依赖或调用关系大概是这样
index.html ==> DanmuDriveMe.go ==> rtmp.go ==> rtmp_sample_api.c ==> librtmp.so ==> libssl.so+libcrypto.so+libz.so

How To

在做之前,先理清一下关于步骤的逻辑。

  1. 要先在电脑上跑通
    推流要使用开源C工程rtmpdump中的librtmp库,在不熟悉它的情况下,最好先在电脑上demo成功,同时在此过程中了解rtmpdump的使用。
  2. 要封装librtmp库
    封装是为了方便给Go程序调用,如果不封装的话,就要在Go程序中使用很多复杂的C数据类型,这会带来很多麻烦,因此要对librtmp库进行简单封装以保持与Go对接的C接口和数据类型都足够简单。
  3. 用Go程序测试这个封装
    这点不必多说,前面的简单封装就是为了方便Go程序调用的。
  4. 本次最终目标——下位机推流
    这里有个大前提,即这是一个嵌入式平台,所以不管搞什么,都要针对平台来做,所有C库,都必须交叉编译才能用。包括librtmp库和它所依赖的库,统统要交叉编译。

前3步的工作体现在我的rtmpdump工程源码的以下文件中

  • demo.c(要看这个文件的前一个版本,此版本已经使用封装的接口了)是第1步的成果,参考的是雷霄骅,张晖的例子源码
  • rtmp_sample_api.c和rtmp_sample_api.h是第2步的工作成果
  • demo.go是第3步的工作成果,使用Go程序调用了第2步的封装接口

在上位机demo rtmpdump

这项内容在网上资料很多,这里只讲一些值的一说的点。

  • 雷霄骅前辈引用的是librtmp/rtmp_sys.h,至少在我使用的rtmpdump版本中,这不是一个public头文件,使用自己编的库还好,要是使用安装到系统的librtmp库,会找不到这个头文件。
  • librtmp提供了两个推流API,它们分别是RTMP_Write和RTMP_SendPacket,雷前辈的demo中都有展示,我选择了RTMP_Write的方式,因为我的参考依据是OBS Studio的方式,参考一个成熟的开源应用会靠谱一点,并且如果出了什么问题相当于有个官方可以参考,可以借助OBS开发者们的智慧,这也是开源软件的魅力。
  • 编译问题要仔细读rtmpdump的README,针对自己平台指定make参数,不要上来就一个make。

上位机封装与Go程序

如果熟悉了librtmp库的使用,封装就很简单,这里就不多说了,只说一些稍微值得一提的点吧。

  • 关于CGO
    据我所知,Go语言调用C至少有两个工具可用,它们分别是CGO和SWIG,SWIG是mind SDK官方例子中OpenCVSkill中的opencv采用的封装方法,具体不太了解。
    我用的是CGO,用法也很简单,和gcc基本编译的那些知识很容易对应上。下面是一个典型的Go代码文件结构,如果经常用gcc的话,应该很容易理解下面注释的意思。
package <包名>
/*
#cgo LDFLAGS: ld参数,比如-L...和-l...之类的,和Makefile常见的写法一样的
#cgo CFLAGS: gcc参数,比如-I...之类的,也合Makefile常见写法一样的
//此处写C代码完全是C语法
void c_fun(){}
*///此处和下一句import "C"之间不可以有空行
import "C"
import(
<其他包>
...
)
func go_func_name() {
    C.c_fun() //调用C函数
}
  • 数据类型转换
    网上有很多对Go和C基本数据类型转换的文章,我不想展开讨论。我这里就做个记录,下面这段[]byte转char *的代码写得让我不舒服,希望将来可以优化。
//buf 是[]byte类型
cc := C.CString(string(buf))
//rtmp_sample_add_data第一个参数要char *类型
C.rtmp_sample_add_data(cc, C.int(datalength)+15)
C.free(unsafe.Pointer(cc))

下位机推流

有了上位机的demo之后,下位机代码就很好写了,所以这个步骤的难点不在于代码,而在于如何交叉编译动态链接库并正确引用他们。

如何建立交叉编译环境?

如果想效仿官方做法,一个方法是读mind SDK的源码,我这么做得到了以下认知:

  • 官方使用了Docker
    这当然是废话,因为一开始就要求开发者安装了Docker,关键是怎么使用。我对它不是很了解,我只知道它可以帮我建立类似虚拟机的环境。当然,在自己平台上建立一个交叉编译环境也不是不可能,只是相当麻烦而已。
    官方的mind SDK的mind x命令实际上就是运行了一个Docker容器(类似虚拟机),并在这个容器环境里执行mind x后面跟的命令。如果可以用mind x搞定很多事,但是也有一些不便之处。总之,经过研究,我误入歧途地得到了构造并进入一个官方Docker容器的命令,即
    docker run -it -v <主机绝对路径>:/go/src/skill vincross/mindcli bash
    上述命令中-v后面跟的分别是主机(电脑)和容器(类似虚拟机)的路径,-v实际上类似于mount命令,这样一来,在容器中,我们可以直接对主机文件进行操作;vincross/mindcli大概是容器的类型,这里指定使用官方容器类型;bash应该是在容器中运行的程序,意思应该是要启动bash shell。
  • 交叉编译器
    我绕了些弯路,终于在mind-sdk/xcompile/Dockerfile中看到了这一句ENV CROSS arm-linux-gnueabihf,虽然我不了解Dockerfile的机制,但是猜得出这句话的意思,然后就好办了,在Docker容器中安装它就行了,安装命令是
    apt-get install gcc-arm-linux-gnueabihf

要交叉编译啥?

当然是rtmpdump啦,先来个make SYS=posix尝尝,立刻发现找不到,搞定openssl之后,再来,会发现找不到。如果先读了README就明白了,rtmpdump依赖openssl和zlib库,所以我得把rtmpdumpopensslzlib都交叉编译成动态库

zlib编译

下载源码,然后执行

CC=arm-linux-gnueabihf-gcc ./configure --prefix=<安装目录的绝对路径>
make
make install

openssl编译

下载源码,下载后要注意两点

  1. 选择版本,很可能由于API变动,最新版不和rtmpdump不能匹配,所以要预先知道版本,切换到那个版本的tag上再编译。一个确定版本的方法是,自己的上位机openssl版本或者Docker容器中的openssl版本,因为在前面的第一个步骤中,已经证明了系统中的openssl是可以和rtmpdump匹配的,用它准没错。
  2. 如果build了错误版本,再build rtmpdump时发现一些奇怪的问题,要换openssl版本的话,一定要先删除.gitignore之后再git clean -fd,因为有些ignore的文件是会影响编译的。

我在这吃了一些亏,最后发现了openssl API变动的修改,原本公开的结构体不再公开了,那么rtmpdump中使用这个结构体的地方就编不过了。
确定好版本后,编译命令如下:

AR="arm-linux-gnueabihf-ar" RANLIB=arm-linux-gnueabihf-ranlib CC=arm-linux-gnueabihf-gcc /usr/bin/perl ./Configure shared linux-armv4 --prefix=<安装目录的绝对路径>
make
make install

rtmpdump编译

下载源码,完整的make命令我先不给出来,因为要引用自己编的openssl和zlib库,makefile和make命令改得有点乱,一是目前处于demo阶段,二是预计很长时间内,我没有必要再交叉编译librtmp库,所以这里偷个懒,只是强调一下,认真读README,都能搞定的!

导入库到skill

这里看一下rtmp.go中开头部分对CGO的配置,再结合库文件和C文件的位置就明白了。
一个特别的地方是#include "../deps/include/rtmp_sample_api.c"这一句,这里直接弃用了.h文件,也只是偷个懒,因为在前面混乱的研究中,只引用头文件的话,好像会找不到.c文件,索性就直接引用.c文件了,也可能只是个误会,不过我最近感冒,暂时懒得研究了。

总结和计划

  • 上次说的“调通OpenCV,来验证C/C++库的调用”太简单了,没什么好讲的
  • 完成了rtmp推流的上位机和下位机的demo
  • 交叉编译和Go调用C的套路基本清楚了
  • 官方framework中,可以分别获得视频的YCbCr raw data和音频的raw data,这样的数据不能直接交给librtmp库推流,需要经过压缩编码,所以下一步要研究的是如何用ffmpeg的库将raw data压缩成flv格式的包,并交给librtmp库推流
  • 还有些事情可以开始种草了,包括弹幕命令集、机器人命名等

下一篇 HEXA娱乐开发日志技术点004——一步到位的推流

你可能感兴趣的:(HEXA娱乐开发日志技术点003——下位机成功推流)