rtmp: improve control message compatibility with non-standard cameras (#4909)

* feat: improve RTMP control message compatibility with non-standard cameras

- Add flexible chunk stream ID validation for control messages
- Support common non-standard chunk stream IDs (2,3,4,5,6) used by various cameras
- Maintain backward compatibility with standard RTMP clients
- Fix connection issues with PTZ cameras using chunk stream ID 4

This resolves compatibility issues with IP cameras that don't strictly follow
RTMP spec while maintaining full support for standard RTMP implementations.
Previously, cameras using non-standard chunk stream IDs would fail with
'unexpected chunk stream ID' errors. Now MediaMTX provides nginx-rtmp level
compatibility with real-world camera implementations."

* add tests

* entirely remove control chunk stream ID validation

---------

Co-authored-by: Hyesung.Kim <hyesung.kim@xcath.com>
Co-authored-by: aler9 <46489434+aler9@users.noreply.github.com>
This commit is contained in:
hyesung913
2025-09-03 15:11:32 +02:00
committed by GitHub
co-authored by Hyesung.Kim aler9
parent 15e2713ae7
commit cb16f68432
12 changed files with 16 additions and 44 deletions
@@ -12,10 +12,6 @@ type Acknowledge struct {
}
func (m *Acknowledge) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
if len(raw.Body) != 4 {
return fmt.Errorf("unexpected body size")
}
@@ -12,10 +12,6 @@ type SetChunkSize struct {
}
func (m *SetChunkSize) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
if len(raw.Body) != 4 {
return fmt.Errorf("invalid body size")
}
@@ -13,10 +13,6 @@ type SetPeerBandwidth struct {
}
func (m *SetPeerBandwidth) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
if len(raw.Body) != 5 {
return fmt.Errorf("invalid body size")
}
@@ -12,10 +12,6 @@ type SetWindowAckSize struct {
}
func (m *SetWindowAckSize) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
if len(raw.Body) != 4 {
return fmt.Errorf("invalid body size")
}
@@ -12,10 +12,6 @@ type UserControlPingRequest struct {
}
func (m *UserControlPingRequest) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
if len(raw.Body) != 6 {
return fmt.Errorf("invalid body size")
}
@@ -12,10 +12,6 @@ type UserControlPingResponse struct {
}
func (m *UserControlPingResponse) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
if len(raw.Body) != 6 {
return fmt.Errorf("invalid body size")
}
@@ -13,10 +13,6 @@ type UserControlSetBufferLength struct {
}
func (m *UserControlSetBufferLength) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
if len(raw.Body) != 10 {
return fmt.Errorf("invalid body size")
}
@@ -12,10 +12,6 @@ type UserControlStreamBegin struct {
}
func (m *UserControlStreamBegin) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
if len(raw.Body) != 6 {
return fmt.Errorf("invalid body size")
}
@@ -12,10 +12,6 @@ type UserControlStreamDry struct {
}
func (m *UserControlStreamDry) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
if len(raw.Body) != 6 {
return fmt.Errorf("invalid body size")
}
@@ -12,10 +12,6 @@ type UserControlStreamEOF struct {
}
func (m *UserControlStreamEOF) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
if len(raw.Body) != 6 {
return fmt.Errorf("invalid body size")
}
@@ -12,10 +12,6 @@ type UserControlStreamIsRecorded struct {
}
func (m *UserControlStreamIsRecorded) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
if len(raw.Body) != 6 {
return fmt.Errorf("invalid body size")
}
@@ -645,6 +645,22 @@ func TestReader(t *testing.T) {
}
}
func TestReaderNonStandardControlChunkStreamID(t *testing.T) {
buf := []byte{
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04,
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
0x8a, 0xce,
}
bc := bytecounter.NewReader(bytes.NewReader(buf))
r := NewReader(bc, bc, nil)
dec, err := r.Read()
require.NoError(t, err)
require.Equal(t, &UserControlStreamDry{
StreamID: 35534,
}, dec)
}
func FuzzReader(f *testing.F) {
for _, ca := range readWriterCases {
f.Add(ca.enc)