Browse Source

srt, udp: support reading and writing MPEG-1 audio streams (#2147)

pull/2150/head
Alessandro Ros 3 years ago committed by GitHub
parent
commit
8bb71ac8d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      README.md
  2. 8
      go.mod
  3. 16
      go.sum
  4. 3
      internal/core/api_test.go
  5. 4
      internal/core/hls_source.go
  6. 2
      internal/core/hls_source_test.go
  7. 22
      internal/core/rtmp_conn.go
  8. 6
      internal/core/rtmp_source.go
  9. 137
      internal/core/srt_conn.go
  10. 1
      internal/core/srt_server_test.go
  11. 29
      internal/core/srt_source.go
  12. 1
      internal/core/srt_source_test.go
  13. 28
      internal/core/udp_source.go
  14. 36
      internal/formatprocessor/mpeg1audio.go
  15. 4
      internal/formatprocessor/processor.go
  16. 10
      internal/rtmp/message/audio.go
  17. 4
      internal/rtmp/message/reader_test.go
  18. 14
      internal/rtmp/reader.go
  19. 22
      internal/rtmp/writer.go

8
README.md

@ -20,8 +20,8 @@ Live streams can be published to the server with:
|protocol|variants|video codecs|audio codecs| |protocol|variants|video codecs|audio codecs|
|--------|--------|------------|------------| |--------|--------|------------|------------|
|[SRT clients](#srt-clients)||H265, H264|Opus, MPEG-4 Audio (AAC)| |[SRT clients](#srt-clients)||H265, H264|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3)|
|[SRT servers](#srt-servers)||H265, H264|Opus, MPEG-4 Audio (AAC)| |[SRT servers](#srt-servers)||H265, H264|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3)|
|[WebRTC clients](#webrtc-clients)|Browser-based, WHIP|AV1, VP9, VP8, H264|Opus, G722, G711| |[WebRTC clients](#webrtc-clients)|Browser-based, WHIP|AV1, VP9, VP8, H264|Opus, G722, G711|
|[WebRTC servers](#webrtc-servers)|WHEP|AV1, VP9, VP8, H264|Opus, G722, G711| |[WebRTC servers](#webrtc-servers)|WHEP|AV1, VP9, VP8, H264|Opus, G722, G711|
|[RTSP clients](#rtsp-clients)|UDP, TCP, RTSPS|AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, M-JPEG and any RTP-compatible codec|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), G726, G722, G711, LPCM and any RTP-compatible codec| |[RTSP clients](#rtsp-clients)|UDP, TCP, RTSPS|AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, M-JPEG and any RTP-compatible codec|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), G726, G722, G711, LPCM and any RTP-compatible codec|
@ -29,14 +29,14 @@ Live streams can be published to the server with:
|[RTMP clients](#rtmp-clients)|RTMP, RTMPS, Enhanced RTMP|AV1, H265, H264|MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3)| |[RTMP clients](#rtmp-clients)|RTMP, RTMPS, Enhanced RTMP|AV1, H265, H264|MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3)|
|[RTMP cameras and servers](#rtmp-cameras-and-servers)|RTMP, RTMPS, Enhanced RTMP|H264|MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3)| |[RTMP cameras and servers](#rtmp-cameras-and-servers)|RTMP, RTMPS, Enhanced RTMP|H264|MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3)|
|[HLS cameras and servers](#hls-cameras-and-servers)|Low-Latency HLS, MP4-based HLS, legacy HLS|H265, H264|Opus, MPEG-4 Audio (AAC)| |[HLS cameras and servers](#hls-cameras-and-servers)|Low-Latency HLS, MP4-based HLS, legacy HLS|H265, H264|Opus, MPEG-4 Audio (AAC)|
|[UDP/MPEG-TS](#udpmpeg-ts)|Unicast, broadcast, multicast|H265, H264|Opus, MPEG-4 Audio (AAC)| |[UDP/MPEG-TS](#udpmpeg-ts)|Unicast, broadcast, multicast|H265, H264|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3)|
|[Raspberry Pi Cameras](#raspberry-pi-cameras)||H264|| |[Raspberry Pi Cameras](#raspberry-pi-cameras)||H264||
And can be read from the server with: And can be read from the server with:
|protocol|variants|video codecs|audio codecs| |protocol|variants|video codecs|audio codecs|
|--------|--------|------------|------------| |--------|--------|------------|------------|
|[SRT](#srt)||H265, H264|Opus, MPEG-4 Audio (AAC)| |[SRT](#srt)||H265, H264|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3)|
|[WebRTC](#webrtc)|Browser-based, WHEP|AV1, VP9, VP8, H264|Opus, G722, G711| |[WebRTC](#webrtc)|Browser-based, WHEP|AV1, VP9, VP8, H264|Opus, G722, G711|
|[RTSP](#rtsp)|UDP, UDP-Multicast, TCP, RTSPS|AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, M-JPEG and any RTP-compatible codec|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), G726, G722, G711, LPCM and any RTP-compatible codec| |[RTSP](#rtsp)|UDP, UDP-Multicast, TCP, RTSPS|AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, M-JPEG and any RTP-compatible codec|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), G726, G722, G711, LPCM and any RTP-compatible codec|
|[RTMP](#rtmp)|RTMP, RTMPS, Enhanced RTMP|H264|MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3)| |[RTMP](#rtmp)|RTMP, RTMPS, Enhanced RTMP|H264|MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3)|

8
go.mod

@ -6,9 +6,9 @@ require (
code.cloudfoundry.org/bytefmt v0.0.0 code.cloudfoundry.org/bytefmt v0.0.0
github.com/abema/go-mp4 v0.11.0 github.com/abema/go-mp4 v0.11.0
github.com/alecthomas/kong v0.8.0 github.com/alecthomas/kong v0.8.0
github.com/bluenviron/gohlslib v0.3.1-0.20230730162911-eb9f86511072 github.com/bluenviron/gohlslib v0.3.1-0.20230805121442-9577da1cb068
github.com/bluenviron/gortsplib/v3 v3.9.1-0.20230801115353-f296099f26d5 github.com/bluenviron/gortsplib/v3 v3.9.1-0.20230805122812-17acec3f0dea
github.com/bluenviron/mediacommon v0.7.1-0.20230730144331-10b74a4f6eda github.com/bluenviron/mediacommon v0.7.1-0.20230805114828-bee33f3b286d
github.com/datarhei/gosrt v0.5.3 github.com/datarhei/gosrt v0.5.3
github.com/fsnotify/fsnotify v1.6.0 github.com/fsnotify/fsnotify v1.6.0
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.9.1
@ -33,7 +33,7 @@ require (
require ( require (
github.com/aler9/writerseeker v0.0.0-20220601075008-6f0e685b9c82 // indirect github.com/aler9/writerseeker v0.0.0-20220601075008-6f0e685b9c82 // indirect
github.com/asticode/go-astikit v0.30.0 // indirect github.com/asticode/go-astikit v0.30.0 // indirect
github.com/asticode/go-astits v1.11.1-0.20230727094110-0df190a2dd87 // indirect github.com/asticode/go-astits v1.12.0 // indirect
github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c // indirect github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c // indirect
github.com/bytedance/sonic v1.9.1 // indirect github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect

16
go.sum

@ -10,16 +10,16 @@ github.com/aler9/writerseeker v0.0.0-20220601075008-6f0e685b9c82 h1:9WgSzBLo3a9T
github.com/aler9/writerseeker v0.0.0-20220601075008-6f0e685b9c82/go.mod h1:qsMrZCbeBf/mCLOeF16KDkPu4gktn/pOWyaq1aYQE7U= github.com/aler9/writerseeker v0.0.0-20220601075008-6f0e685b9c82/go.mod h1:qsMrZCbeBf/mCLOeF16KDkPu4gktn/pOWyaq1aYQE7U=
github.com/asticode/go-astikit v0.30.0 h1:DkBkRQRIxYcknlaU7W7ksNfn4gMFsB0tqMJflxkRsZA= github.com/asticode/go-astikit v0.30.0 h1:DkBkRQRIxYcknlaU7W7ksNfn4gMFsB0tqMJflxkRsZA=
github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0= github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
github.com/asticode/go-astits v1.11.1-0.20230727094110-0df190a2dd87 h1:SCAqalLhgKGDghGz03yYVWr8TavHluP/i7IwshKU9yA= github.com/asticode/go-astits v1.12.0 h1:BiefTgVEyPgEB8nT6J+Sys/uxE4H/a04SW/aedpOpPc=
github.com/asticode/go-astits v1.11.1-0.20230727094110-0df190a2dd87/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI= github.com/asticode/go-astits v1.12.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI=
github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c h1:8XZeJrs4+ZYhJeJ2aZxADI2tGADS15AzIF8MQ8XAhT4= github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c h1:8XZeJrs4+ZYhJeJ2aZxADI2tGADS15AzIF8MQ8XAhT4=
github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c/go.mod h1:x1vxHcL/9AVzuk5HOloOEPrtJY0MaalYr78afXZ+pWI= github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c/go.mod h1:x1vxHcL/9AVzuk5HOloOEPrtJY0MaalYr78afXZ+pWI=
github.com/bluenviron/gohlslib v0.3.1-0.20230730162911-eb9f86511072 h1:pAbC7frXsTMxP7Ck3E50hl7oFeSeD2dgc2lWjmHXztQ= github.com/bluenviron/gohlslib v0.3.1-0.20230805121442-9577da1cb068 h1:mNSdk14esA9ggCP0+gDmcG9wLvb7QzHLta071nTLYe4=
github.com/bluenviron/gohlslib v0.3.1-0.20230730162911-eb9f86511072/go.mod h1:rK4b161qErs82QqvBEl84vpi2xhdZBUT0yubXuytZ7E= github.com/bluenviron/gohlslib v0.3.1-0.20230805121442-9577da1cb068/go.mod h1:DouD/43mEaXgwbeQZdtVhzRnA7RuKFYeaVPz6tj/Rfk=
github.com/bluenviron/gortsplib/v3 v3.9.1-0.20230801115353-f296099f26d5 h1:d50XiZLfcoTWfPU/FWSk0hN8qWrqv6hqp02vBZQFnJk= github.com/bluenviron/gortsplib/v3 v3.9.1-0.20230805122812-17acec3f0dea h1:AK6GuUoXDaGfk3HDe6qMJ/Swmj1hIxVtMuw98sa2VO8=
github.com/bluenviron/gortsplib/v3 v3.9.1-0.20230801115353-f296099f26d5/go.mod h1:reIJG1PKHiUwNa8NZQAyUmprtR8jeGZEnzU958rDgH4= github.com/bluenviron/gortsplib/v3 v3.9.1-0.20230805122812-17acec3f0dea/go.mod h1:9GOyv9dKyDjMHXcrh7rytIoXWvRk+/DC0Mjq3vqEyCU=
github.com/bluenviron/mediacommon v0.7.1-0.20230730144331-10b74a4f6eda h1:+ungCWRNDjsy/CVL1l/UjAj4vYL4+NIJQoJJWbR3Xw8= github.com/bluenviron/mediacommon v0.7.1-0.20230805114828-bee33f3b286d h1:ye0ze/XtVq23NwgxV/PWcjtagcZXTKZq+V6ZBT0HoTY=
github.com/bluenviron/mediacommon v0.7.1-0.20230730144331-10b74a4f6eda/go.mod h1:tfk0qGPhqnOxVCrElu8ct3LKQn6Cj4Tpu3zbbJBTKj4= github.com/bluenviron/mediacommon v0.7.1-0.20230805114828-bee33f3b286d/go.mod h1:8Y0rvMJDUCgqDegYrUxG1rbumB6DV0TZ98Rw9mowIrA=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=

3
internal/core/api_test.go

@ -677,7 +677,6 @@ func TestAPIProtocolList(t *testing.T) {
defer conn.Close() defer conn.Close()
track := &mpegts.Track{ track := &mpegts.Track{
PID: 256,
Codec: &mpegts.CodecH264{}, Codec: &mpegts.CodecH264{},
} }
@ -958,7 +957,6 @@ func TestAPIProtocolGet(t *testing.T) {
defer conn.Close() defer conn.Close()
track := &mpegts.Track{ track := &mpegts.Track{
PID: 256,
Codec: &mpegts.CodecH264{}, Codec: &mpegts.CodecH264{},
} }
@ -1230,7 +1228,6 @@ func TestAPIProtocolKick(t *testing.T) {
defer conn.Close() defer conn.Close()
track := &mpegts.Track{ track := &mpegts.Track{
PID: 256,
Codec: &mpegts.CodecH264{}, Codec: &mpegts.CodecH264{},
} }

4
internal/core/hls_source.go

@ -130,7 +130,7 @@ func (s *hlsSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan
}}, }},
} }
c.OnDataMPEG4Audio(track, func(pts time.Duration, dts time.Duration, aus [][]byte) { c.OnDataMPEG4Audio(track, func(pts time.Duration, aus [][]byte) {
stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitMPEG4AudioGeneric{ stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitMPEG4AudioGeneric{
BaseUnit: formatprocessor.BaseUnit{ BaseUnit: formatprocessor.BaseUnit{
NTP: time.Now(), NTP: time.Now(),
@ -149,7 +149,7 @@ func (s *hlsSource) run(ctx context.Context, cnf *conf.PathConf, reloadConf chan
}}, }},
} }
c.OnDataOpus(track, func(pts time.Duration, dts time.Duration, packets [][]byte) { c.OnDataOpus(track, func(pts time.Duration, packets [][]byte) {
stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitOpus{ stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitOpus{
BaseUnit: formatprocessor.BaseUnit{ BaseUnit: formatprocessor.BaseUnit{
NTP: time.Now(), NTP: time.Now(),

2
internal/core/hls_source_test.go

@ -20,12 +20,10 @@ import (
) )
var track1 = &mpegts.Track{ var track1 = &mpegts.Track{
PID: 256,
Codec: &mpegts.CodecH264{}, Codec: &mpegts.CodecH264{},
} }
var track2 = &mpegts.Track{ var track2 = &mpegts.Track{
PID: 257,
Codec: &mpegts.CodecMPEG4Audio{ Codec: &mpegts.CodecMPEG4Audio{
Config: mpeg4audio.Config{ Config: mpeg4audio.Config{
Type: 2, Type: 2,

22
internal/core/rtmp_conn.go

@ -14,7 +14,7 @@ import (
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v3/pkg/media"
"github.com/bluenviron/gortsplib/v3/pkg/ringbuffer" "github.com/bluenviron/gortsplib/v3/pkg/ringbuffer"
"github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/codecs/h264"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg2audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg1audio"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/google/uuid" "github.com/google/uuid"
@ -512,16 +512,16 @@ func (c *rtmpConn) setupAudio(
return audioMedia, audioFormatMPEG4AudioLATM return audioMedia, audioFormatMPEG4AudioLATM
} }
var audioFormatMPEG2 *formats.MPEG2Audio var audioFormatMPEG1 *formats.MPEG1Audio
audioMedia = stream.Medias().FindFormat(&audioFormatMPEG2) audioMedia = stream.Medias().FindFormat(&audioFormatMPEG1)
if audioMedia != nil { if audioMedia != nil {
startPTSFilled := false startPTSFilled := false
var startPTS time.Duration var startPTS time.Duration
stream.AddReader(c, audioMedia, audioFormatMPEG2, func(unit formatprocessor.Unit) { stream.AddReader(c, audioMedia, audioFormatMPEG1, func(unit formatprocessor.Unit) {
ringBuffer.Push(func() error { ringBuffer.Push(func() error {
tunit := unit.(*formatprocessor.UnitMPEG2Audio) tunit := unit.(*formatprocessor.UnitMPEG1Audio)
if !startPTSFilled { if !startPTSFilled {
startPTSFilled = true startPTSFilled = true
@ -541,7 +541,7 @@ func (c *rtmpConn) setupAudio(
} }
for _, frame := range tunit.Frames { for _, frame := range tunit.Frames {
var h mpeg2audio.FrameHeader var h mpeg1audio.FrameHeader
err := h.Unmarshal(frame) err := h.Unmarshal(frame)
if err != nil { if err != nil {
return err return err
@ -552,7 +552,7 @@ func (c *rtmpConn) setupAudio(
} }
c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout))) c.nconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
err = (*w).WriteMPEG2Audio(pts, &h, frame) err = (*w).WriteMPEG1Audio(pts, &h, frame)
if err != nil { if err != nil {
return err return err
} }
@ -565,7 +565,7 @@ func (c *rtmpConn) setupAudio(
}) })
}) })
return audioMedia, audioFormatMPEG2 return audioMedia, audioFormatMPEG1
} }
return nil, nil return nil, nil
@ -674,9 +674,9 @@ func (c *rtmpConn) runPublish(conn *rtmp.Conn, u *url.URL) error {
}) })
}) })
case *formats.MPEG2Audio: case *formats.MPEG1Audio:
r.OnDataMPEG2Audio(func(pts time.Duration, frame []byte) { r.OnDataMPEG1Audio(func(pts time.Duration, frame []byte) {
stream.WriteUnit(audioMedia, audioFormat, &formatprocessor.UnitMPEG2Audio{ stream.WriteUnit(audioMedia, audioFormat, &formatprocessor.UnitMPEG1Audio{
BaseUnit: formatprocessor.BaseUnit{ BaseUnit: formatprocessor.BaseUnit{
NTP: time.Now(), NTP: time.Now(),
}, },

6
internal/core/rtmp_source.go

@ -160,9 +160,9 @@ func (s *rtmpSource) runReader(u *url.URL, nconn net.Conn) error {
}) })
}) })
case *formats.MPEG2Audio: case *formats.MPEG1Audio:
mc.OnDataMPEG2Audio(func(pts time.Duration, frame []byte) { mc.OnDataMPEG1Audio(func(pts time.Duration, frame []byte) {
stream.WriteUnit(audioMedia, audioFormat, &formatprocessor.UnitMPEG2Audio{ stream.WriteUnit(audioMedia, audioFormat, &formatprocessor.UnitMPEG1Audio{
BaseUnit: formatprocessor.BaseUnit{ BaseUnit: formatprocessor.BaseUnit{
NTP: time.Now(), NTP: time.Now(),
}, },

137
internal/core/srt_conn.go

@ -308,7 +308,7 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
}}, }},
} }
r.OnDataMPEG4Audio(track, func(pts int64, _ int64, aus [][]byte) error { r.OnDataMPEG4Audio(track, func(pts int64, aus [][]byte) error {
stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitMPEG4AudioGeneric{ stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitMPEG4AudioGeneric{
BaseUnit: formatprocessor.BaseUnit{ BaseUnit: formatprocessor.BaseUnit{
NTP: time.Now(), NTP: time.Now(),
@ -328,7 +328,7 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
}}, }},
} }
r.OnDataOpus(track, func(pts int64, _ int64, packets [][]byte) error { r.OnDataOpus(track, func(pts int64, packets [][]byte) error {
stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitOpus{ stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitOpus{
BaseUnit: formatprocessor.BaseUnit{ BaseUnit: formatprocessor.BaseUnit{
NTP: time.Now(), NTP: time.Now(),
@ -338,11 +338,35 @@ func (c *srtConn) runPublishReader(sconn srt.Conn, path *path) error {
}) })
return nil return nil
}) })
case *mpegts.CodecMPEG1Audio:
medi = &media.Media{
Type: media.TypeAudio,
Formats: []formats.Format{&formats.MPEG1Audio{}},
}
r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error {
stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitMPEG1Audio{
BaseUnit: formatprocessor.BaseUnit{
NTP: time.Now(),
},
PTS: decodeTime(pts),
Frames: frames,
})
return nil
})
default:
continue
} }
medias = append(medias, medi) medias = append(medias, medi)
} }
if len(medias) == 0 {
return fmt.Errorf("no supported tracks found")
}
rres := path.startPublisher(pathStartPublisherReq{ rres := path.startPublisher(pathStartPublisherReq{
author: c, author: c,
medias: medias, medias: medias,
@ -410,7 +434,6 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
}() }()
var w *mpegts.Writer var w *mpegts.Writer
nextPID := uint16(256)
var tracks []*mpegts.Track var tracks []*mpegts.Track
var medias media.Medias var medias media.Medias
bw := bufio.NewWriterSize(sconn, srtMaxPayloadSize(c.udpMaxPayloadSize)) bw := bufio.NewWriterSize(sconn, srtMaxPayloadSize(c.udpMaxPayloadSize))
@ -419,17 +442,20 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
leadingTrackInitialized := false leadingTrackInitialized := false
var leadingTrackStartDTS time.Duration var leadingTrackStartDTS time.Duration
addTrack := func(medi *media.Media, codec mpegts.Codec) *mpegts.Track {
track := &mpegts.Track{
Codec: codec,
}
tracks = append(tracks, track)
medias = append(medias, medi)
return track
}
for _, medi := range res.stream.Medias() { for _, medi := range res.stream.Medias() {
for _, format := range medi.Formats { for _, format := range medi.Formats {
switch format := format.(type) { switch format := format.(type) {
case *formats.H265: case *formats.H265:
track := &mpegts.Track{ track := addTrack(medi, &mpegts.CodecH265{})
PID: nextPID,
Codec: &mpegts.CodecH265{},
}
tracks = append(tracks, track)
medias = append(medias, medi)
nextPID++
var startPTS time.Duration var startPTS time.Duration
startPTSFilled := false startPTSFilled := false
@ -493,13 +519,7 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
}) })
case *formats.H264: case *formats.H264:
track := &mpegts.Track{ track := addTrack(medi, &mpegts.CodecH264{})
PID: nextPID,
Codec: &mpegts.CodecH264{},
}
tracks = append(tracks, track)
medias = append(medias, medi)
nextPID++
var startPTS time.Duration var startPTS time.Duration
startPTSFilled := false startPTSFilled := false
@ -563,15 +583,9 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
}) })
case *formats.MPEG4AudioGeneric: case *formats.MPEG4AudioGeneric:
track := &mpegts.Track{ track := addTrack(medi, &mpegts.CodecMPEG4Audio{
PID: nextPID, Config: *format.Config,
Codec: &mpegts.CodecMPEG4Audio{ })
Config: *format.Config,
},
}
tracks = append(tracks, track)
medias = append(medias, medi)
nextPID++
var startPTS time.Duration var startPTS time.Duration
startPTSFilled := false startPTSFilled := false
@ -609,15 +623,9 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
if format.Config != nil && if format.Config != nil &&
len(format.Config.Programs) == 1 && len(format.Config.Programs) == 1 &&
len(format.Config.Programs[0].Layers) == 1 { len(format.Config.Programs[0].Layers) == 1 {
track := &mpegts.Track{ track := addTrack(medi, &mpegts.CodecMPEG4Audio{
PID: nextPID, Config: *format.Config.Programs[0].Layers[0].AudioSpecificConfig,
Codec: &mpegts.CodecMPEG4Audio{ })
Config: *format.Config.Programs[0].Layers[0].AudioSpecificConfig,
},
}
tracks = append(tracks, track)
medias = append(medias, medi)
nextPID++
var startPTS time.Duration var startPTS time.Duration
startPTSFilled := false startPTSFilled := false
@ -653,20 +661,14 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
} }
case *formats.Opus: case *formats.Opus:
track := &mpegts.Track{ track := addTrack(medi, &mpegts.CodecOpus{
PID: nextPID, ChannelCount: func() int {
Codec: &mpegts.CodecOpus{ if format.IsStereo {
ChannelCount: func() int { return 2
if format.IsStereo { }
return 2 return 1
} }(),
return 1 })
}(),
},
}
tracks = append(tracks, track)
medias = append(medias, medi)
nextPID++
var startPTS time.Duration var startPTS time.Duration
startPTSFilled := false startPTSFilled := false
@ -699,6 +701,43 @@ func (c *srtConn) runRead(req srtNewConnReq, pathName string, user string, pass
return bw.Flush() return bw.Flush()
}) })
}) })
case *formats.MPEG1Audio:
// TODO: check whether's a MPEG-1 or MPEG-2 stream by parsing header
// of first frame
track := addTrack(medi, &mpegts.CodecMPEG1Audio{})
var startPTS time.Duration
startPTSFilled := false
res.stream.AddReader(c, medi, format, func(unit formatprocessor.Unit) {
ringBuffer.Push(func() error {
tunit := unit.(*formatprocessor.UnitMPEG1Audio)
if tunit.Frames == nil {
return nil
}
if !startPTSFilled {
startPTS = tunit.PTS
startPTSFilled = true
}
if leadingTrackChosen && !leadingTrackInitialized {
return nil
}
pts := tunit.PTS
pts -= startPTS
pts -= leadingTrackStartDTS
sconn.SetWriteDeadline(time.Now().Add(time.Duration(c.writeTimeout)))
err = w.WriteMPEG1Audio(track, durationGoToMPEGTS(pts), tunit.Frames)
if err != nil {
return err
}
return bw.Flush()
})
})
} }
} }
} }

1
internal/core/srt_server_test.go

@ -28,7 +28,6 @@ func TestSRTServer(t *testing.T) {
defer publisher.Close() defer publisher.Close()
track := &mpegts.Track{ track := &mpegts.Track{
PID: 256,
Codec: &mpegts.CodecH264{}, Codec: &mpegts.CodecH264{},
} }

29
internal/core/srt_source.go

@ -2,6 +2,7 @@ package core
import ( import (
"context" "context"
"fmt"
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v3/pkg/formats"
@ -156,7 +157,7 @@ func (s *srtSource) runReader(sconn srt.Conn) error {
}}, }},
} }
r.OnDataMPEG4Audio(track, func(pts int64, _ int64, aus [][]byte) error { r.OnDataMPEG4Audio(track, func(pts int64, aus [][]byte) error {
stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitMPEG4AudioGeneric{ stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitMPEG4AudioGeneric{
BaseUnit: formatprocessor.BaseUnit{ BaseUnit: formatprocessor.BaseUnit{
NTP: time.Now(), NTP: time.Now(),
@ -176,7 +177,7 @@ func (s *srtSource) runReader(sconn srt.Conn) error {
}}, }},
} }
r.OnDataOpus(track, func(pts int64, _ int64, packets [][]byte) error { r.OnDataOpus(track, func(pts int64, packets [][]byte) error {
stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitOpus{ stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitOpus{
BaseUnit: formatprocessor.BaseUnit{ BaseUnit: formatprocessor.BaseUnit{
NTP: time.Now(), NTP: time.Now(),
@ -186,11 +187,35 @@ func (s *srtSource) runReader(sconn srt.Conn) error {
}) })
return nil return nil
}) })
case *mpegts.CodecMPEG1Audio:
medi = &media.Media{
Type: media.TypeAudio,
Formats: []formats.Format{&formats.MPEG1Audio{}},
}
r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error {
stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitMPEG1Audio{
BaseUnit: formatprocessor.BaseUnit{
NTP: time.Now(),
},
PTS: decodeTime(pts),
Frames: frames,
})
return nil
})
default:
continue
} }
medias = append(medias, medi) medias = append(medias, medi)
} }
if len(medias) == 0 {
return fmt.Errorf("no supported tracks found")
}
res := s.parent.setReady(pathSourceStaticSetReadyReq{ res := s.parent.setReady(pathSourceStaticSetReadyReq{
medias: medias, medias: medias,
generateRTPPackets: true, generateRTPPackets: true,

1
internal/core/srt_source_test.go

@ -38,7 +38,6 @@ func TestSRTSource(t *testing.T) {
defer conn.Close() defer conn.Close()
track := &mpegts.Track{ track := &mpegts.Track{
PID: 256,
Codec: &mpegts.CodecH264{}, Codec: &mpegts.CodecH264{},
} }

28
internal/core/udp_source.go

@ -204,7 +204,7 @@ func (s *udpSource) runReader(pc net.PacketConn) error {
}}, }},
} }
r.OnDataMPEG4Audio(track, func(pts int64, _ int64, aus [][]byte) error { r.OnDataMPEG4Audio(track, func(pts int64, aus [][]byte) error {
stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitMPEG4AudioGeneric{ stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitMPEG4AudioGeneric{
BaseUnit: formatprocessor.BaseUnit{ BaseUnit: formatprocessor.BaseUnit{
NTP: time.Now(), NTP: time.Now(),
@ -224,7 +224,7 @@ func (s *udpSource) runReader(pc net.PacketConn) error {
}}, }},
} }
r.OnDataOpus(track, func(pts int64, _ int64, packets [][]byte) error { r.OnDataOpus(track, func(pts int64, packets [][]byte) error {
stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitOpus{ stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitOpus{
BaseUnit: formatprocessor.BaseUnit{ BaseUnit: formatprocessor.BaseUnit{
NTP: time.Now(), NTP: time.Now(),
@ -234,11 +234,35 @@ func (s *udpSource) runReader(pc net.PacketConn) error {
}) })
return nil return nil
}) })
case *mpegts.CodecMPEG1Audio:
medi = &media.Media{
Type: media.TypeAudio,
Formats: []formats.Format{&formats.MPEG1Audio{}},
}
r.OnDataMPEG1Audio(track, func(pts int64, frames [][]byte) error {
stream.WriteUnit(medi, medi.Formats[0], &formatprocessor.UnitMPEG1Audio{
BaseUnit: formatprocessor.BaseUnit{
NTP: time.Now(),
},
PTS: decodeTime(pts),
Frames: frames,
})
return nil
})
default:
continue
} }
medias = append(medias, medi) medias = append(medias, medi)
} }
if len(medias) == 0 {
return fmt.Errorf("no supported tracks found")
}
res := s.parent.setReady(pathSourceStaticSetReadyReq{ res := s.parent.setReady(pathSourceStaticSetReadyReq{
medias: medias, medias: medias,
generateRTPPackets: true, generateRTPPackets: true,

36
internal/formatprocessor/mpeg2audio.go → internal/formatprocessor/mpeg1audio.go

@ -5,33 +5,33 @@ import (
"time" "time"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v3/pkg/formats"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg2audio" "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg1audio"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/logger"
) )
// UnitMPEG2Audio is a MPEG-1/2 Audio data unit. // UnitMPEG1Audio is a MPEG-1/2 Audio data unit.
type UnitMPEG2Audio struct { type UnitMPEG1Audio struct {
BaseUnit BaseUnit
PTS time.Duration PTS time.Duration
Frames [][]byte Frames [][]byte
} }
type formatProcessorMPEG2Audio struct { type formatProcessorMPEG1Audio struct {
udpMaxPayloadSize int udpMaxPayloadSize int
format *formats.MPEG2Audio format *formats.MPEG1Audio
encoder *rtpmpeg2audio.Encoder encoder *rtpmpeg1audio.Encoder
decoder *rtpmpeg2audio.Decoder decoder *rtpmpeg1audio.Decoder
} }
func newMPEG2Audio( func newMPEG1Audio(
udpMaxPayloadSize int, udpMaxPayloadSize int,
forma *formats.MPEG2Audio, forma *formats.MPEG1Audio,
generateRTPPackets bool, generateRTPPackets bool,
_ logger.Writer, _ logger.Writer,
) (*formatProcessorMPEG2Audio, error) { ) (*formatProcessorMPEG1Audio, error) {
t := &formatProcessorMPEG2Audio{ t := &formatProcessorMPEG1Audio{
udpMaxPayloadSize: udpMaxPayloadSize, udpMaxPayloadSize: udpMaxPayloadSize,
format: forma, format: forma,
} }
@ -46,15 +46,15 @@ func newMPEG2Audio(
return t, nil return t, nil
} }
func (t *formatProcessorMPEG2Audio) createEncoder() error { func (t *formatProcessorMPEG1Audio) createEncoder() error {
t.encoder = &rtpmpeg2audio.Encoder{ t.encoder = &rtpmpeg1audio.Encoder{
PayloadMaxSize: t.udpMaxPayloadSize - 12, PayloadMaxSize: t.udpMaxPayloadSize - 12,
} }
return t.encoder.Init() return t.encoder.Init()
} }
func (t *formatProcessorMPEG2Audio) Process(unit Unit, hasNonRTSPReaders bool) error { //nolint:dupl func (t *formatProcessorMPEG1Audio) Process(unit Unit, hasNonRTSPReaders bool) error { //nolint:dupl
tunit := unit.(*UnitMPEG2Audio) tunit := unit.(*UnitMPEG1Audio)
if tunit.RTPPackets != nil { if tunit.RTPPackets != nil {
pkt := tunit.RTPPackets[0] pkt := tunit.RTPPackets[0]
@ -80,7 +80,7 @@ func (t *formatProcessorMPEG2Audio) Process(unit Unit, hasNonRTSPReaders bool) e
frames, pts, err := t.decoder.Decode(pkt) frames, pts, err := t.decoder.Decode(pkt)
if err != nil { if err != nil {
if err == rtpmpeg2audio.ErrNonStartingPacketAndNoPrevious || err == rtpmpeg2audio.ErrMorePacketsNeeded { if err == rtpmpeg1audio.ErrNonStartingPacketAndNoPrevious || err == rtpmpeg1audio.ErrMorePacketsNeeded {
return nil return nil
} }
return err return err
@ -104,8 +104,8 @@ func (t *formatProcessorMPEG2Audio) Process(unit Unit, hasNonRTSPReaders bool) e
return nil return nil
} }
func (t *formatProcessorMPEG2Audio) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) Unit { func (t *formatProcessorMPEG1Audio) UnitForRTPPacket(pkt *rtp.Packet, ntp time.Time) Unit {
return &UnitMPEG2Audio{ return &UnitMPEG1Audio{
BaseUnit: BaseUnit{ BaseUnit: BaseUnit{
RTPPackets: []*rtp.Packet{pkt}, RTPPackets: []*rtp.Packet{pkt},
NTP: ntp, NTP: ntp,

4
internal/formatprocessor/processor.go

@ -46,8 +46,8 @@ func New(
case *formats.AV1: case *formats.AV1:
return newAV1(udpMaxPayloadSize, forma, generateRTPPackets, log) return newAV1(udpMaxPayloadSize, forma, generateRTPPackets, log)
case *formats.MPEG2Audio: case *formats.MPEG1Audio:
return newMPEG2Audio(udpMaxPayloadSize, forma, generateRTPPackets, log) return newMPEG1Audio(udpMaxPayloadSize, forma, generateRTPPackets, log)
case *formats.MPEG4AudioGeneric: case *formats.MPEG4AudioGeneric:
return newMPEG4AudioGeneric(udpMaxPayloadSize, forma, generateRTPPackets, log) return newMPEG4AudioGeneric(udpMaxPayloadSize, forma, generateRTPPackets, log)

10
internal/rtmp/message/audio.go

@ -14,7 +14,7 @@ const (
// supported audio codecs // supported audio codecs
const ( const (
CodecMPEG2Audio = 2 CodecMPEG1Audio = 2
CodecMPEG4Audio = 10 CodecMPEG4Audio = 10
) )
@ -52,7 +52,7 @@ func (m *Audio) Unmarshal(raw *rawmessage.Message) error {
m.Codec = raw.Body[0] >> 4 m.Codec = raw.Body[0] >> 4
switch m.Codec { switch m.Codec {
case CodecMPEG2Audio, CodecMPEG4Audio: case CodecMPEG1Audio, CodecMPEG4Audio:
default: default:
return fmt.Errorf("unsupported audio codec: %d", m.Codec) return fmt.Errorf("unsupported audio codec: %d", m.Codec)
} }
@ -61,7 +61,7 @@ func (m *Audio) Unmarshal(raw *rawmessage.Message) error {
m.Depth = (raw.Body[0] >> 1) & 0x01 m.Depth = (raw.Body[0] >> 1) & 0x01
m.Channels = raw.Body[0] & 0x01 m.Channels = raw.Body[0] & 0x01
if m.Codec == CodecMPEG2Audio { if m.Codec == CodecMPEG1Audio {
m.Payload = raw.Body[1:] m.Payload = raw.Body[1:]
} else { } else {
m.AACType = AudioAACType(raw.Body[1]) m.AACType = AudioAACType(raw.Body[1])
@ -80,7 +80,7 @@ func (m *Audio) Unmarshal(raw *rawmessage.Message) error {
// Marshal implements Message. // Marshal implements Message.
func (m Audio) Marshal() (*rawmessage.Message, error) { func (m Audio) Marshal() (*rawmessage.Message, error) {
var l int var l int
if m.Codec == CodecMPEG2Audio { if m.Codec == CodecMPEG1Audio {
l = 1 + len(m.Payload) l = 1 + len(m.Payload)
} else { } else {
l = 2 + len(m.Payload) l = 2 + len(m.Payload)
@ -89,7 +89,7 @@ func (m Audio) Marshal() (*rawmessage.Message, error) {
body[0] = m.Codec<<4 | m.Rate<<2 | m.Depth<<1 | m.Channels body[0] = m.Codec<<4 | m.Rate<<2 | m.Depth<<1 | m.Channels
if m.Codec == CodecMPEG2Audio { if m.Codec == CodecMPEG1Audio {
copy(body[1:], m.Payload) copy(body[1:], m.Payload)
} else { } else {
body[1] = uint8(m.AACType) body[1] = uint8(m.AACType)

4
internal/rtmp/message/reader_test.go

@ -27,12 +27,12 @@ var readWriterCases = []struct {
}, },
}, },
{ {
"audio mpeg2", "audio mpeg1",
&Audio{ &Audio{
ChunkStreamID: 7, ChunkStreamID: 7,
DTS: 6013806 * time.Millisecond, DTS: 6013806 * time.Millisecond,
MessageStreamID: 4534543, MessageStreamID: 4534543,
Codec: CodecMPEG2Audio, Codec: CodecMPEG1Audio,
Rate: flvio.SOUND_44Khz, Rate: flvio.SOUND_44Khz,
Depth: flvio.SOUND_16BIT, Depth: flvio.SOUND_16BIT,
Channels: flvio.SOUND_STEREO, Channels: flvio.SOUND_STEREO,

14
internal/rtmp/reader.go

@ -27,8 +27,8 @@ type OnDataH26xFunc func(pts time.Duration, au [][]byte)
// OnDataMPEG4AudioFunc is the prototype of the callback passed to OnDataMPEG4Audio(). // OnDataMPEG4AudioFunc is the prototype of the callback passed to OnDataMPEG4Audio().
type OnDataMPEG4AudioFunc func(pts time.Duration, au []byte) type OnDataMPEG4AudioFunc func(pts time.Duration, au []byte)
// OnDataMPEG2AudioFunc is the prototype of the callback passed to OnDataMPEG2Audio(). // OnDataMPEG1AudioFunc is the prototype of the callback passed to OnDataMPEG1Audio().
type OnDataMPEG2AudioFunc func(pts time.Duration, frame []byte) type OnDataMPEG1AudioFunc func(pts time.Duration, frame []byte)
func h265FindNALU(array []gomp4.HEVCNaluArray, typ h265.NALUType) []byte { func h265FindNALU(array []gomp4.HEVCNaluArray, typ h265.NALUType) []byte {
for _, entry := range array { for _, entry := range array {
@ -126,8 +126,8 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form
case 0: case 0:
return false, nil return false, nil
case message.CodecMPEG2Audio: case message.CodecMPEG1Audio:
audioTrack = &formats.MPEG2Audio{} audioTrack = &formats.MPEG1Audio{}
return true, nil return true, nil
case message.CodecMPEG4Audio: case message.CodecMPEG4Audio:
@ -340,7 +340,7 @@ outer:
} }
if videoTrack == nil && audioTrack == nil { if videoTrack == nil && audioTrack == nil {
return nil, nil, fmt.Errorf("no tracks found") return nil, nil, fmt.Errorf("no supported tracks found")
} }
return videoTrack, audioTrack, nil return videoTrack, audioTrack, nil
@ -524,8 +524,8 @@ func (r *Reader) OnDataMPEG4Audio(cb OnDataMPEG4AudioFunc) {
} }
} }
// OnDataMPEG2Audio sets a callback that is called when MPEG-2 Audio data is received. // OnDataMPEG1Audio sets a callback that is called when MPEG-1 Audio data is received.
func (r *Reader) OnDataMPEG2Audio(cb OnDataMPEG2AudioFunc) { func (r *Reader) OnDataMPEG1Audio(cb OnDataMPEG1AudioFunc) {
r.onDataAudio = func(msg *message.Audio) error { r.onDataAudio = func(msg *message.Audio) error {
cb(msg.DTS, msg.Payload) cb(msg.DTS, msg.Payload)
return nil return nil

22
internal/rtmp/writer.go

@ -5,7 +5,7 @@ import (
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v3/pkg/formats"
"github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/codecs/h264"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg2audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg1audio"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/notedit/rtmp/format/flv/flvio" "github.com/notedit/rtmp/format/flv/flvio"
@ -13,7 +13,7 @@ import (
"github.com/bluenviron/mediamtx/internal/rtmp/message" "github.com/bluenviron/mediamtx/internal/rtmp/message"
) )
func mpeg2AudioRate(sr int) uint8 { func mpeg1AudioRate(sr int) uint8 {
switch sr { switch sr {
case 5500: case 5500:
return flvio.SOUND_5_5Khz return flvio.SOUND_5_5Khz
@ -26,8 +26,8 @@ func mpeg2AudioRate(sr int) uint8 {
} }
} }
func mpeg2AudioChannels(m mpeg2audio.ChannelMode) uint8 { func mpeg1AudioChannels(m mpeg1audio.ChannelMode) uint8 {
if m == mpeg2audio.ChannelModeMono { if m == mpeg1audio.ChannelModeMono {
return flvio.SOUND_MONO return flvio.SOUND_MONO
} }
return flvio.SOUND_STEREO return flvio.SOUND_STEREO
@ -84,8 +84,8 @@ func (w *Writer) writeTracks(videoTrack formats.Format, audioTrack formats.Forma
K: "audiocodecid", K: "audiocodecid",
V: func() float64 { V: func() float64 {
switch audioTrack.(type) { switch audioTrack.(type) {
case *formats.MPEG2Audio: case *formats.MPEG1Audio:
return message.CodecMPEG2Audio return message.CodecMPEG1Audio
case *formats.MPEG4AudioGeneric, *formats.MPEG4AudioLATM: case *formats.MPEG4AudioGeneric, *formats.MPEG4AudioLATM:
return message.CodecMPEG4Audio return message.CodecMPEG4Audio
@ -193,15 +193,15 @@ func (w *Writer) WriteMPEG4Audio(pts time.Duration, au []byte) error {
}) })
} }
// WriteMPEG2Audio writes MPEG-2 Audio data. // WriteMPEG1Audio writes MPEG-1 Audio data.
func (w *Writer) WriteMPEG2Audio(pts time.Duration, h *mpeg2audio.FrameHeader, frame []byte) error { func (w *Writer) WriteMPEG1Audio(pts time.Duration, h *mpeg1audio.FrameHeader, frame []byte) error {
return w.conn.Write(&message.Audio{ return w.conn.Write(&message.Audio{
ChunkStreamID: message.AudioChunkStreamID, ChunkStreamID: message.AudioChunkStreamID,
MessageStreamID: 0x1000000, MessageStreamID: 0x1000000,
Codec: message.CodecMPEG2Audio, Codec: message.CodecMPEG1Audio,
Rate: mpeg2AudioRate(h.SampleRate), Rate: mpeg1AudioRate(h.SampleRate),
Depth: flvio.SOUND_16BIT, Depth: flvio.SOUND_16BIT,
Channels: mpeg2AudioChannels(h.ChannelMode), Channels: mpeg1AudioChannels(h.ChannelMode),
Payload: frame, Payload: frame,
DTS: pts, DTS: pts,
}) })

Loading…
Cancel
Save