如何用海思HI3516DV300/3518EV200推流H.264

一开始尝试用hi3518EV200, 后来为了给自己加难度, 就买了个hi3516的板子, 看看差异多大, 结论是, 不大。。。
在这里插入图片描述

前期准备: 你得有一个3516或者3518的开发板(淘宝有卖各种开发板, 推荐一下易百纳, 但是整个板子没有支架, 摄像头都不能支楞起来, 怎么玩? 我就打印了一个非常潦草的支架, 如上图), 有对应的sdk, 有个安卓手机, 电脑上装了虚拟ubuntu14/16, 用于编译海思的应用, 安装Android Studio, 用于编译安卓APP.
然后有最起码的海思交叉编译的知识, 安卓APP的编译安装基础知识.

数据从海思板子上的GC2053摄像头, 从MIPI接口读出来>>>>>编码成H.264>>>>>>TCP发送到公网服务器>>>>>>服务器上的端口转发数据<<<<<<安卓端APP连接到公网<<<<<<<读到数据后解码播放.

不会画图, 将就看吧.

先说说目标, 简单来说, 就是为了我的4G小车的视频推流准备的,小车这边使用海思或者RK采集视像头信号,使用h.264压缩后,通过WiFi接4G的随身路由器推流到公网的固定ip的服务器, 然后再用手机连接我服务器的公网IP, 由公网上的服务器做一个中转, 手机上使用mediacodec+jni的方式解码。
一步步来, 首先, 海思上面跑一个截图摄像头信号, 参考那个著名的venc的sample程序, 并把数据通过TCP连接把数据帧发出去.
海思这部分技能三年前点花了4000块买了朱老师的海思教程才算入门。
海思部分的代码我放到了github, 把它复制到sdk的sample目录下面, 用make编译就好了

https://github.com/MontaukLaw/hi3516_venc_tcp.git

编译好的文件在smp目录下面, mipi_venc就是可执行文件
值得注意的就是

  1. 获取摄像头数据的部分请参考sample的vi部分
  2. 获取数据编码的部分参考venc的部分
  3. 拿到数据之后, 就是在venc的原本写入文件的部分, 改为将数据丢到一个链表里面进行缓存, 链表也非常简单, FIFO的小链表
  4. 然后用另一个进程取出链表中的头部数据, 一旦tcp连接建立, 就用write发送数据即可
  5. 理论上, APP跑起来的时候, 第一时间会去连接公网上的TCP服务器, 所以这部分出现延迟的可能性不大, 链表缓存中的数据最多是因为海思到公网这部分连接的网络延时, 不过值得观察一下缓存的大小.
  6. 分辨率应该就是调低延迟的一个主方向, 毕竟码流小了, 速度可能会更快点儿. 目前是1920*1080

接下来在公网的服务器上, 跑一个转流的程序, 因为海思跟手机都没有公网ip, 拜谁所赐呢?不敢细想。
海思的数据, 通过read海思连接的socketFd读取出来,再马上write到手机的socketFd上去,这部分还挺简单的, 不过也参考了b站上的黑马的linux网络编程的教程, 那位老师叫啥不知道, 但是真的讲得特别好。
重点是通过select管理多连接 。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <printf.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <time.h>
#include <sys/time.h>

#define SERVER_TCP_PORT 54322  // tcp连接的端口
#define BUFF_SIZE 1024*10      // 缓存大小
// #define BUFF_SIZE 1400

