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);
    

      

    五、最终效果

  • 相关阅读:
    LeetCode 79. 单词搜索
    LeetCode 1143. 最长公共子序列
    LeetCode 55. 跳跃游戏
    LeetCode 48. 旋转图像
    LeetCode 93. 复原 IP 地址
    LeetCode 456. 132模式
    LeetCode 341. 扁平化嵌套列表迭代器
    LeetCode 73. 矩阵置零
    LeetCode 47. 全排列 II
    LeetCode 46. 全排列
  • 原文地址:https://www.cnblogs.com/doudouyoutang/p/9179702.html
Copyright © 2011-2022 走看看