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

  

五、最终效果

 

posted @ 2018-06-13 20:31  兜兜有糖的博客  阅读(2391)  评论(0编辑  收藏  举报