Skip to content

Add demo to open Bambu X1 Carbon live stream #128

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
443 changes: 440 additions & 3 deletions Cargo.lock

Large diffs are not rendered by default.

33 changes: 25 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ edition = "2021"
panic = "abort"

[workspace]
members = [
"bambulabs",
"moonraker"
]
members = ["bambulabs", "moonraker", "bambu-stream"]

[features]
default = ["bambu", "formlabs", "moonraker", "serial"]
Expand Down Expand Up @@ -42,7 +39,10 @@ opentelemetry = "0.25.0"
opentelemetry-otlp = "0.25.0"
opentelemetry_sdk = { version = "0.25.0", features = ["rt-tokio"] }
prometheus-client = "0.22.3"
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
reqwest = { version = "0.12", default-features = false, features = [
"json",
"rustls-tls",
] }
schemars = { version = "0.8", features = ["chrono", "uuid1", "bigdecimal"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand All @@ -52,13 +52,30 @@ slog-async = "2.7.0"
slog-json = "2.6.1"
slog-term = "2.9.1"
thiserror = "1.0.64"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time", "net"] }
tokio-serial = { version = "5", optional = true, features = ["tokio-util", "libudev"] }
tokio = { version = "1", features = [
"rt-multi-thread",
"macros",
"time",
"net",
] }
tokio-serial = { version = "5", optional = true, features = [
"tokio-util",
"libudev",
] }
toml = "0.8.19"
tracing = "0.1"
tracing-opentelemetry = "0.26.0"
tracing-slog = "0.3.0"
tracing-subscriber = { version = "0.3.18", features = ["registry", "std", "fmt", "smallvec", "ansi", "tracing-log", "json", "env-filter"] }
tracing-subscriber = { version = "0.3.18", features = [
"registry",
"std",
"fmt",
"smallvec",
"ansi",
"tracing-log",
"json",
"env-filter",
] }
uuid = "1.11.0"

[dev-dependencies]
Expand Down
2 changes: 2 additions & 0 deletions bambu-stream/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
*.log
27 changes: 27 additions & 0 deletions bambu-stream/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "bambu-stream"
version = "0.1.0"
edition = "2021"

[dependencies]
tokio-rustls = "0.26.0"
rcgen = { version = "0.13", features = ["pem"] }
tokio = { version = "1.0", features = ["full"] }
futures-util = "0.3.1"
lazy_static = "1.1"
webpki-roots = "0.26"
rustls-pemfile = "2"
env_logger = "0.11.5"
log = "0.4.22"
rustls = "0.23.12"
url = "2.5.2"
anyhow = "1.0.86"
http-auth = "0.1.9"
openh264 = "0.6.2"
sdl2 = "0.37.0"
nom = "7.1.3"
maplit = "1.0.2"
rtp-types = "0.1.1"
rtp = "0.11.0"
bytes = "1.7.1"
webrtc-util = "0.9.0"
173 changes: 173 additions & 0 deletions bambu-stream/NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# RTP

<https://www.rfc-editor.org/rfc/rfc3550#page-12>

Stuff is sent in network byte order, i.e. big-endian.

To be able to decrypt RTSP frames in Wireshark, we can use `ffplay`:

```bash
export SSLKEYLOGFILE=~/ssl-keys.log
ffplay "rtsps://bblp:[email protected]:322/streaming/live/1"
```

Then go into the Wireshark TLS settings and select the log file to use.

## Payload type 96: "dynamic"

<https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml>,
[RFC3551](https://www.rfc-editor.org/rfc/rfc3551.html).

## Header

The original diagram in the spec is really annoying as it works in base 10. I want bytes! So here
they are:

```
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X| CC |M| PT | sequence number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| synchronization source (SSRC) identifier |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
| contributing source (CSRC) identifiers |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```

It seems that most frames apart from a couple at the start begin with `80 60 c3`, then what I
_reckon_ is the low byte of the sequence counter in the next byte. This smells like an RTP header,
although none of the fixed values (like `V=2`) seem to match up. The preceding stuff like
`24 00 05 ac` might be some kind of frame delimiter? Or some custom stuff by the LIVE555 streaming
crap? I dunno.

# Figuring out the stream format

The normal `00 00 00 01` NALU packet delimiter doesn't exist in any of the data I've captured from
either my Rust code or `ffplay`.

A Wireshark dump shows small frames like this from `ffplay`:

```
24 00 05 ac
```

`05 ac` is `1452` in decimal, which is the length of the next chunk of data captured. Does this mean
that the stream is in fact AVCC? The `24 00` part is still a mystery.

Lining up `24 00` with the header diagram above we get:

```
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
|V=2|P|X| CC |M| PT |

Rust print:
0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0

Each byte mirrored:
0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0
```

Which doesn't make much sense...

What about `80 60`?

```
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
|V=2|P|X| CC |M| PT |

Rust print:
1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0

Each byte mirrored:
0 0 0 0 0 0 0 1 0 0 0 0 0 1 1 0
```

---

Every chunk seems to have a common header of `80, 60, 5d, a2, 4a, d4, 97, f9, d4, 9e, 7f, e1` after
the weird 4 byte header.

---

According to <https://stackoverflow.com/a/75748093>:

> AVCC is best option for: a stored file (with known sizes & offsets).

But how much do you want to bet that Bambu just threw it into their live streaming setup? The
LIVE555 website implies it prefers streaming files, so maybe that's why.

First 4 bytes of an AVCC frame are called "extradata" or "sequence header"
<https://stackoverflow.com/a/24890903>

---

From ffmpeg/ffplay in the DESCRIBE response:

```
v=0
o=- 1723111495901673 1 IN IP4 192.168.0.96
s=rtsp stream server
i=Thu Aug 8 11:04:55 2024

t=0 0
a=tool:LIVE555 Streaming Media v2023.03.30
a=type:broadcast
a=control:*
a=range:npt=now-
a=x-qt-text-nam:rtsp stream server
a=x-qt-text-inf:Thu Aug 8 11:04:55 2024

m=video 0 RTP/AVP 96
c=IN IP4 0.0.0.0
b=AS:17186
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=42C01F;sprop-parameter-sets=Z0LAH42NUCSC2TZAAAADAEAAAA8jwiEagA==,aM4xsg==
a=control:track1
```

This is an SDP (`application/sdp`) formatted response. I can test them with VLC:
<https://stackoverflow.com/q/20634476>

The last little base64 `aM4xsg==` decodes to (hex):

```
68,CE,31,B2
```

This is present in the bytes I get in the Rust code! No idea what this means...

---

The longer `Z0LAH42NUCSC2TZAAAADAEAAAA8jwiEagA==` (SPS and PPS?) is:

```
67,42,C0,1F,8D,8D,50,24,82,D9,36,40,00,00,03,00,40,00,00,0F,23,C2,21,1A,80
```

which is indeed present as well.

Some more info here <https://www.cardinalpeak.com/blog/the-h-264-sequence-parameter-set>

The received frame starts like this:

```
[ 24, 00, 00, 25 ], 80, 60, 07, 8f, 84, b4, 4d, 39, 5b, 6e, 5f, 94, [ 67, 42, c0, 1f, 8d, 8d, 50, 24, 82, d9, 36, 40, 00, 00, 03, 00, 40, 00, 00, 0f, 23, c2, 21 ]
```

---

`ffmpeg` says

```
Input #0, rtsp, from 'rtsps://bblp:[email protected]:322/streaming/live/1':
Metadata:
title : rtsp stream server
comment : Sat Aug 10 00:25:18 2024
Duration: N/A, start: 0.031978, bitrate: N/A
Stream #0:0, 21, 1/90000: Video: h264 (Constrained Baseline), 1 reference frame, yuvj420p(pc, progressive, left), 1168x720, 0/1, 30 fps, 30 tbr, 90k tb
```

Profile is `66d` with constrained bit set to 1
15 changes: 15 additions & 0 deletions bambu-stream/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Bambu X1 Carbon RTSP streaming

Authenticates with a Bambu X1 Carbon RTSP stream and shows the frames in an SDL2 window. The printer
IP and access code are currently hard coded. SDL2 can be a bit of a pain to get working on macOS but
[this](https://github.com/embedded-graphics/simulator?tab=readme-ov-file#macos-brew) works well for
me at least.

```bash
cargo run --release
```

`RUST_LOG=debug` can be useful for debugging RTSP auth issues.

Note that exiting the program doesn't work very well, even though to my knowledge the SDL event loop
isn't blocked. Try using `killall bambu-steam` on Linux.
81 changes: 81 additions & 0 deletions bambu-stream/sample-handshake.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
From `ffplay "rtsps://bblp:[email protected]:322/streaming/live/1"`

OPTIONS rtsps://192.168.0.96:322/streaming/live/1 RTSP/1.0
CSeq: 1
User-Agent: Lavf60.16.100

RTSP/1.0 200 OK
CSeq: 1
Date: Thu, Aug 08 2024 10:57:17 GMT
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER

DESCRIBE rtsps://192.168.0.96:322/streaming/live/1 RTSP/1.0
Accept: application/sdp
CSeq: 2
User-Agent: Lavf60.16.100

RTSP/1.0 401 Unauthorized
CSeq: 2
Date: Thu, Aug 08 2024 10:57:17 GMT
WWW-Authenticate: Digest realm="LIVE555 Streaming Media", nonce="db860ca0377a9fe8769112644ba5db76"

DESCRIBE rtsps://192.168.0.96:322/streaming/live/1 RTSP/1.0
Accept: application/sdp
CSeq: 3
User-Agent: Lavf60.16.100
Authorization: Digest username="bblp", realm="LIVE555 Streaming Media", nonce="db860ca0377a9fe8769112644ba5db76", uri="rtsps://192.168.0.96:322/streaming/live/1", response="142ead4ed9c3a58148d54eee6b4d0715"

RTSP/1.0 200 OK
CSeq: 3
Date: Thu, Aug 08 2024 10:57:17 GMT
Content-Base: rtsps://192.168.0.96/streaming/live/1/
Content-Type: application/sdp
Content-Length: 496

v=0
o=- 1723111495901673 1 IN IP4 192.168.0.96
s=rtsp stream server
i=Thu Aug 8 11:04:55 2024

t=0 0
a=tool:LIVE555 Streaming Media v2023.03.30
a=type:broadcast
a=control:*
a=range:npt=now-
a=x-qt-text-nam:rtsp stream server
a=x-qt-text-inf:Thu Aug 8 11:04:55 2024

m=video 0 RTP/AVP 96
c=IN IP4 0.0.0.0
b=AS:17186
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=42C01F;sprop-parameter-sets=Z0LAH42NUCSC2TZAAAADAEAAAA8jwiEagA==,aM4xsg==
a=control:track1

SETUP rtsps://192.168.0.96/streaming/live/1/track1 RTSP/1.0
Transport: RTP/AVP/TCP;unicast;interleaved=0-1
CSeq: 4
User-Agent: Lavf60.16.100
Authorization: Digest username="bblp", realm="LIVE555 Streaming Media", nonce="db860ca0377a9fe8769112644ba5db76", uri="rtsps://192.168.0.96/streaming/live/1/track1", response="5bb1433eef289f517fbcce998eb868ec"

RTSP/1.0 200 OK
CSeq: 4
Date: Thu, Aug 08 2024 10:57:17 GMT
Transport: RTP/AVP/TCP;unicast;destination=192.168.0.74;source=192.168.0.96;interleaved=0-1
Session: 061546E4;timeout=10

PLAY rtsps://192.168.0.96/streaming/live/1/ RTSP/1.0
Range: npt=0.000-
CSeq: 5
User-Agent: Lavf60.16.100
Session: 061546E4
Authorization: Digest username="bblp", realm="LIVE555 Streaming Media", nonce="db860ca0377a9fe8769112644ba5db76", uri="rtsps://192.168.0.96/streaming/live/1/", response="a5d9483ecc37519ee2c1f270b2a38e94"

// Some binary crap here, maybe video frames that start coming in?

RTSP/1.0 200 OK
CSeq: 5
Date: Thu, Aug 08 2024 10:57:17 GMT
Range: npt=0.000-
Session: 061546E4
RTP-Info: url=rtsps://192.168.0.96/streaming/live/1/track1;seq=61243;rtptime=665680901
Loading
Loading