Files
go2rtc/pkg/mp4/muxer.go
T
2023-03-19 17:17:05 +03:00

166 lines
3.9 KiB
Go

package mp4
import (
"encoding/hex"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/h264"
"github.com/AlexxIT/go2rtc/pkg/h265"
"github.com/AlexxIT/go2rtc/pkg/iso"
"github.com/deepch/vdk/codec/h264parser"
"github.com/deepch/vdk/codec/h265parser"
"github.com/pion/rtp"
)
type Muxer struct {
fragIndex uint32
dts []uint64
pts []uint32
}
const (
MimeH264 = "avc1.640029"
MimeH265 = "hvc1.1.6.L153.B0"
MimeAAC = "mp4a.40.2"
MimeOpus = "opus"
)
func (m *Muxer) MimeCodecs(codecs []*core.Codec) string {
var s string
for i, codec := range codecs {
if i > 0 {
s += ","
}
switch codec.Name {
case core.CodecH264:
s += "avc1." + h264.GetProfileLevelID(codec.FmtpLine)
case core.CodecH265:
// H.265 profile=main level=5.1
// hvc1 - supported in Safari, hev1 - doesn't, both supported in Chrome
s += MimeH265
case core.CodecAAC:
s += MimeAAC
case core.CodecOpus:
s += MimeOpus
}
}
return s
}
func (m *Muxer) GetInit(codecs []*core.Codec) ([]byte, error) {
mv := iso.NewMovie(1024)
mv.WriteFileType()
mv.StartAtom(iso.Moov)
mv.WriteMovieHeader()
for i, codec := range codecs {
switch codec.Name {
case core.CodecH264:
sps, pps := h264.GetParameterSet(codec.FmtpLine)
if sps == nil {
// some dummy SPS and PPS not a problem
sps = []byte{0x67, 0x42, 0x00, 0x0a, 0xf8, 0x41, 0xa2}
pps = []byte{0x68, 0xce, 0x38, 0x80}
}
codecData, err := h264parser.NewCodecDataFromSPSAndPPS(sps, pps)
if err != nil {
return nil, err
}
mv.WriteVideoTrack(
uint32(i+1), codec.Name, codec.ClockRate,
uint16(codecData.Width()), uint16(codecData.Height()),
codecData.AVCDecoderConfRecordBytes(),
)
case core.CodecH265:
vps, sps, pps := h265.GetParameterSet(codec.FmtpLine)
if sps == nil {
// some dummy SPS and PPS not a problem
vps = []byte{0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x99, 0xac, 0x09}
sps = []byte{0x42, 0x01, 0x01, 0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x99, 0xa0, 0x01, 0x40, 0x20, 0x05, 0xa1, 0xfe, 0x5a, 0xee, 0x46, 0xc1, 0xae, 0x55, 0x04}
pps = []byte{0x44, 0x01, 0xc0, 0x73, 0xc0, 0x4c, 0x90}
}
codecData, err := h265parser.NewCodecDataFromVPSAndSPSAndPPS(vps, sps, pps)
if err != nil {
return nil, err
}
mv.WriteVideoTrack(
uint32(i+1), codec.Name, codec.ClockRate,
uint16(codecData.Width()), uint16(codecData.Height()),
codecData.AVCDecoderConfRecordBytes(),
)
case core.CodecAAC:
s := core.Between(codec.FmtpLine, "config=", ";")
b, err := hex.DecodeString(s)
if err != nil {
return nil, err
}
mv.WriteAudioTrack(
uint32(i+1), codec.Name, codec.ClockRate, codec.Channels, b,
)
case core.CodecOpus, core.CodecMP3, core.CodecPCMU, core.CodecPCMA:
mv.WriteAudioTrack(
uint32(i+1), codec.Name, codec.ClockRate, codec.Channels, nil,
)
}
m.pts = append(m.pts, 0)
m.dts = append(m.dts, 0)
}
mv.StartAtom(iso.MoovMvex)
for i := range codecs {
mv.WriteTrackExtend(uint32(i + 1))
}
mv.EndAtom() // MVEX
mv.EndAtom() // MOOV
return mv.Bytes(), nil
}
func (m *Muxer) Reset() {
m.fragIndex = 0
for i := range m.dts {
m.dts[i] = 0
m.pts[i] = 0
}
}
func (m *Muxer) Marshal(trackID byte, packet *rtp.Packet) []byte {
// important before increment
time := m.dts[trackID]
m.fragIndex++
var duration uint32
newTime := packet.Timestamp
if m.pts[trackID] > 0 {
duration = newTime - m.pts[trackID]
m.dts[trackID] += uint64(duration)
} else {
// important, or Safari will fail with first frame
duration = 1
}
m.pts[trackID] = newTime
mv := iso.NewMovie(1024 + len(packet.Payload))
mv.WriteMovieFragment(
m.fragIndex, uint32(trackID+1), duration,
uint32(len(packet.Payload)), time,
)
mv.WriteData(packet.Payload)
return mv.Bytes()
}