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开发,技术小白,文章如有什么错误请指出,万分感谢!!!

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当使用Swift开发连接外部WiFi摄像头并显示实时图像时,你可以使用第三方库来简化开发过程。在这个例子中,我们将使用`CocoaAsyncSocket`库来处理网络连接和数据传输,以及`AVFoundation`库来显示实时图像。 首先,确保在你的项目中导入了`CocoaAsyncSocket`和`AVFoundation`库。 接下来,创建一个ViewController,并在其中添加以下代码: ```swift import UIKit import CocoaAsyncSocket import AVFoundation class ViewController: UIViewController, GCDAsyncUdpSocketDelegate { var udpSocket: GCDAsyncUdpSocket! var videoPlayer: AVPlayer! var videoPlayerLayer: AVPlayerLayer! override func viewDidLoad() { super.viewDidLoad() // 创建UDP socket udpSocket = GCDAsyncUdpSocket(delegate: self, delegateQueue: DispatchQueue.main) // 设置socket接收数据的端口号 do { try udpSocket.bind(toPort: 8888) try udpSocket.beginReceiving() } catch { print("Error setting up UDP socket: \(error.localizedDescription)") } } // 处理接收到的数据 func udpSocket(_ sock: GCDAsyncUdpSocket, didReceive data: Data, fromAddress address: Data, withFilterContext filterContext: Any?) { // 处理接收到的数据 // 在这里解析数据并处理图像显示逻辑 // 例如,你可以将数据转换为图像,并将其显示在界面上 let image = UIImage(data: data) showImage(image) } // 显示图像 func showImage(_ image: UIImage?) { guard let image = image else { return } // 创建AVPlayer和AVPlayerLayer来显示实时图像 let playerItem = AVPlayerItem(url: URL(string: "fake_video_url")!) videoPlayer = AVPlayer(playerItem: playerItem) videoPlayerLayer = AVPlayerLayer(player: videoPlayer) // 设置图像显示的位置和大小 videoPlayerLayer.frame = view.bounds view.layer.addSublayer(videoPlayerLayer) // 播放实时图像 videoPlayer.play() } } ``` 在上述代码中,我们创建了一个UDP socket来接收来自摄像头实时图像数据。在`udpSocket(_:didReceive:fromAddress:withFilterContext:)`方法中,你可以解析接收到的数据并进行相应的处理,例如将数据转换为图像并显示在界面上。 请注意,这只是一个基本的示例代码,实际的实现可能因摄像头品牌和型号而有所不同。你可能需要参考摄像头的开发文档或品牌的官方资源,以获取更详细的指导和实现方式。 此外,请确保在Info.plist文件中添加`NSCameraUsageDescription`键,并提供对摄像头的使用说明,以符合App Store的隐私要求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值