Skip to content

Commit 8f31879

Browse files
committed
rtmp: support reading AV1, VP9, H265, Opus, G711, LPCM and multiple tracks at once (#4168) (#4321)
1 parent e0f4748 commit 8f31879

File tree

11 files changed

+1470
-250
lines changed

11 files changed

+1470
-250
lines changed

docs/2-usage/3-read.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Live streams can be read from the server with the following protocols and codecs
99
| [SRT](#srt) | | H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video | Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3 |
1010
| [WebRTC](#webrtc) | WHEP | AV1, VP9, VP8, H265, H264 | Opus, G722, G711 (PCMA, PCMU) |
1111
| [RTSP](#rtsp) | UDP, UDP-Multicast, TCP, RTSPS | AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, M-JPEG and any RTP-compatible codec | Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G726, G722, G711 (PCMA, PCMU), LPCM and any RTP-compatible codec |
12-
| [RTMP](#rtmp) | RTMP, RTMPS, Enhanced RTMP | H264 | MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3) |
12+
| [RTMP](#rtmp) | RTMP, RTMPS, Enhanced RTMP | AV1, VP9, H265, H264 | Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G711 (PCMA, PCMU), LPCM |
1313
| [HLS](#hls) | Low-Latency HLS, MP4-based HLS, legacy HLS | AV1, VP9, H265, H264 | Opus, MPEG-4 Audio (AAC) |
1414

1515
We provide instructions for reading with the following software:
@@ -182,6 +182,18 @@ The RTSP protocol supports several underlying transport protocols, each with its
182182
ffmpeg -rtsp_transport tcp -i rtsp://localhost:8554/mystream -c copy output.mp4
183183
```
184184

185+
FFmpeg can also read a stream with RTMP:
186+
187+
```sh
188+
ffmpeg -i rtmp://localhost:8554/mystream -c copy output.mp4
189+
```
190+
191+
In order to read AV1, VP9, H265, Opus, AC3 tracks and in order to read multiple tracks, the `-rtmp_enhanced_codecs` flag must be present:
192+
193+
```sh
194+
ffmpeg -rtmp_enhanced_codecs ac-3,av01,avc1,ec-3,fLaC,hvc1,.mp3,mp4a,Opus,vp09 -i rtmp://localhost:8554/mystream -c copy output.mp4
195+
```
196+
185197
### GStreamer
186198

187199
GStreamer can read a stream from the server in several ways (RTSP, RTMP, HLS, WebRTC with WHEP, SRT). The recommended one consists in reading with [RTSP](#rtsp):

internal/protocols/rtmp/client.go

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,25 @@ import (
1717
"github.com/google/uuid"
1818
)
1919

20+
// RTMP 1.0 spec, section 7.2.1.1
21+
const (
22+
supportSndNone = 0x0001
23+
supportSndMP3 = 0x0004
24+
supportSndG711A = 0x0080
25+
supportSndG711U = 0x0100
26+
supportSndAAV = 0x0400
27+
28+
supportVidH264 = 0x0080
29+
30+
encodingAMF0 = 0
31+
)
32+
2033
var errAuth = errors.New("auth")
2134

35+
func fourCCToString(c message.FourCC) string {
36+
return string([]byte{byte(c >> 24), byte(c >> 16), byte(c >> 8), byte(c)})
37+
}
38+
2239
func resultIsOK1(res *message.CommandAMF0) bool {
2340
if len(res.Arguments) < 2 {
2441
return false
@@ -252,15 +269,45 @@ func (c *Client) initialize3() error {
252269
{Key: "app", Value: app},
253270
{Key: "flashVer", Value: "LNX 9,0,124,2"},
254271
{Key: "tcUrl", Value: tcURL},
272+
{Key: "objectEncoding", Value: float64(encodingAMF0)},
255273
}
256274

257275
if !c.Publish {
258276
connectArg = append(connectArg,
259-
amf0.ObjectEntry{Key: "fpad", Value: false},
260-
amf0.ObjectEntry{Key: "capabilities", Value: float64(15)},
261-
amf0.ObjectEntry{Key: "audioCodecs", Value: float64(4071)},
262-
amf0.ObjectEntry{Key: "videoCodecs", Value: float64(252)},
263-
amf0.ObjectEntry{Key: "videoFunction", Value: float64(1)},
277+
amf0.ObjectEntry{
278+
Key: "fpad",
279+
Value: false,
280+
},
281+
amf0.ObjectEntry{
282+
Key: "capabilities",
283+
Value: float64(15),
284+
},
285+
amf0.ObjectEntry{
286+
Key: "audioCodecs",
287+
Value: float64(
288+
supportSndNone | supportSndMP3 | supportSndG711A | supportSndG711U | supportSndAAV),
289+
},
290+
amf0.ObjectEntry{
291+
Key: "videoCodecs",
292+
Value: float64(supportVidH264),
293+
},
294+
amf0.ObjectEntry{
295+
Key: "videoFunction",
296+
Value: float64(0),
297+
},
298+
amf0.ObjectEntry{
299+
Key: "fourCcList",
300+
Value: amf0.StrictArray{
301+
fourCCToString(message.FourCCAV1),
302+
fourCCToString(message.FourCCVP9),
303+
fourCCToString(message.FourCCHEVC),
304+
fourCCToString(message.FourCCAVC),
305+
fourCCToString(message.FourCCOpus),
306+
fourCCToString(message.FourCCAC3),
307+
fourCCToString(message.FourCCMP4A),
308+
fourCCToString(message.FourCCMP3),
309+
},
310+
},
264311
)
265312
}
266313

internal/protocols/rtmp/client_test.go

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ func TestClient(t *testing.T) {
6666
require.NoError(t, err2)
6767

6868
switch authState {
69-
case 0:
70-
require.Equal(t, &message.CommandAMF0{
69+
case 0: //nolint:dupl
70+
require.Equal(t, &message.CommandAMF0{ //nolint:dupl
7171
ChunkStreamID: 3,
7272
Name: "connect",
7373
CommandID: 1,
@@ -76,17 +76,28 @@ func TestClient(t *testing.T) {
7676
{Key: "app", Value: "stream"},
7777
{Key: "flashVer", Value: "LNX 9,0,124,2"},
7878
{Key: "tcUrl", Value: "rtmp://127.0.0.1:9121/stream"},
79+
{Key: "objectEncoding", Value: float64(0)},
7980
{Key: "fpad", Value: false},
8081
{Key: "capabilities", Value: float64(15)},
81-
{Key: "audioCodecs", Value: float64(4071)},
82-
{Key: "videoCodecs", Value: float64(252)},
83-
{Key: "videoFunction", Value: float64(1)},
82+
{Key: "audioCodecs", Value: float64(1413)},
83+
{Key: "videoCodecs", Value: float64(128)},
84+
{Key: "videoFunction", Value: float64(0)},
85+
{Key: "fourCcList", Value: amf0.StrictArray{
86+
"av01",
87+
"vp09",
88+
"hvc1",
89+
"avc1",
90+
"Opus",
91+
"ac-3",
92+
"mp4a",
93+
".mp3",
94+
}},
8495
},
8596
},
8697
}, msg)
8798

88-
case 1:
89-
require.Equal(t, &message.CommandAMF0{
99+
case 1: //nolint:dupl
100+
require.Equal(t, &message.CommandAMF0{ //nolint:dupl
90101
ChunkStreamID: 3,
91102
Name: "connect",
92103
CommandID: 1,
@@ -95,11 +106,22 @@ func TestClient(t *testing.T) {
95106
{Key: "app", Value: "stream?authmod=adobe&user=myuser"},
96107
{Key: "flashVer", Value: "LNX 9,0,124,2"},
97108
{Key: "tcUrl", Value: "rtmp://127.0.0.1:9121/stream?authmod=adobe&user=myuser"},
109+
{Key: "objectEncoding", Value: float64(0)},
98110
{Key: "fpad", Value: false},
99111
{Key: "capabilities", Value: float64(15)},
100-
{Key: "audioCodecs", Value: float64(4071)},
101-
{Key: "videoCodecs", Value: float64(252)},
102-
{Key: "videoFunction", Value: float64(1)},
112+
{Key: "audioCodecs", Value: float64(1413)},
113+
{Key: "videoCodecs", Value: float64(128)},
114+
{Key: "videoFunction", Value: float64(0)},
115+
{Key: "fourCcList", Value: amf0.StrictArray{
116+
"av01",
117+
"vp09",
118+
"hvc1",
119+
"avc1",
120+
"Opus",
121+
"ac-3",
122+
"mp4a",
123+
".mp3",
124+
}},
103125
},
104126
},
105127
}, msg)
@@ -127,11 +149,22 @@ func TestClient(t *testing.T) {
127149
Value: "rtmp://127.0.0.1:9121/stream?authmod=adobe&user=myuser&challenge=" +
128150
clientChallenge + "&response=" + response,
129151
},
152+
{Key: "objectEncoding", Value: float64(0)},
130153
{Key: "fpad", Value: false},
131154
{Key: "capabilities", Value: float64(15)},
132-
{Key: "audioCodecs", Value: float64(4071)},
133-
{Key: "videoCodecs", Value: float64(252)},
134-
{Key: "videoFunction", Value: float64(1)},
155+
{Key: "audioCodecs", Value: float64(1413)},
156+
{Key: "videoCodecs", Value: float64(128)},
157+
{Key: "videoFunction", Value: float64(0)},
158+
{Key: "fourCcList", Value: amf0.StrictArray{
159+
"av01",
160+
"vp09",
161+
"hvc1",
162+
"avc1",
163+
"Opus",
164+
"ac-3",
165+
"mp4a",
166+
".mp3",
167+
}},
135168
},
136169
},
137170
}, msg)
@@ -140,7 +173,7 @@ func TestClient(t *testing.T) {
140173
case "read", "read nginx rtmp":
141174
msg, err2 = mrw.Read()
142175
require.NoError(t, err2)
143-
require.Equal(t, &message.CommandAMF0{
176+
require.Equal(t, &message.CommandAMF0{ //nolint:dupl
144177
ChunkStreamID: 3,
145178
Name: "connect",
146179
CommandID: 1,
@@ -149,11 +182,22 @@ func TestClient(t *testing.T) {
149182
{Key: "app", Value: "stream"},
150183
{Key: "flashVer", Value: "LNX 9,0,124,2"},
151184
{Key: "tcUrl", Value: "rtmp://127.0.0.1:9121/stream"},
185+
{Key: "objectEncoding", Value: float64(0)},
152186
{Key: "fpad", Value: false},
153187
{Key: "capabilities", Value: float64(15)},
154-
{Key: "audioCodecs", Value: float64(4071)},
155-
{Key: "videoCodecs", Value: float64(252)},
156-
{Key: "videoFunction", Value: float64(1)},
188+
{Key: "audioCodecs", Value: float64(1413)},
189+
{Key: "videoCodecs", Value: float64(128)},
190+
{Key: "videoFunction", Value: float64(0)},
191+
{Key: "fourCcList", Value: amf0.StrictArray{
192+
"av01",
193+
"vp09",
194+
"hvc1",
195+
"avc1",
196+
"Opus",
197+
"ac-3",
198+
"mp4a",
199+
".mp3",
200+
}},
157201
},
158202
},
159203
}, msg)
@@ -170,6 +214,7 @@ func TestClient(t *testing.T) {
170214
{Key: "app", Value: "stream"},
171215
{Key: "flashVer", Value: "LNX 9,0,124,2"},
172216
{Key: "tcUrl", Value: "rtmp://127.0.0.1:9121/stream"},
217+
{Key: "objectEncoding", Value: float64(0)},
173218
},
174219
},
175220
}, msg)
@@ -408,11 +453,11 @@ func TestClient(t *testing.T) {
408453
switch ca {
409454
case "read", "read nginx rtmp":
410455
require.Equal(t, uint64(3421), conn.BytesReceived())
411-
require.Equal(t, uint64(3409), conn.BytesSent())
456+
require.Equal(t, uint64(0xdb3), conn.BytesSent())
412457

413458
case "publish":
414459
require.Equal(t, uint64(3427), conn.BytesReceived())
415-
require.Equal(t, uint64(0xd27), conn.BytesSent())
460+
require.Equal(t, uint64(0xd40), conn.BytesSent())
416461
}
417462

418463
<-done

internal/protocols/rtmp/conn.go

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package rtmp
22

33
import (
4-
"io"
5-
6-
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/bytecounter"
74
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/message"
85
)
96

@@ -14,33 +11,3 @@ type Conn interface {
1411
Read() (message.Message, error)
1512
Write(msg message.Message) error
1613
}
17-
18-
type dummyConn struct {
19-
rw io.ReadWriter
20-
21-
bc *bytecounter.ReadWriter
22-
mrw *message.ReadWriter
23-
}
24-
25-
func (c *dummyConn) initialize() {
26-
c.bc = bytecounter.NewReadWriter(c.rw)
27-
c.mrw = message.NewReadWriter(c.bc, c.bc, false)
28-
}
29-
30-
// BytesReceived returns the number of bytes received.
31-
func (c *dummyConn) BytesReceived() uint64 {
32-
return c.bc.Reader.Count()
33-
}
34-
35-
// BytesSent returns the number of bytes sent.
36-
func (c *dummyConn) BytesSent() uint64 {
37-
return c.bc.Writer.Count()
38-
}
39-
40-
func (c *dummyConn) Read() (message.Message, error) {
41-
return c.mrw.Read()
42-
}
43-
44-
func (c *dummyConn) Write(msg message.Message) error {
45-
return c.mrw.Write(msg)
46-
}

0 commit comments

Comments
 (0)