Checkpoint audio-only Skald fork work
This commit is contained in:
+39
-191
@@ -22,7 +22,6 @@ import (
|
||||
|
||||
"github.com/jech/samplebuilder"
|
||||
|
||||
gcodecs "git.stormux.org/storm/skald/codecs"
|
||||
"git.stormux.org/storm/skald/conn"
|
||||
"git.stormux.org/storm/skald/hall"
|
||||
"git.stormux.org/storm/skald/rtptime"
|
||||
@@ -30,7 +29,6 @@ import (
|
||||
|
||||
const (
|
||||
audioMaxLate = 32
|
||||
videoMaxLate = 256
|
||||
)
|
||||
|
||||
var Directory string
|
||||
@@ -82,7 +80,7 @@ func (client *Client) Data() map[string]interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (client *Client) PushClient(group, kind, id, username string, perms []string, data map[string]interface{}) error {
|
||||
func (client *Client) PushClient(hall, kind, id, username string, perms []string, data map[string]interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -112,7 +110,7 @@ func (client *Client) Addr() net.Addr {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (client *Client) Joined(group, kind string) error {
|
||||
func (client *Client) Joined(hall, kind string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -173,16 +171,14 @@ type diskConn struct {
|
||||
client *Client
|
||||
directory string
|
||||
username string
|
||||
hasVideo bool
|
||||
|
||||
mu sync.Mutex
|
||||
file *os.File
|
||||
remote conn.Up
|
||||
tracks []*diskTrack
|
||||
width, height uint32
|
||||
lastWarning time.Time
|
||||
originLocal time.Time
|
||||
originRemote uint64
|
||||
mu sync.Mutex
|
||||
file *os.File
|
||||
remote conn.Up
|
||||
tracks []*diskTrack
|
||||
lastWarning time.Time
|
||||
originLocal time.Time
|
||||
originRemote uint64
|
||||
}
|
||||
|
||||
// called locked
|
||||
@@ -315,14 +311,10 @@ type diskTrack struct {
|
||||
|
||||
remoteNTP uint64
|
||||
remoteRTP uint32
|
||||
|
||||
kfRequested time.Time
|
||||
lastKf time.Time
|
||||
savedKf *rtp.Packet
|
||||
}
|
||||
|
||||
func newDiskConn(client *Client, directory string, up conn.Up, remoteTracks []conn.UpTrack) (*diskConn, error) {
|
||||
var audio, video conn.UpTrack
|
||||
var audio conn.UpTrack
|
||||
|
||||
for _, remote := range remoteTracks {
|
||||
codec := remote.Codec().MimeType
|
||||
@@ -332,30 +324,16 @@ func newDiskConn(client *Client, directory string, up conn.Up, remoteTracks []co
|
||||
} else {
|
||||
client.hall.WallOps("Multiple audio tracks, recording just one")
|
||||
}
|
||||
} else if strings.EqualFold(codec, "video/vp8") ||
|
||||
strings.EqualFold(codec, "video/vp9") ||
|
||||
strings.EqualFold(codec, "video/h264") {
|
||||
if video == nil || video.Label() == "l" {
|
||||
video = remote
|
||||
} else if remote.Label() != "l" {
|
||||
client.hall.WallOps("Multiple video tracks, recording just one")
|
||||
}
|
||||
} else {
|
||||
client.hall.WallOps("Unknown codec, " + codec + ", not recording")
|
||||
}
|
||||
}
|
||||
|
||||
if video == nil && audio == nil {
|
||||
if audio == nil {
|
||||
return nil, errors.New("no usable tracks found")
|
||||
}
|
||||
|
||||
tracks := make([]conn.UpTrack, 0, 2)
|
||||
if audio != nil {
|
||||
tracks = append(tracks, audio)
|
||||
}
|
||||
if video != nil {
|
||||
tracks = append(tracks, video)
|
||||
}
|
||||
tracks := []conn.UpTrack{audio}
|
||||
|
||||
_, username := up.User()
|
||||
conn := diskConn{
|
||||
@@ -374,24 +352,6 @@ func newDiskConn(client *Client, directory string, up conn.Up, remoteTracks []co
|
||||
audioMaxLate,
|
||||
&codecs.OpusPacket{}, codec.ClockRate,
|
||||
)
|
||||
} else if strings.EqualFold(codec.MimeType, "video/vp8") {
|
||||
builder = samplebuilder.New(
|
||||
videoMaxLate,
|
||||
&codecs.VP8Packet{}, codec.ClockRate,
|
||||
)
|
||||
conn.hasVideo = true
|
||||
} else if strings.EqualFold(codec.MimeType, "video/vp9") {
|
||||
builder = samplebuilder.New(
|
||||
videoMaxLate, &codecs.VP9Packet{},
|
||||
codec.ClockRate,
|
||||
)
|
||||
conn.hasVideo = true
|
||||
} else if strings.EqualFold(codec.MimeType, "video/h264") {
|
||||
builder = samplebuilder.New(
|
||||
videoMaxLate, &codecs.H264Packet{},
|
||||
codec.ClockRate,
|
||||
)
|
||||
conn.hasVideo = true
|
||||
} else {
|
||||
// this shouldn't happen
|
||||
return nil, errors.New(
|
||||
@@ -406,8 +366,6 @@ func newDiskConn(client *Client, directory string, up conn.Up, remoteTracks []co
|
||||
conn.tracks = append(conn.tracks, track)
|
||||
}
|
||||
|
||||
// Only do this after all tracks have been added to conn, to avoid
|
||||
// racing on hasVideo.
|
||||
for _, t := range conn.tracks {
|
||||
err := t.remote.AddLocal(t)
|
||||
if err != nil {
|
||||
@@ -454,7 +412,7 @@ func (t *diskTrack) Write(buf []byte) (int, error) {
|
||||
fetch(t, lastSeqno+i)
|
||||
}
|
||||
} else {
|
||||
requestKeyframe(t)
|
||||
|
||||
}
|
||||
t.lastSeqno = some(uint32(p.SequenceNumber))
|
||||
} else {
|
||||
@@ -462,7 +420,7 @@ func (t *diskTrack) Write(buf []byte) (int, error) {
|
||||
count := lastSeqno - p.SequenceNumber
|
||||
if count >= 512 {
|
||||
t.lastSeqno = none
|
||||
requestKeyframe(t)
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -491,41 +449,14 @@ func fetch(t *diskTrack, seqno uint16) {
|
||||
t.writeRTP(p)
|
||||
}
|
||||
|
||||
func requestKeyframe(t *diskTrack) {
|
||||
now := time.Now()
|
||||
if now.Sub(t.kfRequested) > 500*time.Millisecond {
|
||||
t.remote.RequestKeyframe()
|
||||
t.kfRequested = now
|
||||
}
|
||||
}
|
||||
|
||||
// writeRTP writes the packet without fetching lost packets
|
||||
// Called locked.
|
||||
func (t *diskTrack) writeRTP(p *rtp.Packet) error {
|
||||
codec := t.remote.Codec().MimeType
|
||||
if len(codec) > 6 && strings.EqualFold(codec[:6], "video/") {
|
||||
kf, _ := gcodecs.Keyframe(codec, p)
|
||||
if kf {
|
||||
t.savedKf = p
|
||||
t.lastKf = time.Now()
|
||||
if !valid(t.origin) {
|
||||
t.setOrigin(
|
||||
p.Timestamp, time.Now(),
|
||||
t.remote.Codec().ClockRate,
|
||||
)
|
||||
}
|
||||
} else if time.Since(t.lastKf) > 4*time.Second {
|
||||
requestKeyframe(t)
|
||||
}
|
||||
}
|
||||
|
||||
if !valid(t.origin) {
|
||||
if !t.conn.hasVideo || !t.conn.originLocal.Equal(time.Time{}) {
|
||||
t.setOrigin(
|
||||
p.Timestamp, time.Now(),
|
||||
t.remote.Codec().ClockRate,
|
||||
)
|
||||
}
|
||||
t.setOrigin(
|
||||
p.Timestamp, time.Now(),
|
||||
t.remote.Codec().ClockRate,
|
||||
)
|
||||
}
|
||||
|
||||
t.builder.Push(p)
|
||||
@@ -537,8 +468,6 @@ func (t *diskTrack) writeRTP(p *rtp.Packet) error {
|
||||
// samples will be flushed even if they are preceded by incomplete
|
||||
// samples.
|
||||
func (t *diskTrack) writeBuffered(force bool) error {
|
||||
codec := t.remote.Codec().MimeType
|
||||
|
||||
for {
|
||||
var sample *media.Sample
|
||||
var ts uint32
|
||||
@@ -561,39 +490,13 @@ func (t *diskTrack) writeBuffered(force bool) error {
|
||||
t.conn.close()
|
||||
}
|
||||
|
||||
var keyframe bool
|
||||
if len(codec) > 6 && strings.EqualFold(codec[:6], "video/") {
|
||||
if t.savedKf == nil {
|
||||
keyframe = false
|
||||
} else {
|
||||
keyframe = (ts == t.savedKf.Timestamp)
|
||||
}
|
||||
|
||||
if keyframe {
|
||||
w, h := gcodecs.KeyframeDimensions(
|
||||
codec, t.savedKf,
|
||||
if t.writer == nil {
|
||||
err := t.conn.initWriter(t, ts)
|
||||
if err != nil {
|
||||
t.conn.warn(
|
||||
"Write to disk " + err.Error(),
|
||||
)
|
||||
err := t.conn.initWriter(w, h, t, ts)
|
||||
if err != nil {
|
||||
t.conn.warn(
|
||||
"Write to disk " + err.Error(),
|
||||
)
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
keyframe = true
|
||||
if t.writer == nil {
|
||||
if !t.conn.hasVideo {
|
||||
err := t.conn.initWriter(0, 0, t, ts)
|
||||
if err != nil {
|
||||
t.conn.warn(
|
||||
"Write to disk " +
|
||||
err.Error(),
|
||||
)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,6 +511,7 @@ func (t *diskTrack) writeBuffered(force bool) error {
|
||||
|
||||
tm := (ts - value(t.origin)) /
|
||||
(t.remote.Codec().ClockRate / 1000)
|
||||
keyframe := true
|
||||
_, err := t.writer.Write(keyframe, int64(tm), sample.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -722,67 +626,23 @@ func (t *diskTrack) adjustOrigin(ts uint32) {
|
||||
}
|
||||
|
||||
// called locked
|
||||
func (conn *diskConn) initWriter(width, height uint32, track *diskTrack, ts uint32) error {
|
||||
func (conn *diskConn) initWriter(track *diskTrack, ts uint32) error {
|
||||
if conn.file != nil {
|
||||
if width == conn.width && height == conn.height {
|
||||
return nil
|
||||
} else {
|
||||
conn.close()
|
||||
}
|
||||
conn.close()
|
||||
}
|
||||
|
||||
isWebm := true
|
||||
var desc []mkvcore.TrackDescription
|
||||
for i, t := range conn.tracks {
|
||||
var entry webm.TrackEntry
|
||||
codec := t.remote.Codec()
|
||||
if strings.EqualFold(codec.MimeType, "audio/opus") {
|
||||
entry = webm.TrackEntry{
|
||||
Name: "Audio",
|
||||
TrackNumber: uint64(i + 1),
|
||||
CodecID: "A_OPUS",
|
||||
TrackType: 2,
|
||||
Audio: &webm.Audio{
|
||||
SamplingFrequency: float64(codec.ClockRate),
|
||||
Channels: uint64(codec.Channels),
|
||||
},
|
||||
}
|
||||
} else if strings.EqualFold(codec.MimeType, "video/vp8") {
|
||||
entry = webm.TrackEntry{
|
||||
Name: "Video",
|
||||
TrackNumber: uint64(i + 1),
|
||||
CodecID: "V_VP8",
|
||||
TrackType: 1,
|
||||
Video: &webm.Video{
|
||||
PixelWidth: uint64(width),
|
||||
PixelHeight: uint64(height),
|
||||
},
|
||||
}
|
||||
} else if strings.EqualFold(codec.MimeType, "video/vp9") {
|
||||
entry = webm.TrackEntry{
|
||||
Name: "Video",
|
||||
TrackNumber: uint64(i + 1),
|
||||
CodecID: "V_VP9",
|
||||
TrackType: 1,
|
||||
Video: &webm.Video{
|
||||
PixelWidth: uint64(width),
|
||||
PixelHeight: uint64(height),
|
||||
},
|
||||
}
|
||||
} else if strings.EqualFold(codec.MimeType, "video/h264") {
|
||||
entry = webm.TrackEntry{
|
||||
Name: "Video",
|
||||
TrackNumber: uint64(i + 1),
|
||||
CodecID: "V_MPEG4/ISO/AVC",
|
||||
TrackType: 1,
|
||||
Video: &webm.Video{
|
||||
PixelWidth: uint64(width),
|
||||
PixelHeight: uint64(height),
|
||||
},
|
||||
}
|
||||
isWebm = false
|
||||
} else {
|
||||
return errors.New("unknown track type")
|
||||
entry := webm.TrackEntry{
|
||||
Name: "Audio",
|
||||
TrackNumber: uint64(i + 1),
|
||||
CodecID: "A_OPUS",
|
||||
TrackType: 2,
|
||||
Audio: &webm.Audio{
|
||||
SamplingFrequency: float64(codec.ClockRate),
|
||||
Channels: uint64(codec.Channels),
|
||||
},
|
||||
}
|
||||
desc = append(desc,
|
||||
mkvcore.TrackDescription{
|
||||
@@ -792,27 +652,18 @@ func (conn *diskConn) initWriter(width, height uint32, track *diskTrack, ts uint
|
||||
)
|
||||
}
|
||||
|
||||
extension := "webm"
|
||||
header := webm.DefaultEBMLHeader
|
||||
if !isWebm {
|
||||
extension = "mkv"
|
||||
h := *header
|
||||
h.DocType = "matroska"
|
||||
header = &h
|
||||
}
|
||||
|
||||
if track != nil {
|
||||
track.adjustOrigin(ts)
|
||||
}
|
||||
|
||||
err := conn.open(extension)
|
||||
err := conn.open("webm")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
interceptor, err := mkvcore.NewMultiTrackBlockSorter(
|
||||
// must be larger than the samplebuilder's MaxLate.
|
||||
mkvcore.WithMaxDelayedPackets(videoMaxLate+16),
|
||||
mkvcore.WithMaxDelayedPackets(audioMaxLate+16),
|
||||
mkvcore.WithSortRule(mkvcore.BlockSorterWriteOutdated),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -823,7 +674,7 @@ func (conn *diskConn) initWriter(width, height uint32, track *diskTrack, ts uint
|
||||
|
||||
ws, err := mkvcore.NewSimpleBlockWriter(
|
||||
conn.file, desc,
|
||||
mkvcore.WithEBMLHeader(header),
|
||||
mkvcore.WithEBMLHeader(webm.DefaultEBMLHeader),
|
||||
mkvcore.WithSegmentInfo(webm.DefaultSegmentInfo),
|
||||
mkvcore.WithBlockInterceptor(interceptor),
|
||||
)
|
||||
@@ -839,9 +690,6 @@ func (conn *diskConn) initWriter(width, height uint32, track *diskTrack, ts uint
|
||||
return errors.New("unexpected number of writers")
|
||||
}
|
||||
|
||||
conn.width = width
|
||||
conn.height = height
|
||||
|
||||
for i, t := range conn.tracks {
|
||||
t.writer = ws[i]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user