赞
踩
一开始尝试用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就是可执行文件
值得注意的就是
接下来在公网的服务器上, 跑一个转流的程序, 因为海思跟手机都没有公网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
遗留的问题:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。