Browse Source

rtmp: add new AMF0 encoder and encoder (#3069)

This improves performance, security and removes a dependency.
pull/3071/head
Alessandro Ros 1 year ago committed by GitHub
parent
commit
da7c5744b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      README.md
  2. 1
      go.mod
  3. 2
      go.sum
  4. 171
      internal/protocols/rtmp/amf0/marshal.go
  5. 17
      internal/protocols/rtmp/amf0/marshal_test.go
  6. 53
      internal/protocols/rtmp/amf0/object.go
  7. 28
      internal/protocols/rtmp/amf0/object_test.go
  8. 2
      internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/110121ffaa6941a6
  9. 2
      internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/118a6dec0931d635
  10. 2
      internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/13ba6ce8ecfbc991
  11. 2
      internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/399a2041db7e1ff9
  12. 2
      internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/582528ddfad69eb5
  13. 2
      internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/6df12bd1a096b953
  14. 2
      internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/6f9c29532ccb80bf
  15. 2
      internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/7fbe967ee430d9f4
  16. 2
      internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/97dc7172b48e6ffd
  17. 2
      internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/aba8f2fdaa5caedb
  18. 2
      internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/b9ef082bd4d297b2
  19. 2
      internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/df015416c2cf2dc6
  20. 2
      internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/e93e775e4de86c93
  21. 2
      internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/f3b49d384cddc291
  22. 2
      internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/fb46839f39edbbd8
  23. 189
      internal/protocols/rtmp/amf0/unmarshal.go
  24. 313
      internal/protocols/rtmp/amf0/unmarshal_test.go
  25. 83
      internal/protocols/rtmp/conn.go
  26. 86
      internal/protocols/rtmp/conn_test.go
  27. 6
      internal/protocols/rtmp/message/acknowledge.go
  28. 6
      internal/protocols/rtmp/message/audio.go
  29. 26
      internal/protocols/rtmp/message/command_amf0.go
  30. 73
      internal/protocols/rtmp/message/command_amf0_test.go
  31. 18
      internal/protocols/rtmp/message/data_amf0.go
  32. 6
      internal/protocols/rtmp/message/extended_coded_frames.go
  33. 6
      internal/protocols/rtmp/message/extended_frames_x.go
  34. 6
      internal/protocols/rtmp/message/extended_metadata.go
  35. 6
      internal/protocols/rtmp/message/extended_mpeg2ts_sequence_start.go
  36. 6
      internal/protocols/rtmp/message/extended_sequence_end.go
  37. 6
      internal/protocols/rtmp/message/extended_sequence_start.go
  38. 4
      internal/protocols/rtmp/message/message.go
  39. 2
      internal/protocols/rtmp/message/reader.go
  40. 8
      internal/protocols/rtmp/message/reader_test.go
  41. 6
      internal/protocols/rtmp/message/set_chunk_size.go
  42. 6
      internal/protocols/rtmp/message/set_peer_bandwidth.go
  43. 6
      internal/protocols/rtmp/message/set_window_ack_size.go
  44. 6
      internal/protocols/rtmp/message/user_control_ping_request.go
  45. 6
      internal/protocols/rtmp/message/user_control_ping_response.go
  46. 6
      internal/protocols/rtmp/message/user_control_set_buffer_length.go
  47. 6
      internal/protocols/rtmp/message/user_control_stream_begin.go
  48. 6
      internal/protocols/rtmp/message/user_control_stream_dry.go
  49. 6
      internal/protocols/rtmp/message/user_control_stream_eof.go
  50. 6
      internal/protocols/rtmp/message/user_control_stream_is_recorded.go
  51. 6
      internal/protocols/rtmp/message/video.go
  52. 2
      internal/protocols/rtmp/message/writer.go
  53. 14
      internal/protocols/rtmp/reader.go
  54. 212
      internal/protocols/rtmp/reader_test.go
  55. 20
      internal/protocols/rtmp/writer.go
  56. 12
      internal/protocols/rtmp/writer_test.go

2
README.md

@ -1970,6 +1970,7 @@ All the code in this repository is released under the [MIT License](LICENSE). Co @@ -1970,6 +1970,7 @@ All the code in this repository is released under the [MIT License](LICENSE). Co
|[HLS specifications](https://github.com/bluenviron/gohlslib#specifications)|HLS|
|[RTMP](https://rtmp.veriskope.com/pdf/rtmp_specification_1.0.pdf)|RTMP|
|[Enhanced RTMP](https://raw.githubusercontent.com/veovera/enhanced-rtmp/main/enhanced-rtmp-v1.pdf)|RTMP|
|[Action Message Format](https://rtmp.veriskope.com/pdf/amf0-file-format-specification.pdf)|RTMP|
|[WebRTC: Real-Time Communication in Browsers](https://www.w3.org/TR/webrtc/)|WebRTC|
|[WebRTC HTTP Ingestion Protocol (WHIP)](https://datatracker.ietf.org/doc/draft-ietf-wish-whip/)|WebRTC|
|[WebRTC HTTP Egress Protocol (WHEP)](https://datatracker.ietf.org/doc/draft-murillo-whep/)|WebRTC|
@ -1987,7 +1988,6 @@ All the code in this repository is released under the [MIT License](LICENSE). Co @@ -1987,7 +1988,6 @@ All the code in this repository is released under the [MIT License](LICENSE). Co
* [pion/sdp (SDP library used internally)](https://github.com/pion/sdp)
* [pion/rtp (RTP library used internally)](https://github.com/pion/rtp)
* [pion/rtcp (RTCP library used internally)](https://github.com/pion/rtcp)
* [notedit/rtmp (RTMP library used internally)](https://github.com/notedit/rtmp)
* [go-astits (MPEG-TS library used internally)](https://github.com/asticode/go-astits)
* [go-mp4 (MP4 library used internally)](https://github.com/abema/go-mp4)
* [hls.js (browser-side HLS library used internally)](https://github.com/video-dev/hls.js)

1
go.mod

@ -17,7 +17,6 @@ require ( @@ -17,7 +17,6 @@ require (
github.com/gorilla/websocket v1.5.1
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/matthewhartstonge/argon2 v1.0.0
github.com/notedit/rtmp v0.0.2
github.com/pion/ice/v2 v2.3.11
github.com/pion/interceptor v0.1.25
github.com/pion/logging v0.2.2

2
go.sum

@ -107,8 +107,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w @@ -107,8 +107,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/notedit/rtmp v0.0.2 h1:5+to4yezKATiJgnrcETu9LbV5G/QsWkOV9Ts2M/p33w=
github.com/notedit/rtmp v0.0.2/go.mod h1:vzuE21rowz+lT1NGsWbreIvYulgBpCGnQyeTyFblUHc=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=

171
internal/protocols/rtmp/amf0/marshal.go

@ -0,0 +1,171 @@ @@ -0,0 +1,171 @@
// Package amf0 contains an AMF0 marshaler and unmarshaler.
package amf0
import (
"fmt"
"math"
)
// Marshal encodes AMF0 data.
func Marshal(data []interface{}) ([]byte, error) {
n, err := marshalSize(data)
if err != nil {
return nil, err
}
buf := make([]byte, n)
n = 0
for _, item := range data {
n += marshalItem(item, buf[n:])
}
return buf, nil
}
func marshalSize(data []interface{}) (int, error) {
n := 0
for _, item := range data {
in, err := marshalSizeItem(item)
if err != nil {
return 0, err
}
n += in
}
return n, nil
}
func marshalSizeItem(item interface{}) (int, error) {
switch item := item.(type) {
case float64:
return 9, nil
case bool:
return 2, nil
case string:
return 3 + len(item), nil
case ECMAArray:
n := 5
for _, entry := range item {
en, err := marshalSizeItem(entry.Value)
if err != nil {
return 0, err
}
n += 2 + len(entry.Key) + en
}
n += 3
return n, nil
case Object:
n := 1
for _, entry := range item {
en, err := marshalSizeItem(entry.Value)
if err != nil {
return 0, err
}
n += 2 + len(entry.Key) + en
}
n += 3
return n, nil
case nil:
return 1, nil
default:
return 0, fmt.Errorf("unsupported data type: %T", item)
}
}
func marshalItem(item interface{}, buf []byte) int {
switch item := item.(type) {
case float64:
v := math.Float64bits(item)
buf[0] = markerNumber
buf[1] = byte(v >> 56)
buf[2] = byte(v >> 48)
buf[3] = byte(v >> 40)
buf[4] = byte(v >> 32)
buf[5] = byte(v >> 24)
buf[6] = byte(v >> 16)
buf[7] = byte(v >> 8)
buf[8] = byte(v)
return 9
case bool:
buf[0] = markerBoolean
if item {
buf[1] = 1
}
return 2
case string:
le := len(item)
buf[0] = markerString
buf[1] = byte(le >> 8)
buf[2] = byte(le)
copy(buf[3:], item)
return 3 + le
case ECMAArray:
le := len(item)
buf[0] = markerECMAArray
buf[1] = byte(le >> 24)
buf[2] = byte(le >> 16)
buf[3] = byte(le >> 8)
buf[4] = byte(le)
n := 5
for _, entry := range item {
le := len(entry.Key)
buf[n] = byte(le >> 8)
buf[n+1] = byte(le)
copy(buf[n+2:], entry.Key)
n += 2 + le
n += marshalItem(entry.Value, buf[n:])
}
buf[n] = 0
buf[n+1] = 0
buf[n+2] = markerObjectEnd
return n + 3
case Object:
buf[0] = markerObject
n := 1
for _, entry := range item {
le := len(entry.Key)
buf[n] = byte(le >> 8)
buf[n+1] = byte(le)
copy(buf[n+2:], entry.Key)
n += 2 + le
n += marshalItem(entry.Value, buf[n:])
}
buf[n] = 0
buf[n+1] = 0
buf[n+2] = markerObjectEnd
return n + 3
default:
buf[0] = markerNull
return 1
}
}

17
internal/protocols/rtmp/amf0/marshal_test.go

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
package amf0
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestMarshal(t *testing.T) {
for _, ca := range cases {
t.Run(ca.name, func(t *testing.T) {
enc, err := Marshal(ca.dec)
require.NoError(t, err)
require.Equal(t, ca.enc, enc)
})
}
}

53
internal/protocols/rtmp/amf0/object.go

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
package amf0
// ObjectEntry is an entry of Object.
type ObjectEntry struct {
Key string
Value interface{}
}
// Object is an AMF0 object.
type Object []ObjectEntry
// ECMAArray is an AMF0 ECMA Array.
type ECMAArray Object
// Get returns the value corresponding to key.
func (o Object) Get(key string) (interface{}, bool) {
for _, item := range o {
if item.Key == key {
return item.Value, true
}
}
return nil, false
}
// GetString returns the value corresponding to key, only if that is a string.
func (o Object) GetString(key string) (string, bool) {
v, ok := o.Get(key)
if !ok {
return "", false
}
v2, ok2 := v.(string)
if !ok2 {
return "", false
}
return v2, ok2
}
// GetFloat64 returns the value corresponding to key, only if that is a float64.
func (o Object) GetFloat64(key string) (float64, bool) {
v, ok := o.Get(key)
if !ok {
return 0, false
}
v2, ok2 := v.(float64)
if !ok2 {
return 0, false
}
return v2, ok2
}

28
internal/protocols/rtmp/amf0/object_test.go

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
package amf0
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestObjectGet(t *testing.T) {
o := Object{{Key: "testme", Value: "ok"}}
v, ok := o.Get("testme")
require.Equal(t, true, ok)
require.Equal(t, "ok", v)
}
func TestObjectGetString(t *testing.T) {
o := Object{{Key: "testme", Value: "ok"}}
v, ok := o.GetString("testme")
require.Equal(t, true, ok)
require.Equal(t, "ok", v)
}
func TestObjectGetFloat64(t *testing.T) {
o := Object{{Key: "testme", Value: float64(123)}}
v, ok := o.GetFloat64("testme")
require.Equal(t, true, ok)
require.Equal(t, float64(123), v)
}

2
internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/110121ffaa6941a6 vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x0200")

2
internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/118a6dec0931d635 vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\b000000")

2
internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/13ba6ce8ecfbc991 vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\b0000\x00\x000")

2
internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/399a2041db7e1ff9 vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x000")

2
internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/582528ddfad69eb5 vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0")

2
internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/6df12bd1a096b953 vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x03\x00\x0200")

2
internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/6f9c29532ccb80bf vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\b")

2
internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/7fbe967ee430d9f4 vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x0300")

2
internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/97dc7172b48e6ffd vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x01")

2
internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/aba8f2fdaa5caedb vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x03\x00\x000")

2
internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/b9ef082bd4d297b2 vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\b0000\x00\x00")

2
internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/df015416c2cf2dc6 vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\b0000")

2
internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/e93e775e4de86c93 vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\b0000\x00\x06000000")

2
internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/f3b49d384cddc291 vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x03")

2
internal/protocols/rtmp/amf0/testdata/fuzz/FuzzUnmarshal/fb46839f39edbbd8 vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x03\x00\x00")

189
internal/protocols/rtmp/amf0/unmarshal.go

@ -0,0 +1,189 @@ @@ -0,0 +1,189 @@
package amf0
import (
"errors"
"fmt"
"math"
)
const (
markerNumber = 0x00
markerBoolean = 0x01
markerString = 0x02
markerObject = 0x03
markerMovieclip = 0x04
markerNull = 0x05
markerUndefined = 0x06
markerReference = 0x07
markerECMAArray = 0x08
markerObjectEnd = 0x09
markerStrictArray = 0x0A
markerDate = 0x0B
markerLongString = 0x0C
markerUnsupported = 0x0D
markerRecordset = 0x0E
markerXMLDocument = 0xF
markerTypedObject = 0x10
)
var errBufferTooShort = errors.New("buffer is too short")
// Unmarshal decodes AMF0 data.
func Unmarshal(buf []byte) ([]interface{}, error) {
var out []interface{}
for len(buf) != 0 {
var item interface{}
var err error
item, buf, err = unmarshal(buf)
if err != nil {
return nil, err
}
out = append(out, item)
}
return out, nil
}
func unmarshal(buf []byte) (interface{}, []byte, error) {
if len(buf) < 1 {
return nil, nil, errBufferTooShort
}
var marker byte
marker, buf = buf[0], buf[1:]
switch marker {
case markerNumber:
if len(buf) < 8 {
return nil, nil, errBufferTooShort
}
return math.Float64frombits(uint64(buf[0])<<56 | uint64(buf[1])<<48 | uint64(buf[2])<<40 | uint64(buf[3])<<32 |
uint64(buf[4])<<24 | uint64(buf[5])<<16 | uint64(buf[6])<<8 | uint64(buf[7])), buf[8:], nil
case markerBoolean:
if len(buf) < 1 {
return nil, nil, errBufferTooShort
}
return (buf[0] != 0), buf[1:], nil
case markerString:
if len(buf) < 2 {
return nil, nil, errBufferTooShort
}
le := uint16(buf[0])<<8 | uint16(buf[1])
buf = buf[2:]
if len(buf) < int(le) {
return nil, nil, errBufferTooShort
}
return string(buf[:le]), buf[le:], nil
case markerECMAArray:
if len(buf) < 4 {
return nil, nil, errBufferTooShort
}
buf = buf[4:]
out := ECMAArray{}
for {
if len(buf) < 2 {
return nil, nil, errBufferTooShort
}
keyLen := uint16(buf[0])<<8 | uint16(buf[1])
buf = buf[2:]
if keyLen == 0 {
break
}
if len(buf) < int(keyLen) {
return nil, nil, errBufferTooShort
}
key := string(buf[:keyLen])
buf = buf[keyLen:]
var value interface{}
var err error
value, buf, err = unmarshal(buf)
if err != nil {
return nil, nil, err
}
out = append(out, ObjectEntry{
Key: key,
Value: value,
})
}
if len(buf) < 1 {
return nil, nil, errBufferTooShort
}
if buf[0] != markerObjectEnd {
return nil, nil, fmt.Errorf("object end not found")
}
return out, buf[1:], nil
case markerObject:
out := Object{}
for {
if len(buf) < 2 {
return nil, nil, errBufferTooShort
}
keyLen := uint16(buf[0])<<8 | uint16(buf[1])
buf = buf[2:]
if keyLen == 0 {
break
}
if len(buf) < int(keyLen) {
return nil, nil, errBufferTooShort
}
key := string(buf[:keyLen])
buf = buf[keyLen:]
var value interface{}
var err error
value, buf, err = unmarshal(buf)
if err != nil {
return nil, nil, err
}
out = append(out, ObjectEntry{
Key: key,
Value: value,
})
}
if len(buf) < 1 {
return nil, nil, errBufferTooShort
}
if buf[0] != markerObjectEnd {
return nil, nil, fmt.Errorf("object end not found")
}
return out, buf[1:], nil
case markerNull:
return nil, buf, nil
default:
return nil, nil, fmt.Errorf("unsupported marker 0x%.2x", marker)
}
}

313
internal/protocols/rtmp/amf0/unmarshal_test.go

@ -0,0 +1,313 @@ @@ -0,0 +1,313 @@
package amf0
import (
"testing"
"github.com/stretchr/testify/require"
)
var cases = []struct {
name string
enc []byte
dec []interface{}
}{
{
"on metadata",
[]byte{
0x02, 0x00, 0x0d, 0x40, 0x73, 0x65, 0x74, 0x44,
0x61, 0x74, 0x61, 0x46, 0x72, 0x61, 0x6d, 0x65,
0x02, 0x00, 0x0a, 0x6f, 0x6e, 0x4d, 0x65, 0x74,
0x61, 0x44, 0x61, 0x74, 0x61, 0x08, 0x00, 0x00,
0x00, 0x0d, 0x00, 0x08, 0x64, 0x75, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x77,
0x69, 0x64, 0x74, 0x68, 0x00, 0x40, 0x94, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x68,
0x65, 0x69, 0x67, 0x68, 0x74, 0x00, 0x40, 0x86,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d,
0x76, 0x69, 0x64, 0x65, 0x6f, 0x64, 0x61, 0x74,
0x61, 0x72, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09,
0x66, 0x72, 0x61, 0x6d, 0x65, 0x72, 0x61, 0x74,
0x65, 0x00, 0x40, 0x4d, 0xf8, 0x53, 0xe2, 0x55,
0x6b, 0x28, 0x00, 0x0c, 0x76, 0x69, 0x64, 0x65,
0x6f, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x69, 0x64,
0x00, 0x40, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0d, 0x61, 0x75, 0x64, 0x69, 0x6f,
0x64, 0x61, 0x74, 0x61, 0x72, 0x61, 0x74, 0x65,
0x00, 0x40, 0x57, 0x58, 0x90, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0f, 0x61, 0x75, 0x64, 0x69, 0x6f,
0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x61,
0x74, 0x65, 0x00, 0x40, 0xe7, 0x70, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x75, 0x64,
0x69, 0x6f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65,
0x73, 0x69, 0x7a, 0x65, 0x00, 0x40, 0x30, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x73,
0x74, 0x65, 0x72, 0x65, 0x6f, 0x01, 0x01, 0x00,
0x0c, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x63, 0x6f,
0x64, 0x65, 0x63, 0x69, 0x64, 0x00, 0x40, 0x24,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x02,
0x00, 0x0d, 0x4c, 0x61, 0x76, 0x66, 0x35, 0x36,
0x2e, 0x33, 0x36, 0x2e, 0x31, 0x30, 0x30, 0x00,
0x08, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x69, 0x7a,
0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x09,
},
[]interface{}{
"@setDataFrame",
"onMetaData",
ECMAArray{
{
Key: "duration",
Value: float64(0),
},
{
Key: "width",
Value: float64(1280),
},
{
Key: "height",
Value: float64(720),
},
{
Key: "videodatarate",
Value: float64(0),
},
{
Key: "framerate",
Value: float64(59.94005994005994),
},
{
Key: "videocodecid",
Value: float64(7),
},
{
Key: "audiodatarate",
Value: float64(93.3837890625),
},
{
Key: "audiosamplerate",
Value: float64(48000),
},
{
Key: "audiosamplesize",
Value: float64(16),
},
{
Key: "stereo",
Value: true,
},
{
Key: "audiocodecid",
Value: float64(10),
},
{
Key: "encoder",
Value: "Lavf56.36.100",
},
{
Key: "filesize",
Value: float64(0),
},
},
},
},
{
"connect",
[]byte{
0x02, 0x00, 0x07, 0x63, 0x6f, 0x6e, 0x6e, 0x65,
0x63, 0x74, 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x61, 0x70,
0x70, 0x02, 0x00, 0x02, 0x61, 0x70, 0x00, 0x04,
0x74, 0x79, 0x70, 0x65, 0x02, 0x00, 0x0a, 0x6e,
0x6f, 0x6e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74,
0x65, 0x00, 0x08, 0x66, 0x6c, 0x61, 0x73, 0x68,
0x56, 0x65, 0x72, 0x02, 0x00, 0x24, 0x46, 0x4d,
0x4c, 0x45, 0x2f, 0x33, 0x2e, 0x30, 0x20, 0x28,
0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62,
0x6c, 0x65, 0x3b, 0x20, 0x4c, 0x61, 0x76, 0x66,
0x35, 0x36, 0x2e, 0x31, 0x35, 0x2e, 0x31, 0x30,
0x32, 0x29, 0x00, 0x05, 0x74, 0x63, 0x55, 0x72,
0x6c, 0x02, 0x00, 0x1c, 0x72, 0x74, 0x6d, 0x70,
0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x31,
0x36, 0x38, 0x2e, 0x31, 0x2e, 0x32, 0x33, 0x33,
0x3a, 0x31, 0x39, 0x33, 0x35, 0x2f, 0x61, 0x70,
0x00, 0x00, 0x09,
},
[]interface{}{
"connect",
float64(1),
Object{
{Key: "app", Value: "ap"},
{Key: "type", Value: "nonprivate"},
{Key: "flashVer", Value: "FMLE/3.0 (compatible; Lavf56.15.102)"},
{Key: "tcUrl", Value: "rtmp://192.168.1.233:1935/ap"},
},
},
},
{
"srs",
[]byte{
0x02, 0x00, 0x07, 0x5f, 0x72, 0x65, 0x73, 0x75,
0x6c, 0x74, 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x06, 0x66, 0x6d,
0x73, 0x56, 0x65, 0x72, 0x02, 0x00, 0x0d, 0x46,
0x4d, 0x53, 0x2f, 0x33, 0x2c, 0x35, 0x2c, 0x33,
0x2c, 0x38, 0x38, 0x38, 0x00, 0x0c, 0x63, 0x61,
0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69,
0x65, 0x73, 0x00, 0x40, 0x5f, 0xc0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x6d, 0x6f, 0x64,
0x65, 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x09, 0x03, 0x00, 0x05,
0x6c, 0x65, 0x76, 0x65, 0x6c, 0x02, 0x00, 0x06,
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x00, 0x04,
0x63, 0x6f, 0x64, 0x65, 0x02, 0x00, 0x1d, 0x4e,
0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e,
0x6e, 0x65, 0x63, 0x74, 0x2e, 0x53, 0x75, 0x63,
0x63, 0x65, 0x73, 0x73, 0x00, 0x0b, 0x64, 0x65,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
0x6e, 0x02, 0x00, 0x14, 0x43, 0x6f, 0x6e, 0x6e,
0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x73,
0x75, 0x63, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64,
0x00, 0x0e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74,
0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x04, 0x64, 0x61, 0x74, 0x61, 0x08,
0x00, 0x00, 0x00, 0x0f, 0x00, 0x07, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x02, 0x00, 0x09,
0x33, 0x2c, 0x35, 0x2c, 0x33, 0x2c, 0x38, 0x38,
0x38, 0x00, 0x07, 0x73, 0x72, 0x73, 0x5f, 0x73,
0x69, 0x67, 0x02, 0x00, 0x03, 0x53, 0x52, 0x53,
0x00, 0x0a, 0x73, 0x72, 0x73, 0x5f, 0x73, 0x65,
0x72, 0x76, 0x65, 0x72, 0x02, 0x00, 0x34, 0x53,
0x52, 0x53, 0x20, 0x31, 0x2e, 0x30, 0x2e, 0x31,
0x30, 0x20, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x77, 0x69,
0x6e, 0x6c, 0x69, 0x6e, 0x76, 0x69, 0x70, 0x2f,
0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x72,
0x74, 0x6d, 0x70, 0x2d, 0x73, 0x65, 0x72, 0x76,
0x65, 0x72, 0x29, 0x00, 0x0b, 0x73, 0x72, 0x73,
0x5f, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65,
0x02, 0x00, 0x15, 0x54, 0x68, 0x65, 0x20, 0x4d,
0x49, 0x54, 0x20, 0x4c, 0x69, 0x63, 0x65, 0x6e,
0x73, 0x65, 0x20, 0x28, 0x4d, 0x49, 0x54, 0x29,
0x00, 0x08, 0x73, 0x72, 0x73, 0x5f, 0x72, 0x6f,
0x6c, 0x65, 0x02, 0x00, 0x12, 0x6f, 0x72, 0x69,
0x67, 0x69, 0x6e, 0x2f, 0x65, 0x64, 0x67, 0x65,
0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x00,
0x07, 0x73, 0x72, 0x73, 0x5f, 0x75, 0x72, 0x6c,
0x02, 0x00, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x73,
0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x77, 0x69,
0x6e, 0x6c, 0x69, 0x6e, 0x76, 0x69, 0x70, 0x2f,
0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x72,
0x74, 0x6d, 0x70, 0x2d, 0x73, 0x65, 0x72, 0x76,
0x65, 0x72, 0x00, 0x0b, 0x73, 0x72, 0x73, 0x5f,
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x02,
0x00, 0x06, 0x31, 0x2e, 0x30, 0x2e, 0x31, 0x30,
0x00, 0x08, 0x73, 0x72, 0x73, 0x5f, 0x73, 0x69,
0x74, 0x65, 0x02, 0x00, 0x1c, 0x68, 0x74, 0x74,
0x70, 0x3a, 0x2f, 0x2f, 0x62, 0x6c, 0x6f, 0x67,
0x2e, 0x63, 0x73, 0x64, 0x6e, 0x2e, 0x6e, 0x65,
0x74, 0x2f, 0x77, 0x69, 0x6e, 0x5f, 0x6c, 0x69,
0x6e, 0x00, 0x09, 0x73, 0x72, 0x73, 0x5f, 0x65,
0x6d, 0x61, 0x69, 0x6c, 0x02, 0x00, 0x12, 0x77,
0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x40, 0x76, 0x69,
0x70, 0x2e, 0x31, 0x32, 0x36, 0x2e, 0x63, 0x6f,
0x6d, 0x00, 0x0d, 0x73, 0x72, 0x73, 0x5f, 0x63,
0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74,
0x02, 0x00, 0x1e, 0x43, 0x6f, 0x70, 0x79, 0x72,
0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29,
0x20, 0x32, 0x30, 0x31, 0x33, 0x2d, 0x32, 0x30,
0x31, 0x34, 0x20, 0x77, 0x69, 0x6e, 0x6c, 0x69,
0x6e, 0x00, 0x0b, 0x73, 0x72, 0x73, 0x5f, 0x70,
0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x02, 0x00,
0x06, 0x77, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x00,
0x0b, 0x73, 0x72, 0x73, 0x5f, 0x61, 0x75, 0x74,
0x68, 0x6f, 0x72, 0x73, 0x02, 0x00, 0x0b, 0x77,
0x65, 0x6e, 0x6a, 0x69, 0x65, 0x2e, 0x7a, 0x68,
0x61, 0x6f, 0x00, 0x0d, 0x73, 0x72, 0x73, 0x5f,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69,
0x70, 0x02, 0x00, 0x0b, 0x31, 0x37, 0x32, 0x2e,
0x31, 0x37, 0x2e, 0x30, 0x2e, 0x31, 0x30, 0x00,
0x07, 0x73, 0x72, 0x73, 0x5f, 0x70, 0x69, 0x64,
0x00, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x06, 0x73, 0x72, 0x73, 0x5f, 0x69,
0x64, 0x00, 0x40, 0x5a, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x09,
},
[]interface{}{
"_result",
float64(1),
Object{
{Key: "fmsVer", Value: "FMS/3,5,3,888"},
{Key: "capabilities", Value: float64(127)},
{Key: "mode", Value: float64(1)},
},
Object{
{Key: "level", Value: "status"},
{Key: "code", Value: "NetConnection.Connect.Success"},
{Key: "description", Value: "Connection succeeded"},
{Key: "objectEncoding", Value: float64(0)},
{
Key: "data",
Value: ECMAArray{
{Key: "version", Value: "3,5,3,888"},
{Key: "srs_sig", Value: "SRS"},
{Key: "srs_server", Value: "SRS 1.0.10 (github.com/winlinvip/simple-rtmp-server)"},
{Key: "srs_license", Value: "The MIT License (MIT)"},
{Key: "srs_role", Value: "origin/edge server"},
{Key: "srs_url", Value: "https://github.com/winlinvip/simple-rtmp-server"},
{Key: "srs_version", Value: "1.0.10"},
{Key: "srs_site", Value: "http://blog.csdn.net/win_lin"},
{Key: "srs_email", Value: "winlin@vip.126.com"},
{Key: "srs_copyright", Value: "Copyright (c) 2013-2014 winlin"},
{Key: "srs_primary", Value: "winlin"},
{Key: "srs_authors", Value: "wenjie.zhao"},
{Key: "srs_server_ip", Value: "172.17.0.10"},
{Key: "srs_pid", Value: float64(1)},
{Key: "srs_id", Value: float64(105)},
},
},
},
},
},
{
"play",
[]byte{
0x02, 0x00, 0x04, 0x70, 0x6c, 0x61, 0x79, 0x00,
0x40, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x02, 0x00, 0x01, 0x31, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
[]interface{}{
"play",
float64(3),
nil,
"1",
float64(0),
},
},
}
func TestUnmarshal(t *testing.T) {
for _, ca := range cases {
t.Run(ca.name, func(t *testing.T) {
dec, err := Unmarshal(ca.enc)
require.NoError(t, err)
require.Equal(t, ca.dec, dec)
})
}
}
func FuzzUnmarshal(f *testing.F) {
for _, ca := range cases {
f.Add(ca.enc)
}
f.Fuzz(func(t *testing.T, b []byte) {
Unmarshal(b) //nolint:errcheck
})
}

83
internal/protocols/rtmp/conn.go

@ -7,8 +7,7 @@ import ( @@ -7,8 +7,7 @@ import (
"net/url"
"strings"
"github.com/notedit/rtmp/format/flv/flvio"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/amf0"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/bytecounter"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/handshake"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/message"
@ -19,12 +18,12 @@ func resultIsOK1(res *message.CommandAMF0) bool { @@ -19,12 +18,12 @@ func resultIsOK1(res *message.CommandAMF0) bool {
return false
}
ma, ok := res.Arguments[1].(flvio.AMFMap)
ma, ok := res.Arguments[1].(amf0.Object)
if !ok {
return false
}
v, ok := ma.GetString("level")
v, ok := ma.Get("level")
if !ok {
return false
}
@ -190,15 +189,15 @@ func (c *Conn) initializeClient(u *url.URL, publish bool) error { @@ -190,15 +189,15 @@ func (c *Conn) initializeClient(u *url.URL, publish bool) error {
Name: "connect",
CommandID: 1,
Arguments: []interface{}{
flvio.AMFMap{
{K: "app", V: connectpath},
{K: "flashVer", V: "LNX 9,0,124,2"},
{K: "tcUrl", V: getTcURL(u)},
{K: "fpad", V: false},
{K: "capabilities", V: 15},
{K: "audioCodecs", V: 4071},
{K: "videoCodecs", V: 252},
{K: "videoFunction", V: 1},
amf0.Object{
{Key: "app", Value: connectpath},
{Key: "flashVer", Value: "LNX 9,0,124,2"},
{Key: "tcUrl", Value: getTcURL(u)},
{Key: "fpad", Value: false},
{Key: "capabilities", Value: float64(15)},
{Key: "audioCodecs", Value: float64(4071)},
{Key: "videoCodecs", Value: float64(252)},
{Key: "videoFunction", Value: float64(1)},
},
},
})
@ -360,7 +359,7 @@ func (c *Conn) initializeServer() (*url.URL, bool, error) { @@ -360,7 +359,7 @@ func (c *Conn) initializeServer() (*url.URL, bool, error) {
return nil, false, fmt.Errorf("invalid connect command: %+v", cmd)
}
ma, ok := cmd.Arguments[0].(flvio.AMFMap)
ma, ok := cmd.Arguments[0].(amf0.Object)
if !ok {
return nil, false, fmt.Errorf("invalid connect command: %+v", cmd)
}
@ -409,15 +408,15 @@ func (c *Conn) initializeServer() (*url.URL, bool, error) { @@ -409,15 +408,15 @@ func (c *Conn) initializeServer() (*url.URL, bool, error) {
Name: "_result",
CommandID: cmd.CommandID,
Arguments: []interface{}{
flvio.AMFMap{
{K: "fmsVer", V: "LNX 9,0,124,2"},
{K: "capabilities", V: float64(31)},
amf0.Object{
{Key: "fmsVer", Value: "LNX 9,0,124,2"},
{Key: "capabilities", Value: float64(31)},
},
flvio.AMFMap{
{K: "level", V: "status"},
{K: "code", V: "NetConnection.Connect.Success"},
{K: "description", V: "Connection succeeded."},
{K: "objectEncoding", V: oe},
amf0.Object{
{Key: "level", Value: "status"},
{Key: "code", Value: "NetConnection.Connect.Success"},
{Key: "description", Value: "Connection succeeded."},
{Key: "objectEncoding", Value: oe},
},
},
})
@ -482,10 +481,10 @@ func (c *Conn) initializeServer() (*url.URL, bool, error) { @@ -482,10 +481,10 @@ func (c *Conn) initializeServer() (*url.URL, bool, error) {
CommandID: cmd.CommandID,
Arguments: []interface{}{
nil,
flvio.AMFMap{
{K: "level", V: "status"},
{K: "code", V: "NetStream.Play.Reset"},
{K: "description", V: "play reset"},
amf0.Object{
{Key: "level", Value: "status"},
{Key: "code", Value: "NetStream.Play.Reset"},
{Key: "description", Value: "play reset"},
},
},
})
@ -500,10 +499,10 @@ func (c *Conn) initializeServer() (*url.URL, bool, error) { @@ -500,10 +499,10 @@ func (c *Conn) initializeServer() (*url.URL, bool, error) {
CommandID: cmd.CommandID,
Arguments: []interface{}{
nil,
flvio.AMFMap{
{K: "level", V: "status"},
{K: "code", V: "NetStream.Play.Start"},
{K: "description", V: "play start"},
amf0.Object{
{Key: "level", Value: "status"},
{Key: "code", Value: "NetStream.Play.Start"},
{Key: "description", Value: "play start"},
},
},
})
@ -518,10 +517,10 @@ func (c *Conn) initializeServer() (*url.URL, bool, error) { @@ -518,10 +517,10 @@ func (c *Conn) initializeServer() (*url.URL, bool, error) {
CommandID: cmd.CommandID,
Arguments: []interface{}{
nil,
flvio.AMFMap{
{K: "level", V: "status"},
{K: "code", V: "NetStream.Data.Start"},
{K: "description", V: "data start"},
amf0.Object{
{Key: "level", Value: "status"},
{Key: "code", Value: "NetStream.Data.Start"},
{Key: "description", Value: "data start"},
},
},
})
@ -536,10 +535,10 @@ func (c *Conn) initializeServer() (*url.URL, bool, error) { @@ -536,10 +535,10 @@ func (c *Conn) initializeServer() (*url.URL, bool, error) {
CommandID: cmd.CommandID,
Arguments: []interface{}{
nil,
flvio.AMFMap{
{K: "level", V: "status"},
{K: "code", V: "NetStream.Play.PublishNotify"},
{K: "description", V: "publish notify"},
amf0.Object{
{Key: "level", Value: "status"},
{Key: "code", Value: "NetStream.Play.PublishNotify"},
{Key: "description", Value: "publish notify"},
},
},
})
@ -571,10 +570,10 @@ func (c *Conn) initializeServer() (*url.URL, bool, error) { @@ -571,10 +570,10 @@ func (c *Conn) initializeServer() (*url.URL, bool, error) {
MessageStreamID: 0x1000000,
Arguments: []interface{}{
nil,
flvio.AMFMap{
{K: "level", V: "status"},
{K: "code", V: "NetStream.Publish.Start"},
{K: "description", V: "publish start"},
amf0.Object{
{Key: "level", Value: "status"},
{Key: "code", Value: "NetStream.Publish.Start"},
{Key: "description", Value: "publish start"},
},
},
})

86
internal/protocols/rtmp/conn_test.go

@ -6,9 +6,9 @@ import ( @@ -6,9 +6,9 @@ import (
"net/url"
"testing"
"github.com/notedit/rtmp/format/flv/flvio"
"github.com/stretchr/testify/require"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/amf0"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/bytecounter"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/handshake"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/message"
@ -64,15 +64,15 @@ func TestNewClientConn(t *testing.T) { @@ -64,15 +64,15 @@ func TestNewClientConn(t *testing.T) {
Name: "connect",
CommandID: 1,
Arguments: []interface{}{
flvio.AMFMap{
{K: "app", V: "stream"},
{K: "flashVer", V: "LNX 9,0,124,2"},
{K: "tcUrl", V: "rtmp://127.0.0.1:9121/stream"},
{K: "fpad", V: false},
{K: "capabilities", V: float64(15)},
{K: "audioCodecs", V: float64(4071)},
{K: "videoCodecs", V: float64(252)},
{K: "videoFunction", V: float64(1)},
amf0.Object{
{Key: "app", Value: "stream"},
{Key: "flashVer", Value: "LNX 9,0,124,2"},
{Key: "tcUrl", Value: "rtmp://127.0.0.1:9121/stream"},
{Key: "fpad", Value: false},
{Key: "capabilities", Value: float64(15)},
{Key: "audioCodecs", Value: float64(4071)},
{Key: "videoCodecs", Value: float64(252)},
{Key: "videoFunction", Value: float64(1)},
},
},
}, msg)
@ -82,15 +82,15 @@ func TestNewClientConn(t *testing.T) { @@ -82,15 +82,15 @@ func TestNewClientConn(t *testing.T) {
Name: "_result",
CommandID: 1,
Arguments: []interface{}{
flvio.AMFMap{
{K: "fmsVer", V: "LNX 9,0,124,2"},
{K: "capabilities", V: float64(31)},
amf0.Object{
{Key: "fmsVer", Value: "LNX 9,0,124,2"},
{Key: "capabilities", Value: float64(31)},
},
flvio.AMFMap{
{K: "level", V: "status"},
{K: "code", V: "NetConnection.Connect.Success"},
{K: "description", V: "Connection succeeded."},
{K: "objectEncoding", V: float64(0)},
amf0.Object{
{Key: "level", Value: "status"},
{Key: "code", Value: "NetConnection.Connect.Success"},
{Key: "description", Value: "Connection succeeded."},
{Key: "objectEncoding", Value: float64(0)},
},
},
})
@ -151,10 +151,10 @@ func TestNewClientConn(t *testing.T) { @@ -151,10 +151,10 @@ func TestNewClientConn(t *testing.T) {
}(),
Arguments: []interface{}{
nil,
flvio.AMFMap{
{K: "level", V: "status"},
{K: "code", V: "NetStream.Play.Reset"},
{K: "description", V: "play reset"},
amf0.Object{
{Key: "level", Value: "status"},
{Key: "code", Value: "NetStream.Play.Reset"},
{Key: "description", Value: "play reset"},
},
},
})
@ -228,10 +228,10 @@ func TestNewClientConn(t *testing.T) { @@ -228,10 +228,10 @@ func TestNewClientConn(t *testing.T) {
CommandID: 5,
Arguments: []interface{}{
nil,
flvio.AMFMap{
{K: "level", V: "status"},
{K: "code", V: "NetStream.Publish.Start"},
{K: "description", V: "publish start"},
amf0.Object{
{Key: "level", Value: "status"},
{Key: "code", Value: "NetStream.Publish.Start"},
{Key: "description", Value: "publish start"},
},
},
})
@ -317,15 +317,15 @@ func TestNewServerConn(t *testing.T) { @@ -317,15 +317,15 @@ func TestNewServerConn(t *testing.T) {
Name: "connect",
CommandID: 1,
Arguments: []interface{}{
flvio.AMFMap{
{K: "app", V: "/stream"},
{K: "flashVer", V: "LNX 9,0,124,2"},
{K: "tcUrl", V: tcURL},
{K: "fpad", V: false},
{K: "capabilities", V: 15},
{K: "audioCodecs", V: 4071},
{K: "videoCodecs", V: 252},
{K: "videoFunction", V: 1},
amf0.Object{
{Key: "app", Value: "/stream"},
{Key: "flashVer", Value: "LNX 9,0,124,2"},
{Key: "tcUrl", Value: tcURL},
{Key: "fpad", Value: false},
{Key: "capabilities", Value: float64(15)},
{Key: "audioCodecs", Value: float64(4071)},
{Key: "videoCodecs", Value: float64(252)},
{Key: "videoFunction", Value: float64(1)},
},
},
})
@ -357,15 +357,15 @@ func TestNewServerConn(t *testing.T) { @@ -357,15 +357,15 @@ func TestNewServerConn(t *testing.T) {
Name: "_result",
CommandID: 1,
Arguments: []interface{}{
flvio.AMFMap{
{K: "fmsVer", V: "LNX 9,0,124,2"},
{K: "capabilities", V: float64(31)},
amf0.Object{
{Key: "fmsVer", Value: "LNX 9,0,124,2"},
{Key: "capabilities", Value: float64(31)},
},
flvio.AMFMap{
{K: "level", V: "status"},
{K: "code", V: "NetConnection.Connect.Success"},
{K: "description", V: "Connection succeeded."},
{K: "objectEncoding", V: float64(0)},
amf0.Object{
{Key: "level", Value: "status"},
{Key: "code", Value: "NetConnection.Connect.Success"},
{Key: "description", Value: "Connection succeeded."},
{Key: "objectEncoding", Value: float64(0)},
},
},
}, msg)

6
internal/protocols/rtmp/message/acknowledge.go

@ -11,8 +11,7 @@ type Acknowledge struct { @@ -11,8 +11,7 @@ type Acknowledge struct {
Value uint32
}
// Unmarshal implements Message.
func (m *Acknowledge) Unmarshal(raw *rawmessage.Message) error {
func (m *Acknowledge) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
@ -26,8 +25,7 @@ func (m *Acknowledge) Unmarshal(raw *rawmessage.Message) error { @@ -26,8 +25,7 @@ func (m *Acknowledge) Unmarshal(raw *rawmessage.Message) error {
return nil
}
// Marshal implements Message.
func (m *Acknowledge) Marshal() (*rawmessage.Message, error) {
func (m *Acknowledge) marshal() (*rawmessage.Message, error) {
buf := make([]byte, 4)
buf[0] = byte(m.Value >> 24)

6
internal/protocols/rtmp/message/audio.go

@ -57,8 +57,7 @@ type Audio struct { @@ -57,8 +57,7 @@ type Audio struct {
Payload []byte
}
// Unmarshal implements Message.
func (m *Audio) Unmarshal(raw *rawmessage.Message) error {
func (m *Audio) unmarshal(raw *rawmessage.Message) error {
m.ChunkStreamID = raw.ChunkStreamID
m.DTS = raw.Timestamp
m.MessageStreamID = raw.MessageStreamID
@ -107,8 +106,7 @@ func (m Audio) marshalBodySize() int { @@ -107,8 +106,7 @@ func (m Audio) marshalBodySize() int {
return l
}
// Marshal implements Message.
func (m Audio) Marshal() (*rawmessage.Message, error) {
func (m Audio) marshal() (*rawmessage.Message, error) {
body := make([]byte, m.marshalBodySize())
body[0] = m.Codec<<4 | m.Rate<<2 | m.Depth<<1

26
internal/protocols/rtmp/message/command_amf0.go

@ -3,8 +3,7 @@ package message @@ -3,8 +3,7 @@ package message
import (
"fmt"
"github.com/notedit/rtmp/format/flv/flvio"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/amf0"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/rawmessage"
)
@ -17,12 +16,11 @@ type CommandAMF0 struct { @@ -17,12 +16,11 @@ type CommandAMF0 struct {
Arguments []interface{}
}
// Unmarshal implements Message.
func (m *CommandAMF0) Unmarshal(raw *rawmessage.Message) error {
func (m *CommandAMF0) unmarshal(raw *rawmessage.Message) error {
m.ChunkStreamID = raw.ChunkStreamID
m.MessageStreamID = raw.MessageStreamID
payload, err := flvio.ParseAMFVals(raw.Body, false)
payload, err := amf0.Unmarshal(raw.Body)
if err != nil {
return err
}
@ -48,15 +46,21 @@ func (m *CommandAMF0) Unmarshal(raw *rawmessage.Message) error { @@ -48,15 +46,21 @@ func (m *CommandAMF0) Unmarshal(raw *rawmessage.Message) error {
return nil
}
// Marshal implements Message.
func (m CommandAMF0) Marshal() (*rawmessage.Message, error) {
func (m CommandAMF0) marshal() (*rawmessage.Message, error) {
data := append([]interface{}{
m.Name,
float64(m.CommandID),
}, m.Arguments...)
body, err := amf0.Marshal(data)
if err != nil {
return nil, err
}
return &rawmessage.Message{
ChunkStreamID: m.ChunkStreamID,
Type: uint8(TypeCommandAMF0),
MessageStreamID: m.MessageStreamID,
Body: flvio.FillAMF0ValsMalloc(append([]interface{}{
m.Name,
float64(m.CommandID),
}, m.Arguments...)),
Body: body,
}, nil
}

73
internal/protocols/rtmp/message/command_amf0_test.go

@ -0,0 +1,73 @@ @@ -0,0 +1,73 @@
package message
import (
"testing"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/amf0"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/rawmessage"
)
func BenchmarkCommandAMF0Marshal(b *testing.B) {
msg := &CommandAMF0{
ChunkStreamID: 3,
Name: "connect",
CommandID: 1,
Arguments: []interface{}{
amf0.Object{
{Key: "app", Value: "/stream"},
{Key: "flashVer", Value: "LNX 9,0,124,2"},
{Key: "tcUrl", Value: "http://example.com"},
{Key: "fpad", Value: false},
{Key: "capabilities", Value: 15},
{Key: "audioCodecs", Value: 4071},
{Key: "videoCodecs", Value: 252},
{Key: "videoFunction", Value: 1},
},
},
}
for n := 0; n < b.N; n++ {
msg.marshal() //nolint:errcheck
}
}
func BenchmarkCommandAMF0Unmarshal(b *testing.B) {
raw := &rawmessage.Message{
ChunkStreamID: 0x3,
Timestamp: 0,
Type: 0x14,
MessageStreamID: 0x0,
Body: []uint8{
0x02, 0x00, 0x07, 0x63, 0x6f, 0x6e, 0x6e, 0x65,
0x63, 0x74, 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x61, 0x70,
0x70, 0x02, 0x00, 0x07, 0x2f, 0x73, 0x74, 0x72,
0x65, 0x61, 0x6d, 0x00, 0x08, 0x66, 0x6c, 0x61,
0x73, 0x68, 0x56, 0x65, 0x72, 0x02, 0x00, 0x0d,
0x4c, 0x4e, 0x58, 0x20, 0x39, 0x2c, 0x30, 0x2c,
0x31, 0x32, 0x34, 0x2c, 0x32, 0x00, 0x05, 0x74,
0x63, 0x55, 0x72, 0x6c, 0x02, 0x00, 0x12, 0x68,
0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x65, 0x78,
0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f,
0x6d, 0x00, 0x04, 0x66, 0x70, 0x61, 0x64, 0x01,
0x00, 0x00, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62,
0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x00,
0x40, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0b, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x43,
0x6f, 0x64, 0x65, 0x63, 0x73, 0x00, 0x40, 0xaf,
0xce, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b,
0x76, 0x69, 0x64, 0x65, 0x6f, 0x43, 0x6f, 0x64,
0x65, 0x63, 0x73, 0x00, 0x40, 0x6f, 0x80, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x76, 0x69,
0x64, 0x65, 0x6f, 0x46, 0x75, 0x6e, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x00, 0x3f, 0xf0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09,
},
}
msg := &CommandAMF0{}
for n := 0; n < b.N; n++ {
msg.unmarshal(raw) //nolint:errcheck
}
}

18
internal/protocols/rtmp/message/data_amf0.go

@ -1,8 +1,7 @@ @@ -1,8 +1,7 @@
package message
import (
"github.com/notedit/rtmp/format/flv/flvio"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/amf0"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/rawmessage"
)
@ -13,12 +12,11 @@ type DataAMF0 struct { @@ -13,12 +12,11 @@ type DataAMF0 struct {
Payload []interface{}
}
// Unmarshal implements Message.
func (m *DataAMF0) Unmarshal(raw *rawmessage.Message) error {
func (m *DataAMF0) unmarshal(raw *rawmessage.Message) error {
m.ChunkStreamID = raw.ChunkStreamID
m.MessageStreamID = raw.MessageStreamID
payload, err := flvio.ParseAMFVals(raw.Body, false)
payload, err := amf0.Unmarshal(raw.Body)
if err != nil {
return err
}
@ -27,12 +25,16 @@ func (m *DataAMF0) Unmarshal(raw *rawmessage.Message) error { @@ -27,12 +25,16 @@ func (m *DataAMF0) Unmarshal(raw *rawmessage.Message) error {
return nil
}
// Marshal implements Message.
func (m DataAMF0) Marshal() (*rawmessage.Message, error) {
func (m DataAMF0) marshal() (*rawmessage.Message, error) {
body, err := amf0.Marshal(m.Payload)
if err != nil {
return nil, err
}
return &rawmessage.Message{
ChunkStreamID: m.ChunkStreamID,
Type: uint8(TypeDataAMF0),
MessageStreamID: m.MessageStreamID,
Body: flvio.FillAMF0ValsMalloc(m.Payload),
Body: body,
}, nil
}

6
internal/protocols/rtmp/message/extended_coded_frames.go

@ -17,8 +17,7 @@ type ExtendedCodedFrames struct { @@ -17,8 +17,7 @@ type ExtendedCodedFrames struct {
Payload []byte
}
// Unmarshal implements Message.
func (m *ExtendedCodedFrames) Unmarshal(raw *rawmessage.Message) error {
func (m *ExtendedCodedFrames) unmarshal(raw *rawmessage.Message) error {
if len(raw.Body) < 8 {
return fmt.Errorf("not enough bytes")
}
@ -48,8 +47,7 @@ func (m ExtendedCodedFrames) marshalBodySize() int { @@ -48,8 +47,7 @@ func (m ExtendedCodedFrames) marshalBodySize() int {
return l
}
// Marshal implements Message.
func (m ExtendedCodedFrames) Marshal() (*rawmessage.Message, error) {
func (m ExtendedCodedFrames) marshal() (*rawmessage.Message, error) {
body := make([]byte, m.marshalBodySize())
body[0] = 0b10000000 | byte(ExtendedTypeCodedFrames)

6
internal/protocols/rtmp/message/extended_frames_x.go

@ -16,8 +16,7 @@ type ExtendedFramesX struct { @@ -16,8 +16,7 @@ type ExtendedFramesX struct {
Payload []byte
}
// Unmarshal implements Message.
func (m *ExtendedFramesX) Unmarshal(raw *rawmessage.Message) error {
func (m *ExtendedFramesX) unmarshal(raw *rawmessage.Message) error {
if len(raw.Body) < 6 {
return fmt.Errorf("not enough bytes")
}
@ -35,8 +34,7 @@ func (m ExtendedFramesX) marshalBodySize() int { @@ -35,8 +34,7 @@ func (m ExtendedFramesX) marshalBodySize() int {
return 5 + len(m.Payload)
}
// Marshal implements Message.
func (m ExtendedFramesX) Marshal() (*rawmessage.Message, error) {
func (m ExtendedFramesX) marshal() (*rawmessage.Message, error) {
body := make([]byte, m.marshalBodySize())
body[0] = 0b10000000 | byte(ExtendedTypeFramesX)

6
internal/protocols/rtmp/message/extended_metadata.go

@ -11,8 +11,7 @@ type ExtendedMetadata struct { @@ -11,8 +11,7 @@ type ExtendedMetadata struct {
FourCC FourCC
}
// Unmarshal implements Message.
func (m *ExtendedMetadata) Unmarshal(raw *rawmessage.Message) error {
func (m *ExtendedMetadata) unmarshal(raw *rawmessage.Message) error {
if len(raw.Body) != 5 {
return fmt.Errorf("invalid body size")
}
@ -22,7 +21,6 @@ func (m *ExtendedMetadata) Unmarshal(raw *rawmessage.Message) error { @@ -22,7 +21,6 @@ func (m *ExtendedMetadata) Unmarshal(raw *rawmessage.Message) error {
return fmt.Errorf("ExtendedMetadata is not implemented yet")
}
// Marshal implements Message.
func (m ExtendedMetadata) Marshal() (*rawmessage.Message, error) {
func (m ExtendedMetadata) marshal() (*rawmessage.Message, error) {
return nil, fmt.Errorf("TODO")
}

6
internal/protocols/rtmp/message/extended_mpeg2ts_sequence_start.go

@ -11,8 +11,7 @@ type ExtendedMPEG2TSSequenceStart struct { @@ -11,8 +11,7 @@ type ExtendedMPEG2TSSequenceStart struct {
FourCC FourCC
}
// Unmarshal implements Message.
func (m *ExtendedMPEG2TSSequenceStart) Unmarshal(raw *rawmessage.Message) error {
func (m *ExtendedMPEG2TSSequenceStart) unmarshal(raw *rawmessage.Message) error {
if len(raw.Body) != 5 {
return fmt.Errorf("invalid body size")
}
@ -22,7 +21,6 @@ func (m *ExtendedMPEG2TSSequenceStart) Unmarshal(raw *rawmessage.Message) error @@ -22,7 +21,6 @@ func (m *ExtendedMPEG2TSSequenceStart) Unmarshal(raw *rawmessage.Message) error
return fmt.Errorf("ExtendedMPEG2TSSequenceStart is not implemented yet")
}
// Marshal implements Message.
func (m ExtendedMPEG2TSSequenceStart) Marshal() (*rawmessage.Message, error) {
func (m ExtendedMPEG2TSSequenceStart) marshal() (*rawmessage.Message, error) {
return nil, fmt.Errorf("TODO")
}

6
internal/protocols/rtmp/message/extended_sequence_end.go

@ -11,8 +11,7 @@ type ExtendedSequenceEnd struct { @@ -11,8 +11,7 @@ type ExtendedSequenceEnd struct {
FourCC FourCC
}
// Unmarshal implements Message.
func (m *ExtendedSequenceEnd) Unmarshal(raw *rawmessage.Message) error {
func (m *ExtendedSequenceEnd) unmarshal(raw *rawmessage.Message) error {
if len(raw.Body) != 5 {
return fmt.Errorf("invalid body size")
}
@ -22,7 +21,6 @@ func (m *ExtendedSequenceEnd) Unmarshal(raw *rawmessage.Message) error { @@ -22,7 +21,6 @@ func (m *ExtendedSequenceEnd) Unmarshal(raw *rawmessage.Message) error {
return nil
}
// Marshal implements Message.
func (m ExtendedSequenceEnd) Marshal() (*rawmessage.Message, error) {
func (m ExtendedSequenceEnd) marshal() (*rawmessage.Message, error) {
return nil, fmt.Errorf("TODO")
}

6
internal/protocols/rtmp/message/extended_sequence_start.go

@ -14,8 +14,7 @@ type ExtendedSequenceStart struct { @@ -14,8 +14,7 @@ type ExtendedSequenceStart struct {
Config []byte
}
// Unmarshal implements Message.
func (m *ExtendedSequenceStart) Unmarshal(raw *rawmessage.Message) error {
func (m *ExtendedSequenceStart) unmarshal(raw *rawmessage.Message) error {
if len(raw.Body) < 6 {
return fmt.Errorf("not enough bytes")
}
@ -32,8 +31,7 @@ func (m ExtendedSequenceStart) marshalBodySize() int { @@ -32,8 +31,7 @@ func (m ExtendedSequenceStart) marshalBodySize() int {
return 5 + len(m.Config)
}
// Marshal implements Message.
func (m ExtendedSequenceStart) Marshal() (*rawmessage.Message, error) {
func (m ExtendedSequenceStart) marshal() (*rawmessage.Message, error) {
body := make([]byte, m.marshalBodySize())
body[0] = 0b10000000 | byte(ExtendedTypeSequenceStart)

4
internal/protocols/rtmp/message/message.go

@ -72,6 +72,6 @@ var ( @@ -72,6 +72,6 @@ var (
// Message is a message.
type Message interface {
Unmarshal(*rawmessage.Message) error
Marshal() (*rawmessage.Message, error)
unmarshal(*rawmessage.Message) error
marshal() (*rawmessage.Message, error)
}

2
internal/protocols/rtmp/message/reader.go

@ -138,7 +138,7 @@ func (r *Reader) Read() (Message, error) { @@ -138,7 +138,7 @@ func (r *Reader) Read() (Message, error) {
return nil, err
}
err = msg.Unmarshal(raw)
err = msg.unmarshal(raw)
if err != nil {
return nil, err
}

8
internal/protocols/rtmp/message/reader_test.go

@ -5,9 +5,9 @@ import ( @@ -5,9 +5,9 @@ import (
"testing"
"time"
"github.com/notedit/rtmp/format/flv/flvio"
"github.com/stretchr/testify/require"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/amf0"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/bytecounter"
)
@ -70,9 +70,9 @@ var readWriterCases = []struct { @@ -70,9 +70,9 @@ var readWriterCases = []struct {
Name: "i8yythrergre",
CommandID: 56456,
Arguments: []interface{}{
flvio.AMFMap{
{K: "k1", V: "v1"},
{K: "k2", V: "v2"},
amf0.Object{
{Key: "k1", Value: "v1"},
{Key: "k2", Value: "v2"},
},
nil,
},

6
internal/protocols/rtmp/message/set_chunk_size.go

@ -11,8 +11,7 @@ type SetChunkSize struct { @@ -11,8 +11,7 @@ type SetChunkSize struct {
Value uint32
}
// Unmarshal implements Message.
func (m *SetChunkSize) Unmarshal(raw *rawmessage.Message) error {
func (m *SetChunkSize) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
@ -26,8 +25,7 @@ func (m *SetChunkSize) Unmarshal(raw *rawmessage.Message) error { @@ -26,8 +25,7 @@ func (m *SetChunkSize) Unmarshal(raw *rawmessage.Message) error {
return nil
}
// Marshal implements Message.
func (m *SetChunkSize) Marshal() (*rawmessage.Message, error) {
func (m *SetChunkSize) marshal() (*rawmessage.Message, error) {
buf := make([]byte, 4)
buf[0] = byte(m.Value >> 24)

6
internal/protocols/rtmp/message/set_peer_bandwidth.go

@ -12,8 +12,7 @@ type SetPeerBandwidth struct { @@ -12,8 +12,7 @@ type SetPeerBandwidth struct {
Type byte
}
// Unmarshal implements Message.
func (m *SetPeerBandwidth) Unmarshal(raw *rawmessage.Message) error {
func (m *SetPeerBandwidth) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
@ -28,8 +27,7 @@ func (m *SetPeerBandwidth) Unmarshal(raw *rawmessage.Message) error { @@ -28,8 +27,7 @@ func (m *SetPeerBandwidth) Unmarshal(raw *rawmessage.Message) error {
return nil
}
// Marshal implements Message.
func (m *SetPeerBandwidth) Marshal() (*rawmessage.Message, error) {
func (m *SetPeerBandwidth) marshal() (*rawmessage.Message, error) {
buf := make([]byte, 5)
buf[0] = byte(m.Value >> 24)

6
internal/protocols/rtmp/message/set_window_ack_size.go

@ -11,8 +11,7 @@ type SetWindowAckSize struct { @@ -11,8 +11,7 @@ type SetWindowAckSize struct {
Value uint32
}
// Unmarshal implements Message.
func (m *SetWindowAckSize) Unmarshal(raw *rawmessage.Message) error {
func (m *SetWindowAckSize) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
@ -26,8 +25,7 @@ func (m *SetWindowAckSize) Unmarshal(raw *rawmessage.Message) error { @@ -26,8 +25,7 @@ func (m *SetWindowAckSize) Unmarshal(raw *rawmessage.Message) error {
return nil
}
// Marshal implements Message.
func (m *SetWindowAckSize) Marshal() (*rawmessage.Message, error) {
func (m *SetWindowAckSize) marshal() (*rawmessage.Message, error) {
buf := make([]byte, 4)
buf[0] = byte(m.Value >> 24)

6
internal/protocols/rtmp/message/user_control_ping_request.go

@ -11,8 +11,7 @@ type UserControlPingRequest struct { @@ -11,8 +11,7 @@ type UserControlPingRequest struct {
ServerTime uint32
}
// Unmarshal implements Message.
func (m *UserControlPingRequest) Unmarshal(raw *rawmessage.Message) error {
func (m *UserControlPingRequest) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
@ -26,8 +25,7 @@ func (m *UserControlPingRequest) Unmarshal(raw *rawmessage.Message) error { @@ -26,8 +25,7 @@ func (m *UserControlPingRequest) Unmarshal(raw *rawmessage.Message) error {
return nil
}
// Marshal implements Message.
func (m UserControlPingRequest) Marshal() (*rawmessage.Message, error) {
func (m UserControlPingRequest) marshal() (*rawmessage.Message, error) {
buf := make([]byte, 6)
buf[0] = byte(UserControlTypePingRequest >> 8)

6
internal/protocols/rtmp/message/user_control_ping_response.go

@ -11,8 +11,7 @@ type UserControlPingResponse struct { @@ -11,8 +11,7 @@ type UserControlPingResponse struct {
ServerTime uint32
}
// Unmarshal implements Message.
func (m *UserControlPingResponse) Unmarshal(raw *rawmessage.Message) error {
func (m *UserControlPingResponse) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
@ -26,8 +25,7 @@ func (m *UserControlPingResponse) Unmarshal(raw *rawmessage.Message) error { @@ -26,8 +25,7 @@ func (m *UserControlPingResponse) Unmarshal(raw *rawmessage.Message) error {
return nil
}
// Marshal implements Message.
func (m UserControlPingResponse) Marshal() (*rawmessage.Message, error) {
func (m UserControlPingResponse) marshal() (*rawmessage.Message, error) {
buf := make([]byte, 6)
buf[0] = byte(UserControlTypePingResponse >> 8)

6
internal/protocols/rtmp/message/user_control_set_buffer_length.go

@ -12,8 +12,7 @@ type UserControlSetBufferLength struct { @@ -12,8 +12,7 @@ type UserControlSetBufferLength struct {
BufferLength uint32
}
// Unmarshal implements Message.
func (m *UserControlSetBufferLength) Unmarshal(raw *rawmessage.Message) error {
func (m *UserControlSetBufferLength) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
@ -28,8 +27,7 @@ func (m *UserControlSetBufferLength) Unmarshal(raw *rawmessage.Message) error { @@ -28,8 +27,7 @@ func (m *UserControlSetBufferLength) Unmarshal(raw *rawmessage.Message) error {
return nil
}
// Marshal implements Message.
func (m UserControlSetBufferLength) Marshal() (*rawmessage.Message, error) {
func (m UserControlSetBufferLength) marshal() (*rawmessage.Message, error) {
buf := make([]byte, 10)
buf[0] = byte(UserControlTypeSetBufferLength >> 8)

6
internal/protocols/rtmp/message/user_control_stream_begin.go

@ -11,8 +11,7 @@ type UserControlStreamBegin struct { @@ -11,8 +11,7 @@ type UserControlStreamBegin struct {
StreamID uint32
}
// Unmarshal implements Message.
func (m *UserControlStreamBegin) Unmarshal(raw *rawmessage.Message) error {
func (m *UserControlStreamBegin) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
@ -26,8 +25,7 @@ func (m *UserControlStreamBegin) Unmarshal(raw *rawmessage.Message) error { @@ -26,8 +25,7 @@ func (m *UserControlStreamBegin) Unmarshal(raw *rawmessage.Message) error {
return nil
}
// Marshal implements Message.
func (m UserControlStreamBegin) Marshal() (*rawmessage.Message, error) {
func (m UserControlStreamBegin) marshal() (*rawmessage.Message, error) {
buf := make([]byte, 6)
buf[0] = byte(UserControlTypeStreamBegin >> 8)

6
internal/protocols/rtmp/message/user_control_stream_dry.go

@ -11,8 +11,7 @@ type UserControlStreamDry struct { @@ -11,8 +11,7 @@ type UserControlStreamDry struct {
StreamID uint32
}
// Unmarshal implements Message.
func (m *UserControlStreamDry) Unmarshal(raw *rawmessage.Message) error {
func (m *UserControlStreamDry) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
@ -26,8 +25,7 @@ func (m *UserControlStreamDry) Unmarshal(raw *rawmessage.Message) error { @@ -26,8 +25,7 @@ func (m *UserControlStreamDry) Unmarshal(raw *rawmessage.Message) error {
return nil
}
// Marshal implements Message.
func (m UserControlStreamDry) Marshal() (*rawmessage.Message, error) {
func (m UserControlStreamDry) marshal() (*rawmessage.Message, error) {
buf := make([]byte, 6)
buf[0] = byte(UserControlTypeStreamDry >> 8)

6
internal/protocols/rtmp/message/user_control_stream_eof.go

@ -11,8 +11,7 @@ type UserControlStreamEOF struct { @@ -11,8 +11,7 @@ type UserControlStreamEOF struct {
StreamID uint32
}
// Unmarshal implements Message.
func (m *UserControlStreamEOF) Unmarshal(raw *rawmessage.Message) error {
func (m *UserControlStreamEOF) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
@ -26,8 +25,7 @@ func (m *UserControlStreamEOF) Unmarshal(raw *rawmessage.Message) error { @@ -26,8 +25,7 @@ func (m *UserControlStreamEOF) Unmarshal(raw *rawmessage.Message) error {
return nil
}
// Marshal implements Message.
func (m UserControlStreamEOF) Marshal() (*rawmessage.Message, error) {
func (m UserControlStreamEOF) marshal() (*rawmessage.Message, error) {
buf := make([]byte, 6)
buf[0] = byte(UserControlTypeStreamEOF >> 8)

6
internal/protocols/rtmp/message/user_control_stream_is_recorded.go

@ -11,8 +11,7 @@ type UserControlStreamIsRecorded struct { @@ -11,8 +11,7 @@ type UserControlStreamIsRecorded struct {
StreamID uint32
}
// Unmarshal implements Message.
func (m *UserControlStreamIsRecorded) Unmarshal(raw *rawmessage.Message) error {
func (m *UserControlStreamIsRecorded) unmarshal(raw *rawmessage.Message) error {
if raw.ChunkStreamID != ControlChunkStreamID {
return fmt.Errorf("unexpected chunk stream ID")
}
@ -26,8 +25,7 @@ func (m *UserControlStreamIsRecorded) Unmarshal(raw *rawmessage.Message) error { @@ -26,8 +25,7 @@ func (m *UserControlStreamIsRecorded) Unmarshal(raw *rawmessage.Message) error {
return nil
}
// Marshal implements Message.
func (m UserControlStreamIsRecorded) Marshal() (*rawmessage.Message, error) {
func (m UserControlStreamIsRecorded) marshal() (*rawmessage.Message, error) {
buf := make([]byte, 6)
buf[0] = byte(UserControlTypeStreamIsRecorded >> 8)

6
internal/protocols/rtmp/message/video.go

@ -39,8 +39,7 @@ type Video struct { @@ -39,8 +39,7 @@ type Video struct {
Payload []byte
}
// Unmarshal implements Message.
func (m *Video) Unmarshal(raw *rawmessage.Message) error {
func (m *Video) unmarshal(raw *rawmessage.Message) error {
m.ChunkStreamID = raw.ChunkStreamID
m.DTS = raw.Timestamp
m.MessageStreamID = raw.MessageStreamID
@ -76,8 +75,7 @@ func (m Video) marshalBodySize() int { @@ -76,8 +75,7 @@ func (m Video) marshalBodySize() int {
return 5 + len(m.Payload)
}
// Marshal implements Message.
func (m Video) Marshal() (*rawmessage.Message, error) {
func (m Video) marshal() (*rawmessage.Message, error) {
body := make([]byte, m.marshalBodySize())
if m.IsKeyFrame {

2
internal/protocols/rtmp/message/writer.go

@ -30,7 +30,7 @@ func (w *Writer) SetAcknowledgeValue(v uint32) { @@ -30,7 +30,7 @@ func (w *Writer) SetAcknowledgeValue(v uint32) {
// Write writes a message.
func (w *Writer) Write(msg Message) error {
raw, err := msg.Marshal()
raw, err := msg.marshal()
if err != nil {
return err
}

14
internal/protocols/rtmp/reader.go

@ -12,8 +12,8 @@ import ( @@ -12,8 +12,8 @@ import (
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
"github.com/bluenviron/mediacommon/pkg/codecs/h265"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/notedit/rtmp/format/flv/flvio"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/amf0"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/h264conf"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/message"
)
@ -43,11 +43,11 @@ type OnDataG711Func func(pts time.Duration, samples []byte) @@ -43,11 +43,11 @@ type OnDataG711Func func(pts time.Duration, samples []byte)
// OnDataLPCMFunc is the prototype of the callback passed to OnDataLPCM().
type OnDataLPCMFunc func(pts time.Duration, samples []byte)
func hasVideo(md flvio.AMFMap) (bool, error) {
v, ok := md.GetV("videocodecid")
func hasVideo(md amf0.Object) (bool, error) {
v, ok := md.Get("videocodecid")
if !ok {
// some Dahua cameras send width and height without videocodecid
if v, ok := md.GetV("width"); ok {
if v, ok := md.Get("width"); ok {
if vf, ok := v.(float64); ok && vf != 0 {
return true, nil
}
@ -75,8 +75,8 @@ func hasVideo(md flvio.AMFMap) (bool, error) { @@ -75,8 +75,8 @@ func hasVideo(md flvio.AMFMap) (bool, error) {
return false, fmt.Errorf("unsupported video codec: %v", v)
}
func hasAudio(md flvio.AMFMap, audioTrack *format.Format) (bool, error) {
v, ok := md.GetV("audiocodecid")
func hasAudio(md amf0.Object, audioTrack *format.Format) (bool, error) {
v, ok := md.Get("audiocodecid")
if !ok {
return false, nil
}
@ -156,7 +156,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (format.Format, forma @@ -156,7 +156,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (format.Format, forma
return nil, nil, fmt.Errorf("invalid metadata")
}
md, ok := payload[0].(flvio.AMFMap)
md, ok := payload[0].(amf0.Object)
if !ok {
return nil, nil, fmt.Errorf("invalid metadata")
}

212
internal/protocols/rtmp/reader_test.go

@ -10,9 +10,9 @@ import ( @@ -10,9 +10,9 @@ import (
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
"github.com/bluenviron/mediacommon/pkg/codecs/h265"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/notedit/rtmp/format/flv/flvio"
"github.com/stretchr/testify/require"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/amf0"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/bytecounter"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/h264conf"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/message"
@ -134,22 +134,22 @@ func TestReadTracks(t *testing.T) { @@ -134,22 +134,22 @@ func TestReadTracks(t *testing.T) {
Payload: []interface{}{
"@setDataFrame",
"onMetaData",
flvio.AMFMap{
amf0.Object{
{
K: "videodatarate",
V: float64(0),
Key: "videodatarate",
Value: float64(0),
},
{
K: "videocodecid",
V: float64(message.CodecH264),
Key: "videocodecid",
Value: float64(message.CodecH264),
},
{
K: "audiodatarate",
V: float64(0),
Key: "audiodatarate",
Value: float64(0),
},
{
K: "audiocodecid",
V: float64(message.CodecMPEG4Audio),
Key: "audiocodecid",
Value: float64(message.CodecMPEG4Audio),
},
},
},
@ -204,22 +204,22 @@ func TestReadTracks(t *testing.T) { @@ -204,22 +204,22 @@ func TestReadTracks(t *testing.T) {
Payload: []interface{}{
"@setDataFrame",
"onMetaData",
flvio.AMFMap{
amf0.Object{
{
K: "videodatarate",
V: float64(0),
Key: "videodatarate",
Value: float64(0),
},
{
K: "videocodecid",
V: float64(message.CodecH264),
Key: "videocodecid",
Value: float64(message.CodecH264),
},
{
K: "audiodatarate",
V: float64(0),
Key: "audiodatarate",
Value: float64(0),
},
{
K: "audiocodecid",
V: float64(0),
Key: "audiocodecid",
Value: float64(0),
},
},
},
@ -388,22 +388,22 @@ func TestReadTracks(t *testing.T) { @@ -388,22 +388,22 @@ func TestReadTracks(t *testing.T) {
Payload: []interface{}{
"@setDataFrame",
"onMetaData",
flvio.AMFMap{
amf0.Object{
{
K: "videodatarate",
V: float64(0),
Key: "videodatarate",
Value: float64(0),
},
{
K: "videocodecid",
V: float64(message.CodecH264),
Key: "videocodecid",
Value: float64(message.CodecH264),
},
{
K: "audiodatarate",
V: float64(0),
Key: "audiodatarate",
Value: float64(0),
},
{
K: "audiocodecid",
V: float64(message.CodecMPEG4Audio),
Key: "audiocodecid",
Value: float64(message.CodecMPEG4Audio),
},
},
},
@ -460,22 +460,22 @@ func TestReadTracks(t *testing.T) { @@ -460,22 +460,22 @@ func TestReadTracks(t *testing.T) {
Payload: []interface{}{
"@setDataFrame",
"onMetaData",
flvio.AMFMap{
amf0.Object{
{
K: "videodatarate",
V: float64(0),
Key: "videodatarate",
Value: float64(0),
},
{
K: "videocodecid",
V: "hvc1",
Key: "videocodecid",
Value: "hvc1",
},
{
K: "audiodatarate",
V: float64(0),
Key: "audiodatarate",
Value: float64(0),
},
{
K: "audiocodecid",
V: float64(0),
Key: "audiocodecid",
Value: float64(0),
},
},
},
@ -509,22 +509,22 @@ func TestReadTracks(t *testing.T) { @@ -509,22 +509,22 @@ func TestReadTracks(t *testing.T) {
Payload: []interface{}{
"@setDataFrame",
"onMetaData",
flvio.AMFMap{
amf0.Object{
{
K: "videodatarate",
V: float64(0),
Key: "videodatarate",
Value: float64(0),
},
{
K: "videocodecid",
V: float64(message.FourCCHEVC),
Key: "videocodecid",
Value: float64(message.FourCCHEVC),
},
{
K: "audiodatarate",
V: float64(0),
Key: "audiodatarate",
Value: float64(0),
},
{
K: "audiocodecid",
V: float64(0),
Key: "audiocodecid",
Value: float64(0),
},
},
},
@ -555,38 +555,38 @@ func TestReadTracks(t *testing.T) { @@ -555,38 +555,38 @@ func TestReadTracks(t *testing.T) {
Payload: []interface{}{
"@setDataFrame",
"onMetaData",
flvio.AMFMap{
amf0.Object{
{
K: "duration",
V: float64(0),
Key: "duration",
Value: float64(0),
},
{
K: "width",
V: float64(1920),
Key: "width",
Value: float64(1920),
},
{
K: "height",
V: float64(1080),
Key: "height",
Value: float64(1080),
},
{
K: "videodatarate",
V: float64(0),
Key: "videodatarate",
Value: float64(0),
},
{
K: "framerate",
V: float64(30),
Key: "framerate",
Value: float64(30),
},
{
K: "videocodecid",
V: float64(message.FourCCAV1),
Key: "videocodecid",
Value: float64(message.FourCCAV1),
},
{
K: "encoder",
V: "Lavf60.10.101",
Key: "encoder",
Value: "Lavf60.10.101",
},
{
K: "filesize",
V: float64(0),
Key: "filesize",
Value: float64(0),
},
},
},
@ -634,22 +634,22 @@ func TestReadTracks(t *testing.T) { @@ -634,22 +634,22 @@ func TestReadTracks(t *testing.T) {
Payload: []interface{}{
"@setDataFrame",
"onMetaData",
flvio.AMFMap{
amf0.Object{
{
K: "width",
V: float64(1280),
Key: "width",
Value: float64(1280),
},
{
K: "height",
V: float64(720),
Key: "height",
Value: float64(720),
},
{
K: "framerate",
V: float64(30),
Key: "framerate",
Value: float64(30),
},
{
K: "audiocodecid",
V: float64(10),
Key: "audiocodecid",
Value: float64(10),
},
},
},
@ -695,34 +695,34 @@ func TestReadTracks(t *testing.T) { @@ -695,34 +695,34 @@ func TestReadTracks(t *testing.T) {
Payload: []interface{}{
"@setDataFrame",
"onMetaData",
flvio.AMFMap{
amf0.Object{
{
K: "audiodatarate",
V: float64(128),
Key: "audiodatarate",
Value: float64(128),
},
{
K: "framerate",
V: float64(30),
Key: "framerate",
Value: float64(30),
},
{
K: "videocodecid",
V: float64(7),
Key: "videocodecid",
Value: float64(7),
},
{
K: "videodatarate",
V: float64(2500),
Key: "videodatarate",
Value: float64(2500),
},
{
K: "audiocodecid",
V: float64(10),
Key: "audiocodecid",
Value: float64(10),
},
{
K: "height",
V: float64(720),
Key: "height",
Value: float64(720),
},
{
K: "width",
V: float64(1280),
Key: "width",
Value: float64(1280),
},
},
},
@ -773,11 +773,11 @@ func TestReadTracks(t *testing.T) { @@ -773,11 +773,11 @@ func TestReadTracks(t *testing.T) {
Payload: []interface{}{
"@setDataFrame",
"onMetaData",
flvio.AMFMap{
{K: "duration", V: 0},
{K: "audiocodecid", V: 2},
{K: "encoder", V: "Lavf58.45.100"},
{K: "filesize", V: 0},
amf0.Object{
{Key: "duration", Value: float64(0)},
{Key: "audiocodecid", Value: float64(2)},
{Key: "encoder", Value: "Lavf58.45.100"},
{Key: "filesize", Value: float64(0)},
},
},
},
@ -799,11 +799,11 @@ func TestReadTracks(t *testing.T) { @@ -799,11 +799,11 @@ func TestReadTracks(t *testing.T) {
Payload: []interface{}{
"@setDataFrame",
"onMetaData",
flvio.AMFMap{
{K: "duration", V: 0},
{K: "audiocodecid", V: 7},
{K: "encoder", V: "Lavf58.45.100"},
{K: "filesize", V: 0},
amf0.Object{
{Key: "duration", Value: float64(0)},
{Key: "audiocodecid", Value: float64(7)},
{Key: "encoder", Value: "Lavf58.45.100"},
{Key: "filesize", Value: float64(0)},
},
},
},
@ -834,11 +834,11 @@ func TestReadTracks(t *testing.T) { @@ -834,11 +834,11 @@ func TestReadTracks(t *testing.T) {
Payload: []interface{}{
"@setDataFrame",
"onMetaData",
flvio.AMFMap{
{K: "duration", V: 0},
{K: "audiocodecid", V: 8},
{K: "encoder", V: "Lavf58.45.100"},
{K: "filesize", V: 0},
amf0.Object{
{Key: "duration", Value: float64(0)},
{Key: "audiocodecid", Value: float64(8)},
{Key: "encoder", Value: "Lavf58.45.100"},
{Key: "filesize", Value: float64(0)},
},
},
},
@ -869,10 +869,10 @@ func TestReadTracks(t *testing.T) { @@ -869,10 +869,10 @@ func TestReadTracks(t *testing.T) {
Payload: []interface{}{
"@setDataFrame",
"onMetaData",
flvio.AMFMap{
{K: "duration", V: 0},
{K: "audiocodecid", V: 3},
{K: "filesize", V: 0},
amf0.Object{
{Key: "duration", Value: float64(0)},
{Key: "audiocodecid", Value: float64(3)},
{Key: "filesize", Value: float64(0)},
},
},
},

20
internal/protocols/rtmp/writer.go

@ -7,8 +7,8 @@ import ( @@ -7,8 +7,8 @@ import (
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg1audio"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/notedit/rtmp/format/flv/flvio"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/amf0"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/h264conf"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/message"
)
@ -69,14 +69,14 @@ func (w *Writer) writeTracks(videoTrack format.Format, audioTrack format.Format) @@ -69,14 +69,14 @@ func (w *Writer) writeTracks(videoTrack format.Format, audioTrack format.Format)
Payload: []interface{}{
"@setDataFrame",
"onMetaData",
flvio.AMFMap{
amf0.Object{
{
K: "videodatarate",
V: float64(0),
Key: "videodatarate",
Value: float64(0),
},
{
K: "videocodecid",
V: func() float64 {
Key: "videocodecid",
Value: func() float64 {
switch videoTrack.(type) {
case *format.H264:
return message.CodecH264
@ -87,12 +87,12 @@ func (w *Writer) writeTracks(videoTrack format.Format, audioTrack format.Format) @@ -87,12 +87,12 @@ func (w *Writer) writeTracks(videoTrack format.Format, audioTrack format.Format)
}(),
},
{
K: "audiodatarate",
V: float64(0),
Key: "audiodatarate",
Value: float64(0),
},
{
K: "audiocodecid",
V: func() float64 {
Key: "audiocodecid",
Value: func() float64 {
switch audioTrack.(type) {
case *format.MPEG1Audio:
return message.CodecMPEG1Audio

12
internal/protocols/rtmp/writer_test.go

@ -6,9 +6,9 @@ import ( @@ -6,9 +6,9 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/notedit/rtmp/format/flv/flvio"
"github.com/stretchr/testify/require"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/amf0"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/bytecounter"
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/message"
)
@ -56,11 +56,11 @@ func TestWriteTracks(t *testing.T) { @@ -56,11 +56,11 @@ func TestWriteTracks(t *testing.T) {
Payload: []interface{}{
"@setDataFrame",
"onMetaData",
flvio.AMFMap{
{K: "videodatarate", V: float64(0)},
{K: "videocodecid", V: float64(7)},
{K: "audiodatarate", V: float64(0)},
{K: "audiocodecid", V: float64(10)},
amf0.Object{
{Key: "videodatarate", Value: float64(0)},
{Key: "videocodecid", Value: float64(7)},
{Key: "audiodatarate", Value: float64(0)},
{Key: "audiocodecid", Value: float64(10)},
},
},
}, msg)

Loading…
Cancel
Save