I discovered a bug in the WebRTC implementation in my K1C 2025‘s firmware that prevents it from working with common WebRTC libraries. Looks like others have also faced this issue.
I’m trying to get my camera feed into a card in HomeAssistant, anyone have a working solution they can share?
If someone from Creality could have a look and see if they can fix the firmware bug, that would be great. Seems like it wouldn’t be too difficult to send the correct payload type in the SDP answer.
opened 04:25AM - 08 Jan 26 UTC
## Background
I have a **Creality K1C (2025) 3D printer** running firmware:
**… `V1.0.0.22.20250711S`**
The printer includes a built-in webcam that exposes a WebRTC stream. This stream works correctly when viewed from the printer’s own webpage (in both Firefox and Chrome), but fails to display in **go2rtc**, even though the WebRTC connection establishes successfully.
Go2RTC config used:
```yaml
streams:
k1c: webrtc:http://{printer_ip_addr}:8000/call/webrtc_local#format=creality
```
I noticed go2rtc already contains patches for other Creality printers, so I investigated whether the K1C fails for similar reasons. I was able to get the stream working locally by patching both **go2rtc** and **Pion**, but I’m unsure whether the approach I took is the best long-term solution.
> I'm not sure if the approach that I took to patch the issue is the best way to solve the problem. Looking for feedback and suggestions.
>
> Note: this was my first deep dive into WebRTC internals, so I may be missing established patterns here.
---
## Working reference: browser implementation on the printer
The printer serves a working WebRTC client at:
```
http://{printer_ip}:8000/?action=stream
```
That page initializes WebRTC with the following inline JavaScript:
```javascript
var pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});
pc.addTransceiver('video', { direction: 'sendrecv' });
pc.createOffer().then(d => pc.setLocalDescription(d));
pc.onicecandidate = event => {
if (event.candidate === null) {
sendOfferToCall(pc.localDescription.sdp);
}
};
pc.ontrack = function (event) {
var el = document.getElementById('remoteVideos');
el.srcObject = event.streams[0];
el.autoplay = true;
el.controls = true;
el.muted = true;
};
```
Since this implementation works reliably, I assumed the printer’s WebRTC behavior must be valid enough to support consumption by go2rtc with some accommodation.
---
## Problem
With go2rtc:
- ICE reaches `connected`
- DTLS handshake completes
- **No video is rendered**
Packet captures show that RTP packets *are* arriving.
Using Wireshark and logging, I observed:
1. RTP packets are received (≈3,800 packets)
2. The **answer SDP** advertises payload types **0 and 96**
3. The printer **actually sends RTP using payload type 98** (from the original offer)
4. `OnTrack` handling fails due to codec lookup issues
5. Two receivers are created internally, causing RTP to be routed to a receiver with no consumers
---
## Root causes
### 1. Codec lookup fails in `getMediaCodec`
**File:** `pkg/webrtc/conn.go`
**Function:** `getMediaCodec`
When `OnTrack` fires with payload type **98**, `getMediaCodec` attempts to find a matching codec in `c.Medias`, which are populated from the **answer SDP**.
Because the answer SDP only lists PT=0 and PT=96, this lookup fails and the function returns:
```go
return nil, nil
```
As a result, the track is never fully created, even though valid RTP packets are arriving.
This appears to be caused by the printer sending RTP using a payload type that it never includes in its answer SDP.
---
### 2. Duplicate receivers created due to pointer comparison
**File:** `pkg/webrtc/producer.go`
**Function:** `GetTrack`
Earlier in setup, the stream system calls `GetTrack` using the codec derived from the answer SDP (PT=0), which creates a receiver.
Later, when `OnTrack` fires with PT=98, `GetTrack` is called again. The existing receiver is not reused because this comparison fails:
```go
track.Codec == codec
```
Although both codecs represent the same underlying media (video), they are different objects, so pointer equality fails. This results in a second receiver being created.
RTP packets are delivered to the PT=98 receiver, but consumers are attached to the PT=0 receiver — so video never reaches the output pipeline.
---
## Changes (workarounds)
### go2rtc: create codec dynamically when missing from SDP
**File:** `pkg/webrtc/conn.go`
When `getMediaCodec` cannot find a codec in the answer SDP, create one dynamically from the `webrtc.TrackRemote` codec information:
```go
// Codec not found in media - create it from track's codec information
// This can happen when the remote sends a payload type not in the answer SDP
params := remote.Codec()
codec := &core.Codec{
PayloadType: uint8(remote.PayloadType()),
ClockRate: params.ClockRate,
Channels: uint8(params.Channels),
FmtpLine: params.SDPFmtpLine,
}
// Convert MIME type to codec name and add to media.Codecs
```
---
### go2rtc: reuse receivers based on codec identity, not pointer
**File:** `pkg/webrtc/producer.go`
If pointer equality fails, attempt to match an existing receiver by codec name and media kind, then update its payload type.
```go
// First, try exact match (pointer equality)
for _, track := range c.Receivers {
if track.Codec == codec {
return track, nil
}
}
// Fallback: match by codec name and media kind
for _, track := range c.Receivers {
if track.Media != nil &&
track.Media.Kind == media.Kind &&
track.Codec.Name == codec.Name {
// Update receiver to actual payload type
track.Codec = codec
return track, nil
}
}
```
This ensures only one receiver exists and both signaling and RTP converge on it.
---
## Required changes in Pion WebRTC
While testing, I also needed the following Pion changes to get the video to show up:
1. Do not start RTP senders when the remote direction is `sendonly` or `inactive`
2. Fall back to default codecs when negotiated codecs do not contain the RTP payload type
These changes should likely live in `pion/webrtc`.
---
## Testing
**Printer firmware:** `V1.0.0.22.20250711S`
Verified with:
```yaml
streams:
k1c: webrtc:http://{printer_ip_addr}:8000/call/webrtc_local#format=creality
```
Steps:
1. Connect to the Creality K1C via the `webrtc:` URL
2. Open `http://localhost:1984/stream.html?src=k1c`
3. Confirm video loads
## Next Steps
Where should we go from here? Clearly, the problem is in the Creality printer itself. So the best solution would be to get them to release a patch in their firmware. However, I'm not very optimistic about the practicality of this solution.
What are your thoughts on the potential approach to work around the issue that I'm suggesting here?
Related: https://github.com/3dg1luk43/ha_creality_ws/issues/46