int main(int argc, char *argv[]) {
    int i, j, n, nready;

    int nullFd = 0;   // 没有接收者连接的时候, 数据被丢弃
    int maxFd = 0;    // 
    int revFd = 0;    // 接收者的fd
    int listenFd, connFd;

    int senderFd = 0;
    int ret;
    struct timeval tv;
    long secNow = 0;
    long byteRate = 0;


    long totalSent = 0;
    nullFd = open("/dev/null", O_WRONLY);
    printf("null fd:%d\n", nullFd);

    char buf[BUFF_SIZE];
    struct sockaddr_in clientAddr, serverAddr;
    socklen_t clientAddrLen;
    listenFd = socket(AF_INET, SOCK_STREAM, 0);

    int opt = 1;
    setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    bzero(&serverAddr, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr.sin_port = htons(SERVER_TCP_PORT);

    ret = bind(listenFd, (struct sockaddr *) &serverAddr, sizeof(serverAddr));

    listen(listenFd, 128);

    fd_set rset, allset;
    maxFd = listenFd;

    FD_ZERO(&allset);
    FD_SET(listenFd, &allset);

    while (1) {
        rset = allset;
        nready = select(maxFd + 1, &rset, NULL, NULL, NULL);
        if (nready < 0) {
            printf("select error\n");
            continue;
        }

        if (FD_ISSET(listenFd, &rset)) {
            clientAddrLen = sizeof(clientAddr);
            connFd = accept(listenFd, (struct sockaddr *) &clientAddr, &clientAddrLen);
            if (connFd < 0) {
                printf("accept error\n");
                continue;
            }

            if (senderFd == 0) {
                senderFd = connFd;
                printf("sender connected first, fd is %d\n", senderFd);

            } else {
                revFd = connFd;
                printf("revFd connected, fd is %d\n", revFd);
            }

            FD_SET(connFd, &allset);
            if (maxFd < connFd) {
                maxFd = connFd;
                printf("maxFd:%d\n", maxFd);
            }

            if (0 == --nready) {
                continue;
            }
        }

        for (i = listenFd + 1; i <= maxFd; i++) {
            if (FD_ISSET(i, &rset)) {
                if ((n = read(i, buf, sizeof(buf))) == 0) {
                    if (i == revFd) {
                        revFd = 0;
                    } else if (i == senderFd) {
                        senderFd = 0;
                    }
                    printf("%d disconnected \n", i);
                    close(i);
                    FD_CLR(i, &allset);
                } else if (n > 0) {
                    // int writeFd = get_recv_fd(i, maxFd);
                    // printf("data from fd:%d write to %d\n", i, writeFd);
                    // write(writeFd, buf, n);
                    if (revFd != 0) {
                        totalSent += n;
                        // printf("sending :%ld to rev\n", totalSent);
                        byteRate += n;
                        gettimeofday(&tv, NULL);

                        if (tv.tv_sec != secNow) {

                            printf("total sent %ld br: %ld\n", totalSent, byteRate);

                            // printf("Seconds since Jan. 1, 1970: %ld\n", tv.tv_sec);
                            secNow = tv.tv_sec;
                            byteRate = 0;
                        }

                        write(revFd, buf, n);
                    } else {
                        printf("sending to null\n");
                        write(nullFd, buf, n);
                    }
                }
            }
        }
    }

    close(nullFd);

}

意思是如果没有接收端进行连接, 就写到/dev/null里面去, 就是扔掉了, 如果有连接, 就往连接的句柄当中写.
这里可以调试的就是接收发送的包大小, 调低延迟的一个观察方向.

之前是安卓端使用c语言的tcp接收,通过jni往上层传递, 其实是不是可以直接用java的socket?我干嘛要用jni呢???可能是花了5000块学的享学的安卓课程里面教我用ffmpeg解码,但是后来没用上。。。
但是用c连接TCP这个我可以啊, 就直接用jni了, 一开始用udp接收的时候, 一点问题都没有, 在内网延迟非常低.
后来改成Java的Socket来接收, 折腾了最少三天, 其实就是因为Java的那些BufferedReader读取的是char流,而不是byte流, 我又把char做了错误的强转, 导致无论如何出来的图像都不对, 解码器之前报错, 保存成文件, 雷神的SpecialVH264, 居然还能认出sps, pps帧, Elecard StreamEye Tools直接就死在当场, 后来在各端观察裸数据才发现,问题就在Java的转码上…
所以接数据的部分, 我直接就用了Java的InputStream, 它是可以直接read到byte数组的.
然后做分包, 确切的说是分帧, 因为mediacodec的input数据是一帧帧的, 话又说回来, 这部分我没仔细测试, 但是应该是这样, 如果可以直接丢数据包进去, 那可能还省去了很多功夫.
总之, 就是用Java找出数据中的sps, pps, sei, I帧跟P帧, 然后放入解码器, 再在output中对videoview进行渲染.
完整代码如下:
https://github.com/MontaukLaw/tcp_h254_decode_android.git

遗留的问题:

  1. 延迟高达300ms
  2. 分辨率太高
  3. 解码的部分可以直接丢数据包么?
  4. 安卓端APP会频繁的随机崩溃.
  5. 有人说webrtc公网150ms, 真的假的? 还有比我这样直来直去的方式更快的???
  • 3
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Hi3518 SDK 安装以及升级使用说明 第一章 Hi3518_SDK_Vx.x.x.x版本升级操作说明 如果您是首次安装本SDK,请直接参看第2章。 第二章 首次安装SDK 1、Hi3518 SDK包位置 在"Hi3518_V100R001SPC***/01.software/board"目录下,您可以看到一个 Hi3518_SDK_Vx.x.x.x.tgz 的文件,该文件就是Hi3518的软件开发包。 其中,Hi3518_V100R001SPC01xxx对应的是uclib版本,Hi3518_V100R001SPC02xxx对应的是glibc版本。 2、解压缩SDK包 在linux服务器上(或者一台装有linux的PC上,主流的linux发行版本均可以),使用命令:tar -zxf Hi3518_SDK_Vx.x.x.x.tgz , 解压缩该文件,可以得到一个Hi3518_SDK_Vx.x.x.x目录。 3、展开SDK包内容 1) 在执行安装脚本前建议修改系统默认shell为bash。 2) 返回Hi3518_SDK_Vx.x.x.x目录,运行source sdk.unpack(请用root或sudo权限执行)将会展开SDK包打包压缩存放的内容,请按照提示完成操作。 如果您需要通过WINDOWS操作系统中转拷贝SDK包,请先运行source sdk.cleanup,收起SDK包的内容,拷贝到新的目录后再展开。 4、在linux服务器上安装交叉编译器 1)安装uclibc交叉编译器(注意,需要有sudo权限或者root权限): 进入Hi3518_SDK_Vx.x.x.x/osdrv/toolchain/arm-hisiv100nptl-linux目录,运行chmod +x cross.install,然后运行./cross.install即可。 2) 安装glibc交叉编译器(注意,需要有sudo权限或者root权限): 进入Hi3518_SDK_Vx.x.x.x/osdrv/toolchain/arm-hisiv200-linux目录,运行chmod +x cross.install,然后运行./cross.install即可。 3) 执行source /etc/profile, 安装交叉编译器的脚本配置的环境变量就可以生效了,或者请重新登陆也可。 5、编译osdrv 参见osdrv目录下readme 6、SDK目录介绍 Hi3518_SDK_Vx.x.x.x 目录结构如下: |-- sdk.cleanup # SDK清理脚本 |-- sdk.unpack # SDK展开脚本 |-- osdrv # 存放操作系统及相关驱动的目录 | |-- busybox # busybox源代码 | |-- drv # drv源代码 | |-- kernel # linux内核源代码 | |-- pub # 编译好的镜像、工具、drv驱动等 | |-- rootfs_scripts # rootfs源代码 | |-- toolchain # 交叉编译器 | |-- tools # linux工具源代码 | |-- uboot # uboot源代码 | `-- Makefile # osdrv Makefile |-- package # 存放SDK各种压缩包的目录 | |-- osdrv.tgz # linux内核/uboot/rootfs/tools源码压缩包 | |-- mpp.tgz # 媒体处理平台软件压缩包 | `-- image # 可供FLASH烧写的映像文件,如内核、根文件系统 |-- scripts # 存放shell脚本的目录 |-- mpp # 存放媒体处理平台的目录 |-- component # 组件源代码 |-- extdrv # 板级外围驱动源代码 |-- include # 对外头文件 |-- ko # 内核模块 |-- lib # release版本库以及音频库 |-- tools # 媒体处理相关工具 `-- sample # 样例源代码 第三章、安装、升级Hi3518DEMO板开发开发环境 # 如果您使用的Hi3518的DEMO板,可以按照以下步骤烧写u-boot,内核以及文件系统,以下步骤均使用网络来更新。 # 通常,您拿到的单板中已经有烧写u-boot,如果没有的话,建议更换带u-boot的Flash。 # 更详细的操作步骤及说明,请参见01.software\board\documents目录下的《Linux开发环境用户指南》。 # 以下操作假设您的单板上已经有u-boot,使用网口烧写uboot、kernel及rootfs到Flash中。 # Demo单板默认为从SPI Flahs启动。 1、配置tftp服务器 # 可以使用任意的tftp服务器; # 如果使用hi3518a,将package/image_uclibc_hi3518a(或image_glibc_hi3518a)下的相关文件拷贝到tftp服务器目录下; # 如果使用hi3518c,将package/image_uclibc_hi3518c(或image_glibc_hi3518c)下的相关文件拷贝到tftp服务器目录下; # 如果使用hi3516c,则使用package/image_uclibc_hi3516c(或image_glibc_hi3516c)目录下的相关文件镜像。 2、参数配置 # 单板上电后,敲任意键进入u-boot。设置serverip(即tftp服务器的ip)、ipaddr(单板ip)和ethaddr(单板的MAC地址)。 setenv serverip xx.xx.xx.xx setenv ipaddr xx.xx.xx.xx setenv ethaddr xx:xx:xx:xx:xx:xx setenv netmask xx.xx.xx.xx setenv gatewayip xx.xx.xx.xx ping serverip,确保网络畅通。 3、烧写映像文件到SPI Flash 以16M SPI Flash为例。 1)地址空间说明 | 1M | 3M | 12M | |------------|---------------|-----------------------| | boot | kernel | rootfs | 以下的操作均基于图示的地址空间分配,您也可以根据实际情况进行调整。 2)烧写u-boot sf probe 0 sf erase 0 0x100000 mw.b 82000000 ff 100000 tftp 0x82000000 u-boot-200MHZ.bin #如果是hi3516c,使用u-boot-220MHZ.bin sf write 82000000 0 100000 reset 3)烧写内核 sf probe 0 sf erase 100000 0x300000 mw.b 82000000 ff 300000 tftp 82000000 uImage sf write 82000000 100000 300000 4)烧写文件系统 sf probe 0 sf erase 400000 0xc00000 mw.b 82000000 ff c00000 tftp 82000000 rootfs_64k.jffs2 sf write 82000000 400000 0xc00000 5)设置启动参数 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' save 4、烧写映像文件到NAND Flash 注意:Hi3518c不支持nand flash 下面以128M 2k1bit类型的Nand Flash为例。 1)地址空间说明 | 1M | 3M | 12M | 112M | |------------|---------------|-----------------------|-----------------------| | boot | kernel | rootfs | others | 以下的操作均基于图示的地址空间分配,您也可以根据实际情况进行调整。 2)烧写u-boot mw.b 82000000 ff 100000 tftp 82000000 u-boot-200MHZ.bin #如果是hi3516c,使用u-boot-220MHZ.bin nand erase 0 100000 nand write 82000000 0 100000 reset 3)烧写内核 mw.b 82000000 ff f00000 tftp 82000000 uImage nand erase 100000 300000 nand write 82000000 100000 300000 4)烧写文件系统 mw.b 82000000 ff c00000 tftp 82000000 rootfs_2k_1bit.yaffs2 nand erase 400000 c00000 nand write.yaffs 82000000 400000 $(filesize) 5)设置启动参数 setenv bootargs 'mem=64M console=ttyAMA0,115200 root=/dev/mtdblock2 rootfstype=yaffs2 mtdparts=hinand:1M(boot),3M(kernel),12M(rootfs),112M(others)' setenv bootcmd 'nand read 0x82000000 0x100000 0x300000;bootm 0x82000000' save 5、启动新系统 reset # 重启进入新系统。 第四章、开发前环境准备 1、管脚复用 与媒体业务相关的管脚复用都在mpp/ko_Hi3518目录下的sh脚本中配置,如果与实际情况不符请直接修改,此脚本被load3518调用,在加载mpp内核模块之前被执行; mpp之外的其他管脚复用统一在uboot中配置,详细说明请参见《U-boot移植应用开发指南》。 第五章、使用SDK和DEMO板进行开发 1、开启Linux下的网络 # 设置网络 ifconfig eth0 hw ether xx:xx:xx:xx:xx:xx; ifconfig eth0 xx.xx.xx.xx netmask xx.xx.xx.xx; route add default gw xx.xx.xx.xx # 然后ping一下其他机器,如无意外,网络将能正常工作。 2、使用NFS文件系统进行开发 # 在开发阶段,推荐使用NFS作为开发环境,可以省去重新制作和烧写根文件系统的工作。 # 挂载NFS文件系统的操作命令: mount -t nfs -o nolock -o tcp xx.xx.xx.xx:/your-nfs-path /mnt # 然后就可以在/mnt目录下访问服务器上的文件,并进行开发工作。 3、开启telnet服务 # 网络正常后,运行命令 telnetd& 就可以启动单板telnet服务,然后才能使用telnet登录到单板。 4、运行MPP业务 # 在单板linux系统下,进入mpp/ko_Hi3518目录,加载KO。执行load3518脚本时需要带sensor名,如使用ar0130 sensor: cd mpp/ko_hi3518 ./load3518 -i ar0130 # 进入各sample目录下执行相应样例程序(sample需要先在服务器上成功编译过) cd mpp/sample/vio ./sample_vio 0 第六章 地址空间分配与使用 1、DDR内存管理说明 1)所有DDR内存中,一部分由操作系统管理,称为OS内存;另一部分由MMZ模块管理,供媒体业务单独使用,称为MMZ内存。 2)OS内存起始地址为0x80000000,内存大小可通过bootargs进行配置,例如第三章中的setenv bootargs 'mem=64M ... ',表示分配给操作系统内存为64M,您可以根据实际情况进行调整。 3)MMZ内存由MMZ内核模块管理(mpp/ko_hi35xx目录下的mmz.ko),加载mmz模块时,通过模块参数指定其起始地址及大小,例如: insmod mmz.ko mmz=anonymous,0,0x84000000,64M anony=1 表示mmz一块区域,区域的名称为anonymous,起始地址为0x84000000,大小为64M。 您可以通过修改mpp/ko_Hi3518目录下load3518脚本中的mmz模块参数,来修改其起始地址和总大小。 4)请注意MMZ内存地址范围不能与OS内存重叠。 2、DEMO板DDR内存管理示意 1) 以容量为128MBytes的DDR内存为例,以下为根据本文档和SDK默认配置得到的内存管理示意图: -----|-------| 0x80000000 # Memory managed by OS. 64M | OS | | | -----|-------| 0x84000000 # Memory managed by MMZ block anonymous. 64M | MMZ | | | -----|-------| 0x88000000 # End of DDR. 注意: (1)用户在配置启动参数时需要设置OS的管理内存为64M,“setenv bootargs 'mem=64M ...”。 (2)系统启动后,配置load3518的脚本中mmz的管理内存为64M,“insmod mmz.ko mmz=anonymous,0,0x84000000,64M”。
海思hi3516dv300硬件设计用户指南是一本针对海思公司推出的hi3516dv300芯片的硬件设计的指导手册。该手册的目的是帮助用户了解hi3516dv300芯片的硬件设计要求和规范,并提供相关的设计指导和建议。 hi3516dv300芯片是一款高性能、低功耗的视频处理芯片,主要应用于视频监控、智能交通和安防等领域。硬件设计是确保芯片正常工作和发挥最佳性能的基础,因此对于使用hi3516dv300芯片的设计人员来说,掌握硬件设计指南是非常重要的。 在这本指南中,首先介绍了hi3516dv300芯片的主要特性和功能,包括处理能力、视频编解码、音频处理等。然后详细讲解了芯片的外部接口和引脚分配,包括视频输入、输出接口、音频接口、以太网接口等。同时,还对时钟、电源、存储器和外设等方面的设计进行了说明和建议。 此外,指南中还提供了关于PCB设计的一些建议,包括地域分布、信号电源分离、阻抗匹配、电磁兼容性等方面,帮助设计人员提高设计质量和可靠性。 总之,海思hi3516dv300硬件设计用户指南是一本重要的参考资料,它提供了关于hi3516dv300芯片硬件设计方面的详尽说明和建议,对于希望使用该芯片进行设计的用户来说是不可或缺的。通过认真阅读和遵循指南中的规范和要求,设计人员可以更好地实现hi3516dv300芯片的应用,提高产品的性能和可靠性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值