mirror of
https://github.com/aler9/rtsp-simple-server
synced 2025-10-05 07:36:57 +08:00
support recording G711 tracks with fMP4 (#2853)
This commit is contained in:
@@ -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
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
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
Normal file
107
internal/formatprocessor/g711.go
Normal file
@@ -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
|
||||||
|
}
|
@@ -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)
|
||||||
|
|
||||||
|
@@ -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},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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
Normal file
7
internal/unit/g711.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package unit
|
||||||
|
|
||||||
|
// G711 is a G711 data unit.
|
||||||
|
type G711 struct {
|
||||||
|
Base
|
||||||
|
Samples []byte
|
||||||
|
}
|
Reference in New Issue
Block a user