zoukankan      html  css  js  c++  java
  • iOS ReplayKit实时录制屏幕实现方案的细节记录

    项目有个需求,需要把ios设备上的操作画面实时传输出去,也就是类似推流手机直播画面的方案。

    一番调研后发现在ios中,我们可以通过ios自带ReplayKit框架实现。

    关于ReplayKit的讲解,这篇文章写的很好,可以看一下

    iOS端使用replaykit录制屏幕的技术细节

    文章详细介绍了ReplayKit的发展历程,从ios9~ios12的每个版本的功能迭代都有写,包括如何录制当前app内容,还是制系统层次的内容等。

    不过由于我的需求是只录制当前App内容,所以下面只讲解这方面的。

    我的测试demo流程大概这样

    1、通过ReplayKit开启录屏

    2、实时获取视频流CMSampleBuffer

    3、对CMSampleBuffer处理发包或推流

    为了效果快速呈现,这里我采取udp发包来传输内容

    以下代码仅供参考逻辑。

    1、开启录屏

    /// 开启录制屏幕
        func startRecord()  {
            
            if !RPScreenRecorder.shared().isAvailable{
                print("暂不支持xxx功能")
                return
            }
            
            if #available(iOS 11.0, *) {
                printDebug(message: "start record")
                if _udpSocket == nil{
              //初始化udp initUdp() connectUdp() queneConvertImage
    = DispatchQueue(label: "teacher.show.quene") } isScreenRecording = true weak var weakself = self //该方法只能录当前app,如果需要录系统的,用broadcastxxx那个方法 RPScreenRecorder.shared().startCapture(handler: { (sampleBuffer, sampleBufferType, error) in if error == nil{ if CMSampleBufferDataIsReady(sampleBuffer) && sampleBufferType == RPSampleBufferType.video{ weakself?.queneConvertImage.async { weakself?.getUIImageFromCMSampleBuffer(sampleBuffer: sampleBuffer) } } }else{ printDebug(message: error.debugDescription) } }) { (finishError) in } } else { // Fallback on earlier versions print("xxx功能需要ios11版本及以上") } }

    2、对视频文件进行处理

    func getUIImageFromCMSampleBuffer(sampleBuffer:CMSampleBuffer){
           
            /*
             关于两种压缩系数结果测试如下:
             UIImageJPEGRepresentation:
             0.01-63000  0.1-63000   0.2-67000  0.3-73000  0.4-85000  0.5-97000  0.6-110000  0.9-150000  1-290000
             UIImagePNGRepresentation:220000
             
             */
            
            let image1 = K12SampleBufferTool.image(from: sampleBuffer)
            if let data = UIImageJPEGRepresentation(image1!, 0.1),_udpSocket != nil{
                //两次包相同,就忽略本次发送
                if lastBufferlen > 0 && fabs(Double(lastBufferlen - data.count)) < 100{
                    return
                }
                lastBufferlen = data.count
                compressData(data: data, image: image1!)
                
            }
        }
    
        /// 压缩图片发送
        ///
        /// - Parameters:
        ///   - odata: <#odata description#>
        ///   - image: <#image description#>
        func compressData(data:Data,image:UIImage){
         //质量压缩符合大小
    if data.count < maxLength{ printDebug(message: "--- data:(data) ") _udpSocket?.send(data, withTimeout: -1, tag: 0) return } let rate : Double = data.count > maxLength * 2 ? 1.0/Double(data.count/maxLength) : 0.5

         //采用size压缩再次处理 if let d = UIImageJPEGRepresentation(image.compress(with: rate), 0.0),_udpSocket != nil{ //压缩过还超过最大值,就不发送 if d.count > maxLength{ return } printDebug(message: "----data:(data) ------compressdata.size:(d)") _udpSocket?.send(d, withTimeout: -1, tag: 0) } }

    buffer转iamge的方法

    + (UIImage *) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer{
        
        CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
        CGImageRef image = NULL;
        if (@available(iOS 9.0, *)) {
            OSStatus createdImage = VTCreateCGImageFromCVPixelBuffer(imageBuffer, NULL, &image);
            UIImage * image1 = nil;
            if (createdImage == noErr) {
                image1 = [UIImage imageWithCGImage:image];
            }
            CGImageRelease(image);
            return image1;
        } else {
            return nil;
        }
       
    }
    View Code

    以上是简单的测试,udp发送这块可以自行修改自己的逻辑。到这里,内容发出去了

    3、停止录制

    /// 停止录制屏幕
        func stopRecord() {
            if #available(iOS 11.0, *) {
                printDebug(message: "stop record")
                isScreenRecording = false
                RPScreenRecorder.shared().stopCapture { (error) in
                    if error != nil{
                        printDebug(message: "stopRecord success")
                    }
                }
            } else {
                // Fallback on earlier versions
            }
            
            //关闭udp
            nilSocket()
        }

    结束之后记得调用一下关闭方法。

    总结:

    1、由于是采取图片方式udp发送,在CMSampleBuffer转image过程还是比较耗cpu的,但是,录屏本事对cpu和内存对占用是极少的。

    2、CMSampleBuffer的大小是根据画面色彩度来的,如果画面色彩很多,bytes会比较大。

    3、如果不需要实时录制,可以采用提供的结束统一获取视频的方式,那种更简单。

    4、用这个方法实时录制需要ios11系统,这点是个硬伤;但关于另一个录制系统方法,好像ios10就可以了,不过这种实现方式,用户感知比较明显,具体看上文的文章连接。

    5、录屏必须真机测试。

     

  • 相关阅读:
    <%%>,<%!%>,<%=%>,<%@%>,<jsp:include><%@ include%>区别
    struts2学习
    struts2标签
    OGNL
    Andriod XML Editor cannot process this input
    Error 错误: 找不到或无法加载主类
    Class<T> 与T区别
    poi
    POI-java读取Excel(包含合并单元格)
    SQL-字符串连接聚合函数
  • 原文地址:https://www.cnblogs.com/yajunLi/p/10647217.html
Copyright © 2011-2022 走看看