Browse Source

support recording G711 tracks with fMP4 (#2853)

pull/2867/head
Alessandro Ros 2 years ago committed by GitHub
parent
commit
698963ad03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      README.md
  2. 2
      go.mod
  3. 4
      go.sum
  4. 107
      internal/formatprocessor/g711.go
  5. 3
      internal/formatprocessor/processor.go
  6. 26
      internal/record/agent_test.go
  7. 30
      internal/record/format_fmp4.go
  8. 7
      internal/unit/g711.go

2
README.md

@ -46,7 +46,7 @@ And can be recorded with:
|format|video codecs|audio codecs| |format|video codecs|audio codecs|
|------|------------|------------| |------|------------|------------|
|[fMP4](#record-streams-to-disk)|AV1, VP9, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, M-JPEG|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, LPCM| |[fMP4](#record-streams-to-disk)|AV1, VP9, H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video, M-JPEG|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3, G711, LPCM|
|[MPEG-TS](#record-streams-to-disk)|H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3| |[MPEG-TS](#record-streams-to-disk)|H265, H264, MPEG-4 Video (H263, Xvid), MPEG-1/2 Video|Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), AC-3|
**Features** **Features**

2
go.mod

@ -9,7 +9,7 @@ require (
github.com/aler9/writerseeker v1.1.0 github.com/aler9/writerseeker v1.1.0
github.com/bluenviron/gohlslib v1.1.0 github.com/bluenviron/gohlslib v1.1.0
github.com/bluenviron/gortsplib/v4 v4.6.2 github.com/bluenviron/gortsplib/v4 v4.6.2
github.com/bluenviron/mediacommon v1.6.0 github.com/bluenviron/mediacommon v1.6.1-0.20231228213201-7bb211dba7e1
github.com/datarhei/gosrt v0.5.5 github.com/datarhei/gosrt v0.5.5
github.com/fsnotify/fsnotify v1.7.0 github.com/fsnotify/fsnotify v1.7.0
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.9.1

4
go.sum

@ -24,8 +24,8 @@ github.com/bluenviron/gohlslib v1.1.0 h1:NL8CYg1BqHpy9tUugd/SbXH5p5vEi7Im/zaw0dp
github.com/bluenviron/gohlslib v1.1.0/go.mod h1:kG/Sjebsxnf5asMGaGcQ0aSvtFGNChJPgctds2wDHOI= github.com/bluenviron/gohlslib v1.1.0/go.mod h1:kG/Sjebsxnf5asMGaGcQ0aSvtFGNChJPgctds2wDHOI=
github.com/bluenviron/gortsplib/v4 v4.6.2 h1:CGIsxpnUFvSlIxnSFS0oFSSfwsHMmBCmYcrGAtIcwXc= github.com/bluenviron/gortsplib/v4 v4.6.2 h1:CGIsxpnUFvSlIxnSFS0oFSSfwsHMmBCmYcrGAtIcwXc=
github.com/bluenviron/gortsplib/v4 v4.6.2/go.mod h1:dN1YjyPNMfy/NwC17Ga6MiIMiUoQfg5GL7LGsVHa0Jo= github.com/bluenviron/gortsplib/v4 v4.6.2/go.mod h1:dN1YjyPNMfy/NwC17Ga6MiIMiUoQfg5GL7LGsVHa0Jo=
github.com/bluenviron/mediacommon v1.6.0 h1:xJgwbOKKwxyyrEONnSb5J5Sq7NLjNhVQoR/5gs2IDdQ= github.com/bluenviron/mediacommon v1.6.1-0.20231228213201-7bb211dba7e1 h1:f8XDAHvgPbT+n5qf73REdUo9kLmGpP4HNgphKI/8fGE=
github.com/bluenviron/mediacommon v1.6.0/go.mod h1:Ij/kE1LEucSjryNBVTyPL/gBI0d6/Css3f5PyrM957w= github.com/bluenviron/mediacommon v1.6.1-0.20231228213201-7bb211dba7e1/go.mod h1:Ij/kE1LEucSjryNBVTyPL/gBI0d6/Css3f5PyrM957w=
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=

107
internal/formatprocessor/g711.go

@ -0,0 +1,107 @@
package formatprocessor //nolint:dupl
import (
"fmt"
"time"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpsimpleaudio"
"github.com/pion/rtp"
"github.com/bluenviron/mediamtx/internal/unit"
)
type formatProcessorG711 struct {
udpMaxPayloadSize int
format *format.G711
encoder *rtpsimpleaudio.Encoder
decoder *rtpsimpleaudio.Decoder
}
func newG711(
udpMaxPayloadSize int,
forma *format.G711,
generateRTPPackets bool,
) (*formatProcessorG711, error) {
t := &formatProcessorG711{
udpMaxPayloadSize: udpMaxPayloadSize,
format: forma,
}
if generateRTPPackets {
err := t.createEncoder()
if err != nil {
return nil, err
}
}
return t, nil
}
func (t *formatProcessorG711) createEncoder() error {
t.encoder = &rtpsimpleaudio.Encoder{
PayloadMaxSize: t.udpMaxPayloadSize - 12,
}
return t.encoder.Init()
}
func (t *formatProcessorG711) ProcessUnit(uu unit.Unit) error { //nolint:dupl
u := uu.(*unit.G711)
pkt, err := t.encoder.Encode(u.Samples)
if err != nil {
return err
}
ts := uint32(multiplyAndDivide(u.PTS, time.Duration(t.format.ClockRate()), time.Second))
pkt.Timestamp += ts
u.RTPPackets = []*rtp.Packet{pkt}
return nil
}
func (t *formatProcessorG711) ProcessRTPPacket( //nolint:dupl
pkt *rtp.Packet,
ntp time.Time,
pts time.Duration,
hasNonRTSPReaders bool,
) (Unit, error) {
u := &unit.G711{
Base: unit.Base{
RTPPackets: []*rtp.Packet{pkt},
NTP: ntp,
PTS: pts,
},
}
// remove padding
pkt.Header.Padding = false
pkt.PaddingSize = 0
if pkt.MarshalSize() > t.udpMaxPayloadSize {
return nil, fmt.Errorf("payload size (%d) is greater than maximum allowed (%d)",
pkt.MarshalSize(), t.udpMaxPayloadSize)
}
// decode from RTP
if hasNonRTSPReaders || t.decoder != nil {
if t.decoder == nil {
var err error
t.decoder, err = t.format.CreateDecoder()
if err != nil {
return nil, err
}
}
samples, err := t.decoder.Decode(pkt)
if err != nil {
return nil, err
}
u.Samples = samples
}
// route packet as is
return u, nil
}

3
internal/formatprocessor/processor.go

@ -75,6 +75,9 @@ func New(
case *format.AC3: case *format.AC3:
return newAC3(udpMaxPayloadSize, forma, generateRTPPackets) return newAC3(udpMaxPayloadSize, forma, generateRTPPackets)
case *format.G711:
return newG711(udpMaxPayloadSize, forma, generateRTPPackets)
case *format.LPCM: case *format.LPCM:
return newLPCM(udpMaxPayloadSize, forma, generateRTPPackets) return newLPCM(udpMaxPayloadSize, forma, generateRTPPackets)

26
internal/record/agent_test.go

@ -53,6 +53,18 @@ func TestAgent(t *testing.T) {
IndexDeltaLength: 3, IndexDeltaLength: 3,
}}, }},
}, },
{
Type: description.MediaTypeAudio,
Formats: []rtspformat.Format{&rtspformat.G711{
MULaw: false,
}},
},
{
Type: description.MediaTypeAudio,
Formats: []rtspformat.Format{&rtspformat.G711{
MULaw: true,
}},
},
}} }}
writeToStream := func(stream *stream.Stream) { writeToStream := func(stream *stream.Stream) {
@ -107,6 +119,20 @@ func TestAgent(t *testing.T) {
}, },
AUs: [][]byte{{1, 2, 3, 4}}, AUs: [][]byte{{1, 2, 3, 4}},
}) })
stream.WriteUnit(desc.Medias[3], desc.Medias[3].Formats[0], &unit.G711{
Base: unit.Base{
PTS: (50 + time.Duration(i)) * time.Second,
},
Samples: []byte{1, 2, 3, 4},
})
stream.WriteUnit(desc.Medias[4], desc.Medias[4].Formats[0], &unit.G711{
Base: unit.Base{
PTS: (50 + time.Duration(i)) * time.Second,
},
Samples: []byte{1, 2, 3, 4},
})
} }
} }

