Browse Source

rtmp: support clients that publish with empty metadata or no metadata (#386) (#769)

pull/846/head
aler9 5 years ago
parent
commit
983469a1f9
  1. 4
      internal/core/rtmp_conn.go
  2. 2
      internal/core/rtmp_server_test.go
  3. 2
      internal/core/rtmp_source.go
  4. 111
      internal/rtmp/conn.go
  5. 739
      internal/rtmp/conn_test.go

4
internal/core/rtmp_conn.go

@ -283,7 +283,7 @@ func (c *rtmpConn) runRead(ctx context.Context) error {
} }
c.conn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) c.conn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
err := c.conn.WriteMetadata(videoTrack, audioTrack) err := c.conn.WriteTracks(videoTrack, audioTrack)
if err != nil { if err != nil {
return err return err
} }
@ -441,7 +441,7 @@ func (c *rtmpConn) runRead(ctx context.Context) error {
func (c *rtmpConn) runPublish(ctx context.Context) error { func (c *rtmpConn) runPublish(ctx context.Context) error {
c.conn.SetReadDeadline(time.Now().Add(time.Duration(c.readTimeout))) c.conn.SetReadDeadline(time.Now().Add(time.Duration(c.readTimeout)))
videoTrack, audioTrack, err := c.conn.ReadMetadata() videoTrack, audioTrack, err := c.conn.ReadTracks()
if err != nil { if err != nil {
return err return err
} }

2
internal/core/rtmp_server_test.go

@ -142,7 +142,7 @@ func TestRTMPServerAuth(t *testing.T) {
err = conn.ClientHandshake() err = conn.ClientHandshake()
require.NoError(t, err) require.NoError(t, err)
_, _, err = conn.ReadMetadata() _, _, err = conn.ReadTracks()
require.NoError(t, err) require.NoError(t, err)
}) })
} }

2
internal/core/rtmp_source.go

