Wowza/WebRTC - how to re-negotiate video format on camera flip

498 Views Asked by At

I followed some hints from here to make camera flip from front to back while presenting/streaming via WebRTC on iOS. The flip itself works but when video track changes in RTCMediaStream, the RTCPeerConnection calls again peerConnectionShouldNegotiate delegate method. Here is my current implementation of it (it works on initial call for front camera video, but it fails when called for the second time when video flips):

func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) {
        guard let stream = liveStream else {
            return
        }
        print("peerConnectionShouldNegotiate")
        peerConnection.offer(for: mediaConstraints) { (desc, err) in
            if let error = err {
                print("peerConnectionShouldNegotiate error", error)
            } else if let desc = desc {
                self.con?.setLocalDescription(desc, completionHandler: { (err) in
                    if let error = err {
                        print("set local desc error", error)
                    } else {
                        let message: [String: Any] = [
                            "direction": "publish",
                            "command": "sendOffer",
                            "streamInfo": [
                                "applicationName": stream.appName,
                                "streamName": stream.streamName,
                                "sessionId": self.sessionId
                            ],
                            "sdp": [
                                "type": RTCSessionDescription.string(for: desc.type),
                                "sdp": desc.sdp
                            ],
                            "userData": [
                                "param1": "value1"
                            ]
                        ]
                        let messageData = try! JSONSerialization.data(withJSONObject: message, options: [])
                        let messageStr = String(data: messageData, encoding: .utf8)!
                        self.socket?.write(string: messageStr)
                    }
                })
            } else {
                print("this is a no no")
            }
        }
    }

The web-socket response from signaling server comes with error and message Stream name is already in use: [my-stream-id]

I don't know what kind of message I should send to Wowza's signaling server to just update video format in existing stream. It appears sendOffer tries to start a new steam. It's hard to find any documentation about this.

1

There are 1 best solutions below

0
Matej Ukmar On

Posting my solution here if somebody else gets the same issue, it took me several days of trial and error to come to a solution.

What I really needed was to just switch camera without re-negotiating RTCPeerConnection. I saw javascript examples where they switched the video track on RTCRtpSender but that didn't work on iOS, also the iOS framework didn't had replaceVideoTrack method which existed in browser's javascript WebRTC library.

My solution was to just update camera on the existing RTCCameraVideoCapturer and thus preserving existing RTCVideoSource and RTCVideoTrack.

My initial call to create video track is like this:

    func createVideoTrack() -> RTCVideoTrack? {
        let devices = RTCCameraVideoCapturer.captureDevices()
        guard
            let camera = selectedCamera(devices: devices)
        else {
            return nil
        }
        let source = rtcPCFactory.videoSource()
        let format = selectFormat(formats: camera.formats)
        let dims = CMVideoFormatDescriptionGetDimensions(format.formatDescription)
        if dims.width > dims.height {
            videoSize = CGSize(width: CGFloat(dims.height), height: CGFloat(dims.width))
        } else {
            videoSize = CGSize(width: CGFloat(dims.width), height: CGFloat(dims.height))
        }
        let fps = selectFrameRate(ranges: format.videoSupportedFrameRateRanges)
        source.adaptOutputFormat(toWidth: 512, height: 288, fps: Int32(fps))
        let videoTrack = rtcPCFactory.videoTrack(with: source, trackId: internalStreamId + "v0")

        let capturer = RTCCameraVideoCapturer(delegate: source)
        capturer.startCapture(with: camera, format: format, fps: Int(fps))
        
        cameraPreviewView.captureSession = capturer.captureSession
        videoCapturer = capturer
        return videoTrack
    }

which successfully negotiated peer connection and then on the flip of the camera I did this:

    func updateCapturerCamera() {
        let devices = RTCCameraVideoCapturer.captureDevices()
        guard
            let camera = selectedCamera(devices: devices),
            let capturer = videoCapturer
        else {
            return
        }
        let format = selectFormat(formats: camera.formats)
        let dims = CMVideoFormatDescriptionGetDimensions(format.formatDescription)
        if dims.width > dims.height {
            videoSize = CGSize(width: CGFloat(dims.height), height: CGFloat(dims.width))
        } else {
            videoSize = CGSize(width: CGFloat(dims.width), height: CGFloat(dims.height))
        }
        let fps = selectFrameRate(ranges: format.videoSupportedFrameRateRanges)
        capturer.startCapture(with: camera, format: format, fps: Int(fps))
    }

... and the camera flip worked as charm on live video stream without re-negotiating the connection.