30
internal/record/format_fmp4.go

@ -8,6 +8,7 @@ import (
rtspformat "github.com/bluenviron/gortsplib/v4/pkg/format" rtspformat "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediacommon/pkg/codecs/ac3" "github.com/bluenviron/mediacommon/pkg/codecs/ac3"
"github.com/bluenviron/mediacommon/pkg/codecs/av1" "github.com/bluenviron/mediacommon/pkg/codecs/av1"
"github.com/bluenviron/mediacommon/pkg/codecs/g711"
"github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/codecs/h264"
"github.com/bluenviron/mediacommon/pkg/codecs/h265" "github.com/bluenviron/mediacommon/pkg/codecs/h265"
"github.com/bluenviron/mediacommon/pkg/codecs/jpeg" "github.com/bluenviron/mediacommon/pkg/codecs/jpeg"
@ -771,7 +772,34 @@ func (f *formatFMP4) initialize() {
// TODO // TODO
case *rtspformat.G711: case *rtspformat.G711:
// TODO codec := &fmp4.CodecLPCM{
LittleEndian: false,
BitDepth: 16,
SampleRate: 8000,
ChannelCount: 1,
}
track := addTrack(codec)
f.a.agent.Stream.AddReader(f.a.writer, media, forma, func(u unit.Unit) error {
tunit := u.(*unit.G711)
if tunit.Samples == nil {
return nil
}
var out []byte
if forma.MULaw {
out = g711.DecodeMulaw(tunit.Samples)
} else {
out = g711.DecodeAlaw(tunit.Samples)
}
return track.record(&sample{
PartSample: &fmp4.PartSample{
Payload: out,
},
dts: tunit.PTS,
})
})
case *rtspformat.LPCM: case *rtspformat.LPCM:
codec := &fmp4.CodecLPCM{ codec := &fmp4.CodecLPCM{

7
internal/unit/g711.go

@ -0,0 +1,7 @@
package unit
// G711 is a G711 data unit.
type G711 struct {
Base
Samples []byte
}
Loading…
Cancel
Save