diff --git a/client_publish_test.go b/client_publish_test.go index 859b2306..68c50f76 100644 --- a/client_publish_test.go +++ b/client_publish_test.go @@ -171,7 +171,7 @@ func TestClientPublishSerial(t *testing.T) { }(), } - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) conn, err := c.DialPublish("rtsp://localhost:8554/teststream", @@ -313,7 +313,7 @@ func TestClientPublishParallel(t *testing.T) { }(), } - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) writerDone := make(chan struct{}) @@ -471,7 +471,7 @@ func TestClientPublishPauseSerial(t *testing.T) { }(), } - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) conn, err := c.DialPublish("rtsp://localhost:8554/teststream", @@ -609,7 +609,7 @@ func TestClientPublishPauseParallel(t *testing.T) { }(), } - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) conn, err := c.DialPublish("rtsp://localhost:8554/teststream", @@ -747,7 +747,7 @@ func TestClientPublishAutomaticProtocol(t *testing.T) { require.NoError(t, err) }() - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) conn, err := DialPublish("rtsp://localhost:8554/teststream", @@ -888,7 +888,7 @@ func TestClientPublishRTCPReport(t *testing.T) { senderReportPeriod: 1 * time.Second, } - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) conn, err := c.DialPublish("rtsp://localhost:8554/teststream", diff --git a/client_read_test.go b/client_read_test.go index 3331cace..f4e8f18c 100644 --- a/client_read_test.go +++ b/client_read_test.go @@ -23,13 +23,13 @@ import ( ) func TestClientReadTracks(t *testing.T) { - track1, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track1, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) - track2, err := NewTrackAAC(96, []byte{17, 144}) + track2, err := NewTrackAAC(96, &TrackConfigAAC{Type: 1, SampleRate: 44100, ChannelCount: 2}) require.NoError(t, err) - track3, err := NewTrackAAC(96, []byte{0x12, 0x30}) + track3, err := NewTrackAAC(96, &TrackConfigAAC{Type: 1, SampleRate: 96000, ChannelCount: 2}) require.NoError(t, err) l, err := net.Listen("tcp", "localhost:8554") @@ -221,7 +221,7 @@ func TestClientRead(t *testing.T) { require.Equal(t, base.Describe, req.Method) require.Equal(t, mustParseURL(scheme+"://"+listenIP+":8554/teststream"), req.URL) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := cloneAndClearTracks(Tracks{track}) @@ -463,10 +463,10 @@ func TestClientReadPartial(t *testing.T) { require.Equal(t, base.Describe, req.Method) require.Equal(t, mustParseURL("rtsp://"+listenIP+":8554/teststream"), req.URL) - track1, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track1, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) - track2, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track2, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := cloneAndClearTracks(Tracks{track1, track2}) @@ -611,7 +611,7 @@ func TestClientReadNoContentBase(t *testing.T) { require.Equal(t, base.Describe, req.Method) require.Equal(t, mustParseURL("rtsp://localhost:8554/teststream"), req.URL) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := cloneAndClearTracks(Tracks{track}) @@ -719,7 +719,7 @@ func TestClientReadAnyPort(t *testing.T) { require.NoError(t, err) require.Equal(t, base.Describe, req.Method) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := cloneAndClearTracks(Tracks{track}) @@ -847,7 +847,7 @@ func TestClientReadAutomaticProtocol(t *testing.T) { require.NoError(t, err) require.Equal(t, base.Describe, req.Method) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := cloneAndClearTracks(Tracks{track}) @@ -979,7 +979,7 @@ func TestClientReadAutomaticProtocol(t *testing.T) { err = v.ValidateRequest(req, nil) require.NoError(t, err) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := cloneAndClearTracks(Tracks{track}) @@ -1172,7 +1172,7 @@ func TestClientReadDifferentInterleavedIDs(t *testing.T) { require.Equal(t, base.Describe, req.Method) require.Equal(t, mustParseURL("rtsp://localhost:8554/teststream"), req.URL) - track1, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track1, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := cloneAndClearTracks(Tracks{track1}) @@ -1334,7 +1334,7 @@ func TestClientReadRedirect(t *testing.T) { require.NoError(t, err) require.Equal(t, base.Describe, req.Method) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := cloneAndClearTracks(Tracks{track}) @@ -1493,7 +1493,7 @@ func TestClientReadPause(t *testing.T) { require.NoError(t, err) require.Equal(t, base.Describe, req.Method) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := cloneAndClearTracks(Tracks{track}) @@ -1679,7 +1679,7 @@ func TestClientReadRTCPReport(t *testing.T) { require.NoError(t, err) require.Equal(t, base.Describe, req.Method) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := cloneAndClearTracks(Tracks{track}) @@ -1854,7 +1854,7 @@ func TestClientReadErrorTimeout(t *testing.T) { require.NoError(t, err) require.Equal(t, base.Describe, req.Method) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := cloneAndClearTracks(Tracks{track}) @@ -2011,7 +2011,7 @@ func TestClientReadIgnoreTCPInvalidTrack(t *testing.T) { require.NoError(t, err) require.Equal(t, base.Describe, req.Method) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := cloneAndClearTracks(Tracks{track}) @@ -2141,7 +2141,7 @@ func TestClientReadSeek(t *testing.T) { require.NoError(t, err) require.Equal(t, base.Describe, req.Method) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := cloneAndClearTracks(Tracks{track}) diff --git a/client_test.go b/client_test.go index 2ca3b965..9631d551 100644 --- a/client_test.go +++ b/client_test.go @@ -69,7 +69,7 @@ func TestClientSession(t *testing.T) { require.Equal(t, base.HeaderValue{"123456"}, req.Header["Session"]) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := cloneAndClearTracks(Tracks{track}) @@ -149,7 +149,7 @@ func TestClientAuth(t *testing.T) { err = v.ValidateRequest(req, nil) require.NoError(t, err) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := cloneAndClearTracks(Tracks{track}) @@ -212,7 +212,7 @@ func TestClientDescribeCharset(t *testing.T) { require.Equal(t, base.Describe, req.Method) require.Equal(t, mustParseURL("rtsp://localhost:8554/teststream"), req.URL) - track1, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track1, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) err = base.Response{ diff --git a/examples/client-publish-options/main.go b/examples/client-publish-options/main.go index d89d80c5..59fc7e99 100644 --- a/examples/client-publish-options/main.go +++ b/examples/client-publish-options/main.go @@ -36,7 +36,7 @@ func main() { fmt.Println("stream connected") // create a H264 track - track, err := gortsplib.NewTrackH264(96, sps, pps) + track, err := gortsplib.NewTrackH264(96, &gortsplib.TrackConfigH264{sps, pps}) if err != nil { panic(err) } diff --git a/examples/client-publish-pause/main.go b/examples/client-publish-pause/main.go index 9e605d6f..39685374 100644 --- a/examples/client-publish-pause/main.go +++ b/examples/client-publish-pause/main.go @@ -37,7 +37,7 @@ func main() { fmt.Println("stream connected") // create a H264 track - track, err := gortsplib.NewTrackH264(96, sps, pps) + track, err := gortsplib.NewTrackH264(96, &gortsplib.TrackConfigH264{sps, pps}) if err != nil { panic(err) } diff --git a/examples/client-publish/main.go b/examples/client-publish/main.go index 612163cf..0620a23b 100644 --- a/examples/client-publish/main.go +++ b/examples/client-publish/main.go @@ -34,7 +34,7 @@ func main() { fmt.Println("stream connected") // create a H264 track - track, err := gortsplib.NewTrackH264(96, sps, pps) + track, err := gortsplib.NewTrackH264(96, &gortsplib.TrackConfigH264{sps, pps}) if err != nil { panic(err) } diff --git a/pkg/rtpaac/mpeg4audioconfig.go b/pkg/rtpaac/mpeg4audioconfig.go index 124adabd..a8e213b9 100644 --- a/pkg/rtpaac/mpeg4audioconfig.go +++ b/pkg/rtpaac/mpeg4audioconfig.go @@ -3,6 +3,7 @@ package rtpaac import ( "bytes" "fmt" + "io" "github.com/icza/bitio" ) @@ -43,12 +44,13 @@ var channelCounts = []int{ // MPEG4AudioConfig is a MPEG-4 Audio configuration. type MPEG4AudioConfig struct { - Type MPEG4AudioType - SampleRate int - ChannelCount int + Type MPEG4AudioType + SampleRate int + ChannelCount int + AOTSpecificConfig []byte } -// Decode decodes an MPEG-4 Audio configuration. +// Decode decodes an MPEG4AudioConfig. func (c *MPEG4AudioConfig) Decode(byts []byte) error { // ref: https://wiki.multimedia.cx/index.php/MPEG-4_Audio @@ -60,14 +62,6 @@ func (c *MPEG4AudioConfig) Decode(byts []byte) error { } c.Type = MPEG4AudioType(tmp) - if tmp == 31 { - tmp, err = r.ReadBits(6) - if err != nil { - return err - } - c.Type = MPEG4AudioType(tmp + 32) - } - switch c.Type { case MPEG4AudioTypeAACLC: default: @@ -110,5 +104,64 @@ func (c *MPEG4AudioConfig) Decode(byts []byte) error { return fmt.Errorf("invalid channel configuration (%d)", channelConfig) } + for { + byt, err := r.ReadBits(8) + if err != nil { + if err == io.EOF { + break + } + return err + } + + c.AOTSpecificConfig = append(c.AOTSpecificConfig, uint8(byt)) + } + return nil } + +// Encode encodes an MPEG4AudioConfig. +func (c MPEG4AudioConfig) Encode() ([]byte, error) { + var buf bytes.Buffer + w := bitio.NewWriter(&buf) + + w.WriteBits(uint64(c.Type), 5) + + sampleRateIndex := func() int { + for i, s := range sampleRates { + if s == c.SampleRate { + return i + } + } + return -1 + }() + + if sampleRateIndex != -1 { + w.WriteBits(uint64(sampleRateIndex), 4) + } else { + w.WriteBits(uint64(15), 4) + w.WriteBits(uint64(c.SampleRate), 24) + } + + channelConfig := func() int { + for i, co := range channelCounts { + if co == c.ChannelCount { + return i + 1 + } + } + return -1 + }() + + if channelConfig == -1 { + return nil, fmt.Errorf("invalid channel count (%d)", c.ChannelCount) + } + + w.WriteBits(uint64(channelConfig), 4) + + for _, b := range c.AOTSpecificConfig { + w.WriteBits(uint64(b), 8) + } + + w.Close() + + return buf.Bytes(), nil +} diff --git a/pkg/rtpaac/mpeg4audioconfig_test.go b/pkg/rtpaac/mpeg4audioconfig_test.go index 8bb348ed..598a9a4d 100644 --- a/pkg/rtpaac/mpeg4audioconfig_test.go +++ b/pkg/rtpaac/mpeg4audioconfig_test.go @@ -15,9 +15,10 @@ var configCases = []struct { "aac-lc 44.1khz mono", []byte{0x12, 0x08, 0x56, 0xe5, 0x00}, MPEG4AudioConfig{ - Type: MPEG4AudioTypeAACLC, - SampleRate: 44100, - ChannelCount: 1, + Type: MPEG4AudioTypeAACLC, + SampleRate: 44100, + ChannelCount: 1, + AOTSpecificConfig: []byte{0x0A, 0xDC, 0xA0}, }, }, { @@ -33,9 +34,10 @@ var configCases = []struct { "aac-lc 96khz stereo", []byte{0x10, 0x10, 0x56, 0xE5, 0x00}, MPEG4AudioConfig{ - Type: MPEG4AudioTypeAACLC, - SampleRate: 96000, - ChannelCount: 2, + Type: MPEG4AudioTypeAACLC, + SampleRate: 96000, + ChannelCount: 2, + AOTSpecificConfig: []byte{0x0A, 0xDC, 0xA0}, }, }, { @@ -81,14 +83,9 @@ func TestConfigDecodeErrors(t *testing.T) { "EOF", }, { - "extended type missing", - []byte{31 << 3}, - "EOF", - }, - { - "extended type invalid", - []byte{31 << 3, 20}, - "unsupported type: 32", + "unsupported type", + []byte{18 << 3}, + "unsupported type: 18", }, { "sample rate missing", @@ -123,3 +120,36 @@ func TestConfigDecodeErrors(t *testing.T) { }) } } + +func TestConfigEncode(t *testing.T) { + for _, ca := range configCases { + t.Run(ca.name, func(t *testing.T) { + enc, err := ca.dec.Encode() + require.NoError(t, err) + require.Equal(t, ca.enc, enc) + }) + } +} + +func TestConfigEncodeErrors(t *testing.T) { + for _, ca := range []struct { + name string + conf MPEG4AudioConfig + err string + }{ + { + "invalid channel config", + MPEG4AudioConfig{ + Type: 2, + SampleRate: 44100, + ChannelCount: 0, + }, + "invalid channel count (0)", + }, + } { + t.Run(ca.name, func(t *testing.T) { + _, err := ca.conf.Encode() + require.Equal(t, ca.err, err.Error()) + }) + } +} diff --git a/server_publish_test.go b/server_publish_test.go index b7cae9e4..0e5309d2 100644 --- a/server_publish_test.go +++ b/server_publish_test.go @@ -100,7 +100,7 @@ func TestServerPublishErrorAnnounce(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp"}, }, Body: func() []byte { - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) track.Media.Attributes = append(track.Media.Attributes, psdp.Attribute{ Key: "control", @@ -139,7 +139,7 @@ func TestServerPublishErrorAnnounce(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp"}, }, Body: func() []byte { - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) track.Media.Attributes = append(track.Media.Attributes, psdp.Attribute{ Key: "control", @@ -178,7 +178,7 @@ func TestServerPublishErrorAnnounce(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp"}, }, Body: func() []byte { - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) track.Media.Attributes = append(track.Media.Attributes, psdp.Attribute{ Key: "control", @@ -320,7 +320,7 @@ func TestServerPublishSetupPath(t *testing.T) { defer conn.Close() bconn := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) track.Media.Attributes = append(track.Media.Attributes, psdp.Attribute{ Key: "control", @@ -415,7 +415,7 @@ func TestServerPublishErrorSetupDifferentPaths(t *testing.T) { defer conn.Close() bconn := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := Tracks{track} @@ -497,7 +497,7 @@ func TestServerPublishErrorSetupTrackTwice(t *testing.T) { defer conn.Close() bconn := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := Tracks{track} @@ -596,10 +596,10 @@ func TestServerPublishErrorRecordPartialTracks(t *testing.T) { defer conn.Close() bconn := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) - track1, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track1, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) - track2, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track2, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := Tracks{track1, track2} @@ -749,7 +749,7 @@ func TestServerPublish(t *testing.T) { <-connOpened - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := Tracks{track} @@ -937,7 +937,7 @@ func TestServerPublishErrorInvalidProtocol(t *testing.T) { defer conn.Close() bconn := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := Tracks{track} @@ -1038,7 +1038,7 @@ func TestServerPublishRTCPReport(t *testing.T) { defer conn.Close() bconn := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := Tracks{track} @@ -1197,7 +1197,7 @@ func TestServerPublishTimeout(t *testing.T) { defer nconn.Close() bconn := bufio.NewReadWriter(bufio.NewReader(nconn), bufio.NewWriter(nconn)) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := Tracks{track} @@ -1324,7 +1324,7 @@ func TestServerPublishWithoutTeardown(t *testing.T) { require.NoError(t, err) bconn := bufio.NewReadWriter(bufio.NewReader(nconn), bufio.NewWriter(nconn)) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := Tracks{track} @@ -1443,7 +1443,7 @@ func TestServerPublishUDPChangeConn(t *testing.T) { defer conn.Close() bconn := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := Tracks{track} diff --git a/server_read_test.go b/server_read_test.go index 644d4198..55aa8edf 100644 --- a/server_read_test.go +++ b/server_read_test.go @@ -89,7 +89,7 @@ func TestServerReadSetupPath(t *testing.T) { }, } { t.Run(ca.name, func(t *testing.T) { - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) stream := NewServerStream(Tracks{track, track, track, track, track}) @@ -145,7 +145,7 @@ func TestServerReadSetupPath(t *testing.T) { func TestServerReadErrorSetupDifferentPaths(t *testing.T) { connClosed := make(chan struct{}) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) stream := NewServerStream(Tracks{track}) @@ -217,7 +217,7 @@ func TestServerReadErrorSetupDifferentPaths(t *testing.T) { func TestServerReadErrorSetupTrackTwice(t *testing.T) { connClosed := make(chan struct{}) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) stream := NewServerStream(Tracks{track}) @@ -300,7 +300,7 @@ func TestServerRead(t *testing.T) { sessionClosed := make(chan struct{}) framesReceived := make(chan struct{}) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) stream := NewServerStream(Tracks{track}) @@ -593,7 +593,7 @@ func TestServerReadTCPResponseBeforeFrames(t *testing.T) { writerDone := make(chan struct{}) writerTerminate := make(chan struct{}) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) stream := NewServerStream(Tracks{track}) @@ -686,7 +686,7 @@ func TestServerReadTCPResponseBeforeFrames(t *testing.T) { } func TestServerReadPlayPlay(t *testing.T) { - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) stream := NewServerStream(Tracks{track}) @@ -767,7 +767,7 @@ func TestServerReadPlayPausePlay(t *testing.T) { writerDone := make(chan struct{}) writerTerminate := make(chan struct{}) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) stream := NewServerStream(Tracks{track}) @@ -884,7 +884,7 @@ func TestServerReadPlayPausePause(t *testing.T) { writerDone := make(chan struct{}) writerTerminate := make(chan struct{}) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) stream := NewServerStream(Tracks{track}) @@ -1008,7 +1008,7 @@ func TestServerReadTimeout(t *testing.T) { t.Run(proto, func(t *testing.T) { sessionClosed := make(chan struct{}) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) stream := NewServerStream(Tracks{track}) @@ -1100,7 +1100,7 @@ func TestServerReadWithoutTeardown(t *testing.T) { connClosed := make(chan struct{}) sessionClosed := make(chan struct{}) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) stream := NewServerStream(Tracks{track}) @@ -1197,7 +1197,7 @@ func TestServerReadWithoutTeardown(t *testing.T) { } func TestServerReadUDPChangeConn(t *testing.T) { - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) stream := NewServerStream(Tracks{track}) @@ -1295,7 +1295,7 @@ func TestServerReadUDPChangeConn(t *testing.T) { } func TestServerReadNonSetuppedPath(t *testing.T) { - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) stream := NewServerStream(Tracks{track}) @@ -1459,7 +1459,7 @@ func TestServerReadAdditionalInfos(t *testing.T) { return &ri, ssrcs } - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) stream := NewServerStream(Tracks{track, track}) diff --git a/server_test.go b/server_test.go index f5bb90d5..af882f67 100644 --- a/server_test.go +++ b/server_test.go @@ -633,7 +633,7 @@ func TestServerErrorInvalidMethod(t *testing.T) { } func TestServerErrorTCPTwoConnOneSession(t *testing.T) { - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) stream := NewServerStream(Tracks{track}) @@ -730,7 +730,7 @@ func TestServerErrorTCPTwoConnOneSession(t *testing.T) { } func TestServerErrorTCPOneConnTwoSessions(t *testing.T) { - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) stream := NewServerStream(Tracks{track}) @@ -989,7 +989,7 @@ func TestServerSessionClose(t *testing.T) { func TestServerSessionAutoClose(t *testing.T) { sessionClosed := make(chan struct{}) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) stream := NewServerStream(Tracks{track}) @@ -1055,7 +1055,7 @@ func TestServerErrorInvalidPath(t *testing.T) { t.Run(string(method), func(t *testing.T) { connClosed := make(chan struct{}) - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) stream := NewServerStream(Tracks{track}) @@ -1096,7 +1096,7 @@ func TestServerErrorInvalidPath(t *testing.T) { sxID := "" if method == base.Record { - track, err := NewTrackH264(96, []byte("123456"), []byte("123456")) + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) tracks := Tracks{track} diff --git a/track.go b/track.go index 171b4298..fcf81aff 100644 --- a/track.go +++ b/track.go @@ -20,6 +20,20 @@ type Track struct { Media *psdp.MediaDescription } +// TrackConfigH264 is the configuration of an H264 track. +type TrackConfigH264 struct { + SPS []byte + PPS []byte +} + +// TrackConfigAAC is the configuration of an AAC track. +type TrackConfigAAC struct { + Type int + SampleRate int + ChannelCount int + AOTSpecificConfig []byte +} + func (t *Track) hasControlAttribute() bool { for _, attr := range t.Media.Attributes { if attr.Key == "control" { @@ -127,10 +141,10 @@ func (t *Track) ClockRate() (int, error) { } // NewTrackH264 initializes an H264 track from a SPS and PPS. -func NewTrackH264(payloadType uint8, sps []byte, pps []byte) (*Track, error) { - spropParameterSets := base64.StdEncoding.EncodeToString(sps) + - "," + base64.StdEncoding.EncodeToString(pps) - profileLevelID := strings.ToUpper(hex.EncodeToString(sps[1:4])) +func NewTrackH264(payloadType uint8, conf *TrackConfigH264) (*Track, error) { + spropParameterSets := base64.StdEncoding.EncodeToString(conf.SPS) + + "," + base64.StdEncoding.EncodeToString(conf.PPS) + profileLevelID := strings.ToUpper(hex.EncodeToString(conf.SPS[1:4])) typ := strconv.FormatInt(int64(payloadType), 10) @@ -176,21 +190,18 @@ func (t *Track) IsH264() bool { return vals[1] == "H264/90000" } -// ExtractDataH264 extracts the SPS and PPS from an H264 track. -func (t *Track) ExtractDataH264() ([]byte, []byte, error) { +// ExtractConfigH264 extracts the configuration from an H264 track. +func (t *Track) ExtractConfigH264() (*TrackConfigH264, error) { v, ok := t.Media.Attribute("fmtp") if !ok { - return nil, nil, fmt.Errorf("fmtp attribute is missing") + return nil, fmt.Errorf("fmtp attribute is missing") } tmp := strings.SplitN(v, " ", 2) if len(tmp) != 2 { - return nil, nil, fmt.Errorf("invalid fmtp attribute (%v)", v) + return nil, fmt.Errorf("invalid fmtp attribute (%v)", v) } - var sps []byte - var pps []byte - for _, kv := range strings.Split(tmp[1], ";") { kv = strings.Trim(kv, " ") @@ -200,41 +211,47 @@ func (t *Track) ExtractDataH264() ([]byte, []byte, error) { tmp := strings.SplitN(kv, "=", 2) if len(tmp) != 2 { - return nil, nil, fmt.Errorf("invalid fmtp attribute (%v)", v) + return nil, fmt.Errorf("invalid fmtp attribute (%v)", v) } if tmp[0] == "sprop-parameter-sets" { tmp := strings.SplitN(tmp[1], ",", 2) if len(tmp) != 2 { - return nil, nil, fmt.Errorf("invalid sprop-parameter-sets (%v)", v) + return nil, fmt.Errorf("invalid sprop-parameter-sets (%v)", v) } - var err error - sps, err = base64.StdEncoding.DecodeString(tmp[0]) + sps, err := base64.StdEncoding.DecodeString(tmp[0]) if err != nil { - return nil, nil, fmt.Errorf("invalid sprop-parameter-sets (%v)", v) + return nil, fmt.Errorf("invalid sprop-parameter-sets (%v)", v) } - pps, err = base64.StdEncoding.DecodeString(tmp[1]) + pps, err := base64.StdEncoding.DecodeString(tmp[1]) if err != nil { - return nil, nil, fmt.Errorf("invalid sprop-parameter-sets (%v)", v) + return nil, fmt.Errorf("invalid sprop-parameter-sets (%v)", v) } + + conf := &TrackConfigH264{ + SPS: sps, + PPS: pps, + } + + return conf, nil } } - if sps == nil || pps == nil { - return nil, nil, fmt.Errorf("sprop-parameter-sets is missing (%v)", v) - } - - return sps, pps, nil + return nil, fmt.Errorf("sprop-parameter-sets is missing (%v)", v) } -// NewTrackAAC initializes an AAC track from a configuration. -func NewTrackAAC(payloadType uint8, config []byte) (*Track, error) { - var conf rtpaac.MPEG4AudioConfig - err := conf.Decode(config) +// NewTrackAAC initializes an AAC track. +func NewTrackAAC(payloadType uint8, conf *TrackConfigAAC) (*Track, error) { + mpegConf, err := rtpaac.MPEG4AudioConfig{ + Type: rtpaac.MPEG4AudioType(conf.Type), + SampleRate: conf.SampleRate, + ChannelCount: conf.ChannelCount, + AOTSpecificConfig: conf.AOTSpecificConfig, + }.Encode() if err != nil { - return nil, fmt.Errorf("invalid MPEG-4 Audio config: %v", err) + return nil, err } typ := strconv.FormatInt(int64(payloadType), 10) @@ -259,7 +276,7 @@ func NewTrackAAC(payloadType uint8, config []byte) (*Track, error) { "sizelength=13; " + "indexlength=3; " + "indexdeltalength=3; " + - "config=" + hex.EncodeToString(config), + "config=" + hex.EncodeToString(mpegConf), }, }, }, @@ -285,8 +302,8 @@ func (t *Track) IsAAC() bool { return strings.HasPrefix(strings.ToLower(vals[1]), "mpeg4-generic/") } -// ExtractDataAAC extracts the config from an AAC track. -func (t *Track) ExtractDataAAC() ([]byte, error) { +// ExtractConfigAAC extracts the configuration from an AAC track. +func (t *Track) ExtractConfigAAC() (*TrackConfigAAC, error) { v, ok := t.Media.Attribute("fmtp") if !ok { return nil, fmt.Errorf("fmtp attribute is missing") @@ -297,8 +314,6 @@ func (t *Track) ExtractDataAAC() ([]byte, error) { return nil, fmt.Errorf("invalid fmtp (%v)", v) } - var config []byte - for _, kv := range strings.Split(tmp[1], ";") { kv = strings.Trim(kv, " ") @@ -312,19 +327,29 @@ func (t *Track) ExtractDataAAC() ([]byte, error) { } if tmp[0] == "config" { - var err error - config, err = hex.DecodeString(tmp[1]) + enc, err := hex.DecodeString(tmp[1]) if err != nil { - return nil, fmt.Errorf("invalid config (%v)", v) + return nil, fmt.Errorf("invalid AAC config (%v)", tmp[1]) } + + var mpegConf rtpaac.MPEG4AudioConfig + err = mpegConf.Decode(enc) + if err != nil { + return nil, fmt.Errorf("invalid AAC config (%v)", tmp[1]) + } + + conf := &TrackConfigAAC{ + Type: int(mpegConf.Type), + SampleRate: mpegConf.SampleRate, + ChannelCount: mpegConf.ChannelCount, + AOTSpecificConfig: mpegConf.AOTSpecificConfig, + } + + return conf, nil } } - if config == nil { - return nil, fmt.Errorf("config is missing (%v)", v) - } - - return config, nil + return nil, fmt.Errorf("config is missing (%v)", v) } // Tracks is a list of tracks. diff --git a/track_test.go b/track_test.go index a47cd9a4..4955c8eb 100644 --- a/track_test.go +++ b/track_test.go @@ -225,7 +225,7 @@ func TestTrackH264New(t *testing.T) { 0x68, 0xee, 0x3c, 0x80, } - tr, err := NewTrackH264(96, sps, pps) + tr, err := NewTrackH264(96, &TrackConfigH264{sps, pps}) require.NoError(t, err) require.Equal(t, &Track{ Media: &psdp.MediaDescription{ @@ -282,12 +282,11 @@ func TestTrackIsH264(t *testing.T) { } } -func TestTrackH264Extract(t *testing.T) { +func TestTrackExtractConfigH264(t *testing.T) { for _, ca := range []struct { name string track *Track - sps []byte - pps []byte + conf *TrackConfigH264 }{ { "generic", @@ -310,13 +309,15 @@ func TestTrackH264Extract(t *testing.T) { }, }, }, - []byte{ - 0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0, - 0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, - 0x00, 0x03, 0x00, 0x3d, 0x08, - }, - []byte{ - 0x68, 0xee, 0x3c, 0x80, + &TrackConfigH264{ + SPS: []byte{ + 0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0, + 0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, + 0x00, 0x03, 0x00, 0x3d, 0x08, + }, + PPS: []byte{ + 0x68, 0xee, 0x3c, 0x80, + }, }, }, { @@ -340,27 +341,28 @@ func TestTrackH264Extract(t *testing.T) { }, }, }, - []byte{ - 0x67, 0x64, 0x00, 0x1f, 0xac, 0xd9, 0x40, 0x50, - 0x05, 0xbb, 0x01, 0x6c, 0x80, 0x00, 0x00, 0x03, - 0x00, 0x80, 0x00, 0x00, 0x1e, 0x07, 0x8c, 0x18, - 0xcb, - }, - []byte{ - 0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0, + &TrackConfigH264{ + SPS: []byte{ + 0x67, 0x64, 0x00, 0x1f, 0xac, 0xd9, 0x40, 0x50, + 0x05, 0xbb, 0x01, 0x6c, 0x80, 0x00, 0x00, 0x03, + 0x00, 0x80, 0x00, 0x00, 0x1e, 0x07, 0x8c, 0x18, + 0xcb, + }, + PPS: []byte{ + 0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0, + }, }, }, } { t.Run(ca.name, func(t *testing.T) { - sps, pps, err := ca.track.ExtractDataH264() + conf, err := ca.track.ExtractConfigH264() require.NoError(t, err) - require.Equal(t, ca.sps, sps) - require.Equal(t, ca.pps, pps) + require.Equal(t, ca.conf, conf) }) } } -func TestTrackH264ExtractErrors(t *testing.T) { +func TestTrackConfigH264Errors(t *testing.T) { for _, ca := range []struct { name string track *Track @@ -525,14 +527,14 @@ func TestTrackH264ExtractErrors(t *testing.T) { }, } { t.Run(ca.name, func(t *testing.T) { - _, _, err := ca.track.ExtractDataH264() + _, err := ca.track.ExtractConfigH264() require.Equal(t, ca.err, err.Error()) }) } } func TestTrackAACNew(t *testing.T) { - tr, err := NewTrackAAC(96, []byte{17, 144}) + track, err := NewTrackAAC(96, &TrackConfigAAC{Type: 2, SampleRate: 48000, ChannelCount: 2}) require.NoError(t, err) require.Equal(t, &Track{ Media: &psdp.MediaDescription{ @@ -552,7 +554,7 @@ func TestTrackAACNew(t *testing.T) { }, }, }, - }, tr) + }, track) } func TestTrackIsAAC(t *testing.T) { @@ -611,11 +613,11 @@ func TestTrackIsAAC(t *testing.T) { } } -func TestTrackAACExtract(t *testing.T) { +func TestTrackExtractConfigAAC(t *testing.T) { for _, ca := range []struct { - name string - track *Track - config []byte + name string + track *Track + conf *TrackConfigAAC }{ { "generic", @@ -638,7 +640,11 @@ func TestTrackAACExtract(t *testing.T) { }, }, }, - []byte{17, 144}, + &TrackConfigAAC{ + Type: 2, + SampleRate: 48000, + ChannelCount: 2, + }, }, { "vlc rtsp server", @@ -661,18 +667,22 @@ func TestTrackAACExtract(t *testing.T) { }, }, }, - []byte{17, 144}, + &TrackConfigAAC{ + Type: 2, + SampleRate: 48000, + ChannelCount: 2, + }, }, } { t.Run(ca.name, func(t *testing.T) { - config, err := ca.track.ExtractDataAAC() + conf, err := ca.track.ExtractConfigAAC() require.NoError(t, err) - require.Equal(t, ca.config, config) + require.Equal(t, ca.conf, conf) }) } } -func TestTrackAACExtractErrors(t *testing.T) { +func TestTrackConfigAACErrors(t *testing.T) { for _, ca := range []struct { name string track *Track @@ -787,11 +797,11 @@ func TestTrackAACExtractErrors(t *testing.T) { }, }, }, - "invalid config (96 profile-level-id=1; config=zz)", + "invalid AAC config (zz)", }, } { t.Run(ca.name, func(t *testing.T) { - _, err := ca.track.ExtractDataAAC() + _, err := ca.track.ExtractConfigAAC() require.Equal(t, ca.err, err.Error()) }) }