@ -124,7 +124,7 @@ func (s *rtmpSource) runInner() bool {
conn.SetWriteDeadline(time.Time{}) conn.SetWriteDeadline(time.Time{})
conn.SetReadDeadline(time.Now().Add(time.Duration(s.readTimeout))) conn.SetReadDeadline(time.Now().Add(time.Duration(s.readTimeout)))
videoTrack, audioTrack, err := conn.ReadMetadata() videoTrack, audioTrack, err := conn.ReadTracks()
if err != nil { if err != nil {
return err return err
} }

111
internal/rtmp/conn.go

@ -1,6 +1,7 @@
package rtmp package rtmp
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
"net/url" "net/url"
@ -81,41 +82,32 @@ func (c *Conn) WritePacket(pkt av.Packet) error {
return c.rconn.FlushWrite() return c.rconn.FlushWrite()
} }
// ReadMetadata reads track informations. func trackFromH264DecoderConfig(data []byte) (*gortsplib.TrackH264, error) {
func (c *Conn) ReadMetadata() (*gortsplib.TrackH264, *gortsplib.TrackAAC, error) { codec, err := nh264.FromDecoderConfig(data)
var videoTrack *gortsplib.TrackH264 if err != nil {
var audioTrack *gortsplib.TrackAAC return nil, err
}
md, err := func() (flvio.AMFMap, error) {
pkt, err := c.ReadPacket()
if err != nil {
return nil, err
}
if pkt.Type != av.Metadata {
return nil, fmt.Errorf("first packet must be metadata")
}
arr, err := flvio.ParseAMFVals(pkt.Data, false)
if err != nil {
return nil, err
}
if len(arr) != 1 { return gortsplib.NewTrackH264(96, codec.SPS[0], codec.PPS[0], nil)
return nil, fmt.Errorf("invalid metadata") }
}
ma, ok := arr[0].(flvio.AMFMap) var errEmptyMetadata = errors.New("metadata is empty")
if !ok {
return nil, fmt.Errorf("invalid metadata")
}
return ma, nil func (c *Conn) readTracksFromMetadata(pkt av.Packet) (*gortsplib.TrackH264, *gortsplib.TrackAAC, error) {
}() arr, err := flvio.ParseAMFVals(pkt.Data, false)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if len(arr) != 1 {
return nil, nil, fmt.Errorf("invalid metadata")
}
md, ok := arr[0].(flvio.AMFMap)
if !ok {
return nil, nil, fmt.Errorf("invalid metadata")
}
hasVideo, err := func() (bool, error) { hasVideo, err := func() (bool, error) {
v, ok := md.GetV("videocodecid") v, ok := md.GetV("videocodecid")
if !ok { if !ok {
@ -173,9 +165,12 @@ func (c *Conn) ReadMetadata() (*gortsplib.TrackH264, *gortsplib.TrackAAC, error)
} }
if !hasVideo && !hasAudio { if !hasVideo && !hasAudio {
return nil, nil, fmt.Errorf("stream doesn't contain tracks with supported codecs (H264 or AAC)") return nil, nil, errEmptyMetadata
} }
var videoTrack *gortsplib.TrackH264
var audioTrack *gortsplib.TrackAAC
for { for {
var pkt av.Packet var pkt av.Packet
pkt, err = c.ReadPacket() pkt, err = c.ReadPacket()
@ -193,12 +188,7 @@ func (c *Conn) ReadMetadata() (*gortsplib.TrackH264, *gortsplib.TrackAAC, error)
return nil, nil, fmt.Errorf("video track setupped twice") return nil, nil, fmt.Errorf("video track setupped twice")
} }
codec, err := nh264.FromDecoderConfig(pkt.Data) videoTrack, err = trackFromH264DecoderConfig(pkt.Data)
if err != nil {
return nil, nil, err
}
videoTrack, err = gortsplib.NewTrackH264(96, codec.SPS[0], codec.PPS[0], nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -232,8 +222,55 @@ func (c *Conn) ReadMetadata() (*gortsplib.TrackH264, *gortsplib.TrackAAC, error)
} }
} }
// WriteMetadata writes track informations. // ReadTracks reads track informations.
func (c *Conn) WriteMetadata(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.TrackAAC) error { func (c *Conn) ReadTracks() (*gortsplib.TrackH264, *gortsplib.TrackAAC, error) {
pkt, err := c.ReadPacket()
if err != nil {
return nil, nil, err
}
switch pkt.Type {
case av.Metadata:
videoTrack, audioTrack, err := c.readTracksFromMetadata(pkt)
if err != nil {
if err == errEmptyMetadata {
pkt, err := c.ReadPacket()
if err != nil {
return nil, nil, err
}
if pkt.Type != av.H264DecoderConfig {
return nil, nil, fmt.Errorf("unexpected packet (%v)", pkt.Type)
}
videoTrack, err := trackFromH264DecoderConfig(pkt.Data)
if err != nil {
return nil, nil, err
}
return videoTrack, nil, nil
}
return nil, nil, err
}
return videoTrack, audioTrack, nil
case av.H264DecoderConfig:
videoTrack, err := trackFromH264DecoderConfig(pkt.Data)
if err != nil {
return nil, nil, err
}
return videoTrack, nil, nil
default:
return nil, nil, fmt.Errorf("unexpected packet (%v)", pkt.Type)
}
}
// WriteTracks writes track informations.
func (c *Conn) WriteTracks(videoTrack *gortsplib.TrackH264, audioTrack *gortsplib.TrackAAC) error {
err := c.WritePacket(av.Packet{ err := c.WritePacket(av.Packet{
Type: av.Metadata, Type: av.Metadata,
Data: flvio.FillAMF0ValMalloc(flvio.AMFMap{ Data: flvio.FillAMF0ValMalloc(flvio.AMFMap{

739
internal/rtmp/conn_test.go

@ -237,321 +237,434 @@ func (m chunk3) write(w io.Writer) error {
return err return err
} }
func TestReadMetadata(t *testing.T) { func TestReadTracks(t *testing.T) {
ln, err := net.Listen("tcp", "127.0.0.1:9121") for _, ca := range []string{
require.NoError(t, err) "standard",
defer ln.Close() "empty metadata",
"no metadata",
done := make(chan struct{}) } {
t.Run(ca, func(t *testing.T) {
go func() { ln, err := net.Listen("tcp", "127.0.0.1:9121")
conn, err := ln.Accept() require.NoError(t, err)
require.NoError(t, err) defer ln.Close()
defer conn.Close()
done := make(chan struct{})
rconn := NewServerConn(conn)
err = rconn.ServerHandshake() go func() {
require.NoError(t, err) conn, err := ln.Accept()
require.NoError(t, err)
videoTrack, audioTrack, err := rconn.ReadMetadata() defer conn.Close()
require.NoError(t, err)
rconn := NewServerConn(conn)
videoTrack2, err := gortsplib.NewTrackH264(96, err = rconn.ServerHandshake()
[]byte{ require.NoError(t, err)
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, videoTrack, audioTrack, err := rconn.ReadTracks()
0x00, 0x03, 0x00, 0x3d, 0x08, require.NoError(t, err)
},
[]byte{ switch ca {
0x68, 0xee, 0x3c, 0x80, case "standard":
}, videoTrack2, err := gortsplib.NewTrackH264(96,
nil) []byte{
require.NoError(t, err) 0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
require.Equal(t, videoTrack2, videoTrack) 0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00,
0x00, 0x03, 0x00, 0x3d, 0x08,
audioTrack2, err := gortsplib.NewTrackAAC(96, 2, 44100, 2, nil) },
require.NoError(t, err) []byte{
require.Equal(t, audioTrack2, audioTrack) 0x68, 0xee, 0x3c, 0x80,
},
close(done) nil)
}() require.NoError(t, err)
require.Equal(t, videoTrack2, videoTrack)
conn, err := net.Dial("tcp", "127.0.0.1:9121")
require.NoError(t, err) audioTrack2, err := gortsplib.NewTrackAAC(96, 2, 44100, 2, nil)
defer conn.Close() require.NoError(t, err)
require.Equal(t, audioTrack2, audioTrack)
// C->S handshake C0+C1
err = writeHandshakeC0C1(conn) case "empty metadata":
require.NoError(t, err) videoTrack2, err := gortsplib.NewTrackH264(96,
[]byte{
// S->C handshake S0+S1+S2 0x67, 0x64, 0x00, 0x32, 0xac, 0x2c, 0x6a, 0x80,
s0s1s2 := make([]byte, 1536*2+1) 0xa8, 0x02, 0xfe, 0x9b, 0x82, 0x80, 0x82, 0xa0,
_, err = conn.Read(s0s1s2) 0x00, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x06,
require.NoError(t, err) 0x50, 0x80,
},
// C->S handshake C2 []byte{
err = writeHandshakeC2(conn, s0s1s2) 0x68, 0xee, 0x31, 0xb2, 0x1b,
require.NoError(t, err) },
nil)
// C->S connect require.NoError(t, err)
byts := flvio.FillAMF0ValsMalloc([]interface{}{ require.Equal(t, videoTrack2, videoTrack)
"connect",
1, require.Equal(t, (*gortsplib.TrackAAC)(nil), audioTrack)
flvio.AMFMap{
{K: "app", V: "/stream"}, case "no metadata":
{K: "flashVer", V: "LNX 9,0,124,2"}, videoTrack2, err := gortsplib.NewTrackH264(96,
{K: "tcUrl", V: getTcURL("rtmp://127.0.0.1:9121/stream")}, []byte{
{K: "fpad", V: false}, 0x27, 0x42, 0xe0, 0x1f, 0x8d, 0x68, 0x05, 0x00,
{K: "capabilities", V: 15}, 0x5b, 0xa1, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00,
{K: "audioCodecs", V: 4071}, 0x00, 0x03, 0x00, 0x1e, 0x0f, 0x10, 0x7a, 0x80,
{K: "videoCodecs", V: 252}, },
{K: "videoFunction", V: 1}, []byte{
}, 0x28, 0xce, 0x32, 0x48,
}) },
err = chunk0{ nil)
chunkStreamID: 3, require.NoError(t, err)
typ: 0x14, require.Equal(t, videoTrack2, videoTrack)
bodyLen: uint32(len(byts)),
body: byts[:128], require.Equal(t, (*gortsplib.TrackAAC)(nil), audioTrack)
}.write(conn) }
require.NoError(t, err)
err = chunk3{ close(done)
chunkStreamID: 3, }()
body: byts[128:],
}.write(conn) conn, err := net.Dial("tcp", "127.0.0.1:9121")
require.NoError(t, err) require.NoError(t, err)
defer conn.Close()
// S->C window acknowledgement size
var c0 chunk0 // C->S handshake C0+C1
err = c0.read(conn, 128) err = writeHandshakeC0C1(conn)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, chunk0{
chunkStreamID: 2, // S->C handshake S0+S1+S2
typ: 5, s0s1s2 := make([]byte, 1536*2+1)
bodyLen: 4, _, err = conn.Read(s0s1s2)
body: []byte{0x00, 38, 37, 160}, require.NoError(t, err)
}, c0)
// C->S handshake C2
// S->C set peer bandwidth err = writeHandshakeC2(conn, s0s1s2)
err = c0.read(conn, 128) require.NoError(t, err)
require.NoError(t, err)
require.Equal(t, chunk0{ // C->S connect
chunkStreamID: 2, byts := flvio.FillAMF0ValsMalloc([]interface{}{
typ: 6, "connect",
bodyLen: 5, 1,
body: []byte{0x00, 0x26, 0x25, 0xa0, 0x02}, flvio.AMFMap{
}, c0) {K: "app", V: "/stream"},
{K: "flashVer", V: "LNX 9,0,124,2"},
// S->C set chunk size {K: "tcUrl", V: getTcURL("rtmp://127.0.0.1:9121/stream")},
err = c0.read(conn, 128) {K: "fpad", V: false},
require.NoError(t, err) {K: "capabilities", V: 15},
require.Equal(t, chunk0{ {K: "audioCodecs", V: 4071},
chunkStreamID: 2, {K: "videoCodecs", V: 252},
typ: 1, {K: "videoFunction", V: 1},
bodyLen: 4, },
body: []byte{0x00, 0x01, 0x00, 0x00}, })
}, c0) err = chunk0{
chunkStreamID: 3,
// S->C result typ: 0x14,
err = c0.read(conn, 65536) bodyLen: uint32(len(byts)),
require.NoError(t, err) body: byts[:128],
require.Equal(t, uint8(3), c0.chunkStreamID) }.write(conn)
require.Equal(t, uint8(0x14), c0.typ) require.NoError(t, err)
arr, err := flvio.ParseAMFVals(c0.body, false) err = chunk3{
require.NoError(t, err) chunkStreamID: 3,
require.Equal(t, []interface{}{ body: byts[128:],
"_result", }.write(conn)
float64(1), require.NoError(t, err)
flvio.AMFMap{
{K: "fmsVer", V: "LNX 9,0,124,2"}, // S->C window acknowledgement size
{K: "capabilities", V: float64(31)}, var c0 chunk0
}, err = c0.read(conn, 128)
flvio.AMFMap{ require.NoError(t, err)
{K: "level", V: "status"}, require.Equal(t, chunk0{
{K: "code", V: "NetConnection.Connect.Success"}, chunkStreamID: 2,
{K: "description", V: "Connection succeeded."}, typ: 5,
{K: "objectEncoding", V: float64(0)}, bodyLen: 4,
}, body: []byte{0x00, 38, 37, 160},
}, arr) }, c0)
// C->S set chunk size // S->C set peer bandwidth
err = chunk0{ err = c0.read(conn, 128)
chunkStreamID: 2, require.NoError(t, err)
typ: 1, require.Equal(t, chunk0{
bodyLen: 4, chunkStreamID: 2,
body: []byte{0x00, 0x01, 0x00, 0x00}, typ: 6,
}.write(conn) bodyLen: 5,
require.NoError(t, err) body: []byte{0x00, 0x26, 0x25, 0xa0, 0x02},
}, c0)
// C->S releaseStream
err = chunk1{ // S->C set chunk size
chunkStreamID: 3, err = c0.read(conn, 128)
typ: 0x14, require.NoError(t, err)
body: flvio.FillAMF0ValsMalloc([]interface{}{ require.Equal(t, chunk0{
"releaseStream", chunkStreamID: 2,
float64(2), typ: 1,
nil, bodyLen: 4,
"", body: []byte{0x00, 0x01, 0x00, 0x00},
}), }, c0)
}.write(conn)
require.NoError(t, err) // S->C result
err = c0.read(conn, 65536)
// C->S FCPublish require.NoError(t, err)
err = chunk1{ require.Equal(t, uint8(3), c0.chunkStreamID)
chunkStreamID: 3, require.Equal(t, uint8(0x14), c0.typ)
typ: 0x14, arr, err := flvio.ParseAMFVals(c0.body, false)
body: flvio.FillAMF0ValsMalloc([]interface{}{ require.NoError(t, err)
"FCPublish", require.Equal(t, []interface{}{
float64(3), "_result",
nil, float64(1),
"", flvio.AMFMap{
}), {K: "fmsVer", V: "LNX 9,0,124,2"},
}.write(conn) {K: "capabilities", V: float64(31)},
require.NoError(t, err) },
flvio.AMFMap{
// C->S createStream {K: "level", V: "status"},
err = chunk3{ {K: "code", V: "NetConnection.Connect.Success"},
chunkStreamID: 3, {K: "description", V: "Connection succeeded."},
body: flvio.FillAMF0ValsMalloc([]interface{}{ {K: "objectEncoding", V: float64(0)},
"createStream", },
float64(4), }, arr)
nil,
}), // C->S set chunk size
}.write(conn) err = chunk0{
require.NoError(t, err) chunkStreamID: 2,
typ: 1,
// S->C result bodyLen: 4,
err = c0.read(conn, 65536) body: []byte{0x00, 0x01, 0x00, 0x00},
require.NoError(t, err) }.write(conn)
require.Equal(t, uint8(3), c0.chunkStreamID) require.NoError(t, err)
require.Equal(t, uint8(0x14), c0.typ)
arr, err = flvio.ParseAMFVals(c0.body, false) // C->S releaseStream
require.NoError(t, err) err = chunk1{
require.Equal(t, []interface{}{ chunkStreamID: 3,
"_result", typ: 0x14,
float64(4), body: flvio.FillAMF0ValsMalloc([]interface{}{
nil, "releaseStream",
float64(1), float64(2),
}, arr) nil,
"",
// C->S publish }),
byts = flvio.FillAMF0ValsMalloc([]interface{}{ }.write(conn)
"publish", require.NoError(t, err)
float64(5),
nil, // C->S FCPublish
"", err = chunk1{
"live", chunkStreamID: 3,
}) typ: 0x14,
err = chunk0{ body: flvio.FillAMF0ValsMalloc([]interface{}{
chunkStreamID: 8, "FCPublish",
typ: 0x14, float64(3),
streamID: 1, nil,
bodyLen: uint32(len(byts)), "",
body: byts, }),
}.write(conn) }.write(conn)
require.NoError(t, err) require.NoError(t, err)
// S->C onStatus // C->S createStream
err = c0.read(conn, 65536) err = chunk3{
require.NoError(t, err) chunkStreamID: 3,
require.Equal(t, uint8(5), c0.chunkStreamID) body: flvio.FillAMF0ValsMalloc([]interface{}{
require.Equal(t, uint8(0x14), c0.typ) "createStream",
arr, err = flvio.ParseAMFVals(c0.body, false) float64(4),
require.NoError(t, err) nil,
require.Equal(t, []interface{}{ }),
"onStatus", }.write(conn)
float64(5), require.NoError(t, err)
nil,
flvio.AMFMap{ // S->C result
{K: "level", V: "status"}, err = c0.read(conn, 65536)
{K: "code", V: "NetStream.Publish.Start"}, require.NoError(t, err)
{K: "description", V: "publish start"}, require.Equal(t, uint8(3), c0.chunkStreamID)
}, require.Equal(t, uint8(0x14), c0.typ)
}, arr) arr, err = flvio.ParseAMFVals(c0.body, false)
require.NoError(t, err)
// C->S metadata require.Equal(t, []interface{}{
byts = flvio.FillAMF0ValsMalloc([]interface{}{ "_result",
"@setDataFrame", float64(4),
"onMetaData", nil,
flvio.AMFMap{ float64(1),
{ }, arr)
K: "videodatarate",
V: float64(0), // C->S publish
}, byts = flvio.FillAMF0ValsMalloc([]interface{}{
{ "publish",
K: "videocodecid", float64(5),
V: float64(codecH264), nil,
}, "",
{ "live",
K: "audiodatarate", })
V: float64(0), err = chunk0{
}, chunkStreamID: 8,
{ typ: 0x14,
K: "audiocodecid", streamID: 1,
V: float64(codecAAC), bodyLen: uint32(len(byts)),
}, body: byts,
}, }.write(conn)
}) require.NoError(t, err)
err = chunk0{
chunkStreamID: 4, // S->C onStatus
typ: 0x12, err = c0.read(conn, 65536)
streamID: 1, require.NoError(t, err)
bodyLen: uint32(len(byts)), require.Equal(t, uint8(5), c0.chunkStreamID)
body: byts, require.Equal(t, uint8(0x14), c0.typ)
}.write(conn) arr, err = flvio.ParseAMFVals(c0.body, false)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, []interface{}{
// C->S H264 decoder config "onStatus",
codec := nh264.Codec{ float64(5),
SPS: map[int][]byte{ nil,
0: { flvio.AMFMap{
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0, {K: "level", V: "status"},
0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, {K: "code", V: "NetStream.Publish.Start"},
0x00, 0x03, 0x00, 0x3d, 0x08, {K: "description", V: "publish start"},
}, },
}, }, arr)
PPS: map[int][]byte{
0: { switch ca {
0x68, 0xee, 0x3c, 0x80, case "standard":
}, // C->S metadata
}, byts = flvio.FillAMF0ValsMalloc([]interface{}{
"@setDataFrame",
"onMetaData",
flvio.AMFMap{
{
K: "videodatarate",
V: float64(0),
},
{
K: "videocodecid",
V: float64(codecH264),
},
{
K: "audiodatarate",
V: float64(0),
},
{
K: "audiocodecid",
V: float64(codecAAC),
},
},
})
err = chunk0{
chunkStreamID: 4,
typ: 0x12,
streamID: 1,
bodyLen: uint32(len(byts)),
body: byts,
}.write(conn)
require.NoError(t, err)
// C->S H264 decoder config
codec := nh264.Codec{
SPS: map[int][]byte{
0: {
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00,
0x00, 0x03, 0x00, 0x3d, 0x08,
},
},
PPS: map[int][]byte{
0: {
0x68, 0xee, 0x3c, 0x80,
},
},
}
b := make([]byte, 128)
var n int
codec.ToConfig(b, &n)
decConfig := b[:n]
err = chunk0{
chunkStreamID: 6,
typ: flvio.TAG_VIDEO,
streamID: 1,
bodyLen: uint32(len(decConfig) + 5),
body: append([]byte{flvio.FRAME_KEY<<4 | flvio.VIDEO_H264, 0, 0, 0, 0}, decConfig...),
}.write(conn)
require.NoError(t, err)
// C->S AAC decoder config
enc, err := aac.MPEG4AudioConfig{
Type: 2,
SampleRate: 44100,
ChannelCount: 2,
}.Encode()
require.NoError(t, err)
err = chunk0{
chunkStreamID: 4,
typ: flvio.TAG_AUDIO,
streamID: 1,
bodyLen: uint32(len(enc) + 2),
body: append([]byte{
flvio.SOUND_AAC<<4 | flvio.SOUND_44Khz<<2 | flvio.SOUND_16BIT<<1 | flvio.SOUND_STEREO,
flvio.AAC_SEQHDR,
}, enc...),
}.write(conn)
require.NoError(t, err)
case "empty metadata":
// C->S metadata
byts = flvio.FillAMF0ValsMalloc([]interface{}{
"@setDataFrame",
"onMetaData",
flvio.AMFMap{
{
K: "width",
V: float64(2688),
},
{
K: "height",
V: float64(1520),
},
{
K: "framerate",
V: float64(0o25),
},
},
})
err = chunk0{
chunkStreamID: 4,
typ: 0x12,
streamID: 1,
bodyLen: uint32(len(byts)),
body: byts,
}.write(conn)
require.NoError(t, err)
// C->S H264 decoder config
byts := []byte{
0x17, 0x00, 0x00, 0x00, 0x00, 0x01, 0x64, 0x00,
0x32, 0xff, 0xe1, 0x00, 0x1a, 0x67, 0x64, 0x00,
0x32, 0xac, 0x2c, 0x6a, 0x80, 0xa8, 0x02, 0xfe,
0x9b, 0x82, 0x80, 0x82, 0xa0, 0x00, 0x00, 0x03,
0x00, 0x20, 0x00, 0x00, 0x06, 0x50, 0x80, 0x01,
0x00, 0x05, 0x68, 0xee, 0x31, 0xb2, 0x1b,
}
err = chunk0{
chunkStreamID: 21,
typ: 0x09,
streamID: 1,
bodyLen: uint32(len(byts)),
body: byts,
}.write(conn)
require.NoError(t, err)
case "no metadata":
// C->S H264 decoder config
byts := []byte{
0x17, 0x00, 0x00, 0x00, 0x00, 0x01, 0x42, 0xe0,
0x1f, 0xff, 0xe1, 0x00, 0x18, 0x27, 0x42, 0xe0,
0x1f, 0x8d, 0x68, 0x05, 0x00, 0x5b, 0xa1, 0x00,
0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00,
0x1e, 0x0f, 0x10, 0x7a, 0x80, 0x01, 0x00, 0x04,
0x28, 0xce, 0x32, 0x48,
}
err = chunk0{
chunkStreamID: 4,
typ: 0x09,
streamID: 1,
bodyLen: uint32(len(byts)),
body: byts,
}.write(conn)
require.NoError(t, err)
}
<-done
})
} }
b := make([]byte, 128)
var n int
codec.ToConfig(b, &n)
decConfig := b[:n]
err = chunk0{
chunkStreamID: 6,
typ: flvio.TAG_VIDEO,
streamID: 1,
bodyLen: uint32(len(decConfig) + 5),
body: append([]byte{flvio.FRAME_KEY<<4 | flvio.VIDEO_H264, 0, 0, 0, 0}, decConfig...),
}.write(conn)
require.NoError(t, err)
// C->S AAC decoder config
enc, err := aac.MPEG4AudioConfig{
Type: 2,
SampleRate: 44100,
ChannelCount: 2,
}.Encode()
require.NoError(t, err)
err = chunk0{
chunkStreamID: 4,
typ: flvio.TAG_AUDIO,
streamID: 1,
bodyLen: uint32(len(enc) + 2),
body: append([]byte{
flvio.SOUND_AAC<<4 | flvio.SOUND_44Khz<<2 | flvio.SOUND_16BIT<<1 | flvio.SOUND_STEREO,
flvio.AAC_SEQHDR,
}, enc...),
}.write(conn)
require.NoError(t, err)
<-done
} }
func TestWriteMetadata(t *testing.T) { func TestWriteTracks(t *testing.T) {
ln, err := net.Listen("tcp", "127.0.0.1:9121") ln, err := net.Listen("tcp", "127.0.0.1:9121")
require.NoError(t, err) require.NoError(t, err)
defer ln.Close() defer ln.Close()
@ -580,7 +693,7 @@ func TestWriteMetadata(t *testing.T) {
audioTrack, err := gortsplib.NewTrackAAC(96, 2, 44100, 2, nil) audioTrack, err := gortsplib.NewTrackAAC(96, 2, 44100, 2, nil)
require.NoError(t, err) require.NoError(t, err)
err = rconn.WriteMetadata(videoTrack, audioTrack) err = rconn.WriteTracks(videoTrack, audioTrack)
require.NoError(t, err) require.NoError(t, err)
}() }()

Loading…
Cancel
Save