zoukankan      html  css  js  c++  java
  • ReplayKit2 有线投屏项目总结

    一、实现目标

      iOS11.0以上设备通过USB线连接电脑,在电脑端实时看到手机屏幕内容

      画质达到超清720级别,码率可达到1Mbps以上

    二、实现技术方案设计

      1、手机端采用ReplayKit2框架,在Upload Extension 进程中采集到屏幕内容YUV和系统声音PCM+麦克风声音PCM

      

    - (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
        switch (sampleBufferType) {
            case RPSampleBufferTypeVideo:
                break;
            case RPSampleBufferTypeAudioApp:
                break;
            case RPSampleBufferTypeAudioMic:
                break;
            default:
                break;
        }
    }

      2、考虑在在Upload Extension 进程中或者主App进程中对图像和声音进行编码,编码成H264+AAC ,然后封装为FLV格式的包,利用RTMP协议进行推流

        因为目前已经存在一套推流的接口,所以考虑在PC端增加RTMP收流服务,进行解析视频流,然后渲染

      3、在PC端建立RTMP收流服务端,解码,渲染;目前OBS已经存在相关模块

    三、遇到的问题以及解决方案

      1、如果在局域网中,目前的基础上,无线推流到PC和推流到远程直播服务器流程基本一样

      2、如何规避局域网的网络抖动环境,实现高清推流?局域网可能因为多人使用导致带宽分配原因,以及信道干扰原因导致上行速率达不到标称要求

        采用有线方案可以解决这个问题,那么手机如何利用USB线传递数据?

      3、USB传递有线数据有两种方案:

        第一种是MIFI认证,使用iOS外设通信的库,ExternalAccessory

        第二种是通过iproxy , 在PC端执行"iproxy pcport mobileport"的方式实现端口转发,PC上连接pcport会连接到手机的mobileport,当一条TCP连接建立成功之后手机就可以利用USB线和PC实现双向通信了

        这里为什么不能像安卓一样,实现正向的转发,将手机的端口转发到PC上呢?这就是iOS系统相对封闭的原因;

        猜测安卓连接USB线的时候,PC端执行命令会在手机端出发操作实现端口转发规则;而iOS不行

      那么最终采用的是第二种方案。

    四、推流SDK协议改造

      对于采用的第二种方案,实施的时候遇到两个问题?

      第一个如何实现由PC主动连接手机的过程,连接手机的哪个端口?

        对于这个问题,这里解决方案是,第一个在socket上面设置套接字为REUSE相关的属性,保证端口能够重复绑定成功,这里假定这个1397端口只有这个程序使用

                       第二个是在有线投屏的时候,手机要先扫码得到PC的一个key,手机在启动一个TCP监听后将端口号联系这个key一起发给我们的后台,后台通过push或者pc pull的方式,将这个信息通知到PC端,也就是建立信道的方式

      第二个问题,如何在一个RTMP.c的主动发起连接中,修改原有的方式,先尝试被动连接(先启动一个同步阻塞的监听socket等待PC连接)。在这个逻辑中,因为等待过程是阻塞的,必然涉及到延时,在这里遇到了坑

        我们希望在 tcp socket bind一个端口,然后listen,然后accept的时候,希望在accept这个方法实现超时逻辑,最开始是这样实现的

        

     int ret = ::setsockopt(m_nRealServerSocket, SOL_SOCKET, SO_RCVTIMEO, (const char*) &tv, sizeof(tv));
            LOGW("socket accept start 1, set timeout ret = %d", ret);
            ret = ::setsockopt(m_nRealServerSocket, SOL_SOCKET, SO_SNDTIMEO, (const char*) &tv, sizeof(tv));

        上述的代码在安卓和PC上面生效,但是在iOS平台上面无效,虽然设置了一个超时时间,但是这个超时永远不会触发,accept永久阻塞

        为了规避这个问题,我采用select监听文件描述符的方式,select跨平台兼容性效果更好

        采用以下代码实现accept超时逻辑:

            int fd = -1;
            fd_set fdflag;
            sockaddr_in client_addr;
            memset(&client_addr, 0, sizeof(client_addr));
            
            
            FD_ZERO(&fdflag);
            FD_SET(m_nRealServerSocket, &fdflag);
                
            LOGW("socket accept start, timeout = %d secs", tv.tv_sec);
            bool hasProcessConnect = false;
            if(!hasProcessConnect && select(m_nRealServerSocket + 1, &fdflag, NULL, NULL, &tv) > 0)
            {
                hasProcessConnect = true;
                fd = accept(m_nRealServerSocket, (struct sockaddr*)NULL, NULL);
            }
            // 一次事件触发之后, 清理监控的描述符
            FD_ZERO(&fdflag);
    

      

    五、最终效果

  • 相关阅读:
    MFC中char*,string和CString之间的转换(待补充)
    Gem/Bundle/Rvm
    Ruby开发入门
    Maven原型骨架及常见问题
    Nginx Upstream模块源码分析(上)
    mysqldump的几个主要选项探究
    探索Antlr(Antlr 3.0更新版)
    Maven2插件开发入门
    说说家乡的互联网-沈阳
    Nginx模块之SessionSticky
  • 原文地址:https://www.cnblogs.com/doudouyoutang/p/9179702.html
Copyright © 2011-2022 走看看