Swift获取WiFi无线网络摄像头的实时图片流(非视频流)

背景

实验室原有一个支持UVC协议的USB免驱摄像头,但是因为某种原因需要把该摄像头改装成WiFi无线网络摄像头。最后的解决方案是在原有摄像头的基础上再加上一个WiFi模块,把视频流转成WiFi信号进行传输。WiFi模块会提供一个热点信号,iPhone连接该热点信号后,Safari即可通过http://10.10.10.1:8080访问到实时的图片流(非视频流)。

需求

开发一个基于iOS的App来接收实时图片流,开发语言是Swift,UI框架是SwiftUI。

解决过程

Wireshark抓包信息图

响应头信息图

通过使用Wireshark抓包和查看响应头信息大概推断WiFi模块的数据传输方式:

WiFi模块传输的视频实际上是连续的实时二进制图片流(非视频流,这点卡了我好久......),且该图片格式是JPEG。比如视频的帧率是30fps,WiFi模块就会每秒传输30张JPEG格式的图片以此形成动态的视频,并且每一张图片会以一个分隔符分开,也就是上述响应头的boundary字段。

二进制图片流格式样式图

JPEG格式图片的一般以0xFFD8开始, 以0xFFD9结束,我打印了一部分数据流(转成十六进制)发现,我的数据都是0xFFD9后面紧跟着0xFFD8,也就是说该WiFi模块传输的数据直接是一张图紧跟着一张图,并没有用到分隔符。我就以此为根据获取到每一张图片,并把其显示在页面上,以此形成实时视频效果。

代码

import SwiftUI

class CameraModel: NSObject, ObservableObject {
    @Published var image: UIImage?
    private var session: URLSession?
    private var dataTask: URLSessionDataTask?
    private var receivedData = Data()
    let videoStreamUrl: String = "http://10.10.10.1:8080"
    
    override init() {
        super.init()
        self.session = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue.main)
    }
    // 开始捕捉画面
    func startCaptureStream() {
        guard let videoStreamUrl = URL(string: videoStreamUrl) else { return }
        dataTask = session?.dataTask(with: videoStreamUrl)
        dataTask?.resume()
    }
    // 停止捕捉画面
    func stopCaptureStream() {
        dataTask?.cancel()
        // session = nil
        dataTask = nil
        image = nil
    }
}
 
extension CameraModel: URLSessionDataDelegate {
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        receivedData.append(data)
        // JPEG的图片流一般以0xFFD8开始,以0xFFD9结束(十六进制),根据打印出来的data观察,该图片流单纯传输了图片,没有用到分隔符标识
        // upperBound 是闭区间的属性,表示闭区间的上界,也就是子序列的结束位置
        if let imageEndIndex = receivedData.range(of: Data([0xFF, 0xD9]))?.upperBound{
            // 截取一张JPEG的图片流
            let imageData = receivedData.subdata(in: 0..<imageEndIndex)
            if let image = UIImage(data: imageData) {
                DispatchQueue.main.async {
                    self.image = image
                }
            }
            // data清零,准备接收下一张图片流
            receivedData.removeSubrange(..<imageEndIndex)
        }
    }
}


struct ContentView: View {
    @StateObject private var cameraModel = CameraModel()
    
    var body: some View {
        VStack {
            if let image = cameraModel.image {
                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
            } else {
                Text("Waiting for captureStream...")
            }
            HStack {
                Button(action: {
                    cameraModel.startCaptureStream()
                }, label: {
                    Text("开始")
                })
                .frame(width: 120, height: 50)
                .foregroundColor(.white)
                .background(Color.blue)
                .cornerRadius(15)
                .padding()
                Button(action: {
                    cameraModel.stopCaptureStream()
                }, label: {
                    Text("结束")
                })
                .frame(width: 120, height: 50)
                .foregroundColor(.white)
                .background(Color.blue)
                .cornerRadius(15)
                .padding()
            }
        }
    }
}

#Preview {
    ContentView()
}

PS:使用 URLSession 建立一个连接,但不是通过标准的数据任务(dataTask),而是使用更低级别的网络接口来持续读取这种数据流。这点也卡了我很久,我直接用dataTask,发现请求一直进不去回调方法体中。

效果 

效果图

结尾:目前正在学习iOS开发,技术小白,文章如有什么错误请指出,万分感谢!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值