1
Fork 0
galene/diskwriter/diskwriter.go

712 lines
15 KiB
Go
Raw Normal View History

2020-10-04 19:01:06 +02:00
package diskwriter
2020-05-23 01:48:36 +02:00
import (
2020-09-18 10:23:53 +02:00
crand "crypto/rand"
"encoding/hex"
2020-05-23 01:48:36 +02:00
"errors"
"fmt"
2020-12-23 21:51:25 +01:00
"log"
2020-05-23 01:48:36 +02:00
"os"
"path/filepath"
2020-12-23 23:13:22 +01:00
"runtime"
2020-12-04 01:15:52 +01:00
"strings"
2020-05-30 00:23:54 +02:00
"sync"
2020-05-23 01:48:36 +02:00
"time"
"github.com/at-wat/ebml-go/webm"
"github.com/pion/rtp"
"github.com/pion/rtp/codecs"
"github.com/pion/webrtc/v3/pkg/media"
"github.com/jech/samplebuilder"
2020-09-13 11:04:16 +02:00
2020-12-19 17:37:48 +01:00
"github.com/jech/galene/conn"
"github.com/jech/galene/group"
2020-05-23 01:48:36 +02:00
)
2020-09-13 14:12:00 +02:00
var Directory string
type Client struct {
group *group.Group
2020-05-30 00:23:54 +02:00
id string
mu sync.Mutex
down map[string]*diskConn
2020-05-23 01:48:36 +02:00
closed bool
}
2020-09-13 14:14:13 +02:00
func newId() string {
2020-09-18 10:23:53 +02:00
b := make([]byte, 16)
crand.Read(b)
return hex.EncodeToString(b)
2020-09-13 14:14:13 +02:00
}
2020-09-13 14:12:00 +02:00
func New(g *group.Group) *Client {
return &Client{group: g, id: newId()}
2020-09-13 14:14:13 +02:00
}
2020-09-13 14:12:00 +02:00
func (client *Client) Group() *group.Group {
2020-05-23 01:48:36 +02:00
return client.group
}
2020-09-13 14:12:00 +02:00
func (client *Client) Id() string {
2020-05-23 01:48:36 +02:00
return client.id
}
2020-11-29 14:26:42 +01:00
func (client *Client) Username() string {
return "RECORDING"
}
func (client *Client) Challenge(group string, cred group.ClientCredentials) bool {
return true
2020-05-23 01:48:36 +02:00
}
func (client *Client) OverridePermissions(g *group.Group) bool {
return true
}
2020-09-13 14:12:00 +02:00
func (client *Client) SetPermissions(perms group.ClientPermissions) {
return
}
2021-01-14 03:56:37 +01:00
func (client *Client) Permissions() group.ClientPermissions {
return group.ClientPermissions{}
}
func (client *Client) Status() map[string]interface{} {
return nil
}
func (client *Client) PushClient(id, username string, permissions *group.ClientPermissions, status map[string]interface{}, kind string) error {
return nil
}
func (client *Client) RequestConns(target group.Client, g *group.Group, id string) error {
return nil
}
2020-09-13 14:12:00 +02:00
func (client *Client) Close() error {
2020-05-30 00:23:54 +02:00
client.mu.Lock()
defer client.mu.Unlock()
2020-05-23 01:48:36 +02:00
for _, down := range client.down {
down.Close()
}
client.down = nil
client.closed = true
return nil
}
2020-11-30 16:26:11 +01:00
func (client *Client) Kick(id, user, message string) error {
err := client.Close()
group.DelClient(client)
return err
}
func (client *Client) PushConn(g *group.Group, id string, up conn.Up, tracks []conn.UpTrack, replace string) error {
if client.group != g {
return nil
}
2020-05-30 00:23:54 +02:00
client.mu.Lock()
defer client.mu.Unlock()
2020-05-23 01:48:36 +02:00
if client.closed {
return errors.New("disk client is closed")
}
if replace != "" {
rp := client.down[replace]
if rp != nil {
rp.Close()
delete(client.down, replace)
} else {
log.Printf("Disk writer: replacing unknown connection")
}
}
old := client.down[id]
if old != nil {
old.Close()
delete(client.down, id)
}
2020-09-13 11:04:16 +02:00
if up == nil {
return nil
}
2020-09-13 14:12:00 +02:00
directory := filepath.Join(Directory, client.group.Name())
2020-05-23 01:48:36 +02:00
err := os.MkdirAll(directory, 0700)
if err != nil {
2020-12-23 21:51:25 +01:00
g.WallOps("Write to disk: " + err.Error())
2020-05-23 01:48:36 +02:00
return err
}
if client.down == nil {
client.down = make(map[string]*diskConn)
}
down, err := newDiskConn(client, directory, up, tracks)
2020-05-23 01:48:36 +02:00
if err != nil {
2020-12-23 21:51:25 +01:00
g.WallOps("Write to disk: " + err.Error())
2020-05-23 01:48:36 +02:00
return err
}
2020-09-13 11:04:16 +02:00
client.down[up.Id()] = down
2020-05-23 01:48:36 +02:00
return nil
}
type diskConn struct {
client *Client
2020-05-30 00:23:54 +02:00
directory string
username string
hasVideo bool
2020-05-30 00:23:54 +02:00
mu sync.Mutex
2020-05-23 01:48:36 +02:00
file *os.File
2020-09-13 11:04:16 +02:00
remote conn.Up
2020-05-23 01:48:36 +02:00
tracks []*diskTrack
width, height uint32
2020-12-23 21:51:25 +01:00
lastWarning time.Time
}
// called locked
func (conn *diskConn) warn(message string) {
now := time.Now()
if now.Sub(conn.lastWarning) < 10*time.Second {
return
}
log.Println(message)
conn.client.group.WallOps(message)
conn.lastWarning = now
2020-05-23 01:48:36 +02:00
}
2020-05-30 00:23:54 +02:00
// called locked
2020-05-23 01:48:36 +02:00
func (conn *diskConn) reopen() error {
for _, t := range conn.tracks {
if t.writer != nil {
t.writeBuffered(true)
2020-05-23 01:48:36 +02:00
t.writer.Close()
t.writer = nil
}
}
conn.file = nil
file, err := openDiskFile(conn.directory, conn.username)
2020-05-23 01:48:36 +02:00
if err != nil {
return err
}
conn.file = file
return nil
}
func (conn *diskConn) Close() error {
2020-09-13 11:04:16 +02:00
conn.remote.DelLocal(conn)
2020-05-23 01:48:36 +02:00
2020-05-30 00:23:54 +02:00
conn.mu.Lock()
tracks := make([]*diskTrack, 0, len(conn.tracks))
2020-05-23 01:48:36 +02:00
for _, t := range conn.tracks {
if t.writer != nil {
t.writeBuffered(true)
2020-05-23 01:48:36 +02:00
t.writer.Close()
t.writer = nil
}
2020-05-30 00:23:54 +02:00
tracks = append(tracks, t)
}
conn.mu.Unlock()
for _, t := range tracks {
2020-09-13 11:04:16 +02:00
t.remote.DelLocal(t)
2020-05-23 01:48:36 +02:00
}
return nil
}
func openDiskFile(directory, username string) (*os.File, error) {
2020-12-23 23:13:22 +01:00
filenameFormat := "2006-01-02T15:04:05.000"
if runtime.GOOS == "windows" {
filenameFormat = "2006-01-02T15-04-05-000"
}
filename := time.Now().Format(filenameFormat)
if username != "" {
filename = filename + "-" + username
2020-05-23 01:48:36 +02:00
}
for counter := 0; counter < 100; counter++ {
var fn string
if counter == 0 {
fn = fmt.Sprintf("%v.webm", filename)
} else {
fn = fmt.Sprintf("%v-%02d.webm", filename, counter)
}
fn = filepath.Join(directory, fn)
f, err := os.OpenFile(
fn, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600,
)
if err == nil {
return f, nil
} else if !os.IsExist(err) {
return nil, err
}
}
return nil, errors.New("couldn't create file")
}
type maybeUint32 uint64
const none maybeUint32 = 0
func some(value uint32) maybeUint32 {
2021-07-11 23:09:19 +02:00
return maybeUint32(uint64(1<<32) | uint64(value))
}
func valid(m maybeUint32) bool {
return (m & (1 << 32)) != 0
}
func value(m maybeUint32) uint32 {
return uint32(m)
}
2020-05-23 01:48:36 +02:00
type diskTrack struct {
2020-09-13 11:04:16 +02:00
remote conn.UpTrack
2020-05-30 00:23:54 +02:00
conn *diskConn
2021-07-11 23:09:19 +02:00
writer webm.BlockWriteCloser
builder *samplebuilder.SampleBuilder
lastSeqno maybeUint32
origin maybeUint32
kfRequested time.Time
lastKf time.Time
savedKf *rtp.Packet
2020-05-23 01:48:36 +02:00
}
func newDiskConn(client *Client, directory string, up conn.Up, remoteTracks []conn.UpTrack) (*diskConn, error) {
2021-05-12 16:39:46 +02:00
var audio, video conn.UpTrack
for _, remote := range remoteTracks {
codec := remote.Codec().MimeType
if strings.EqualFold(codec, "audio/opus") {
if audio == nil {
audio = remote
} else {
client.group.WallOps("Multiple audio tracks, recording just one")
}
} else if strings.EqualFold(codec, "video/vp8") ||
strings.EqualFold(codec, "video/vp9") {
if video == nil || video.Label() == "l" {
video = remote
} else if remote.Label() != "l" {
client.group.WallOps("Multiple video tracks, recording just one")
}
} else {
client.group.WallOps("Unknown codec, " + codec + ", not recording")
}
}
if video == nil && 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)
}
_, username := up.User()
2020-05-23 01:48:36 +02:00
conn := diskConn{
client: client,
2020-05-23 01:48:36 +02:00
directory: directory,
username: username,
2021-05-12 16:39:46 +02:00
tracks: make([]*diskTrack, 0, len(tracks)),
2020-05-23 01:48:36 +02:00
remote: up,
}
truePartitionTailChecker := func(p *rtp.Packet) bool {
return true
}
markerPartitionTailChecker := func(p *rtp.Packet) bool {
return p.Marker
}
2021-05-12 16:39:46 +02:00
for _, remote := range tracks {
2020-05-23 01:48:36 +02:00
var builder *samplebuilder.SampleBuilder
2020-12-04 01:15:52 +01:00
codec := remote.Codec()
if strings.EqualFold(codec.MimeType, "audio/opus") {
builder = samplebuilder.New(
2020-12-04 01:15:52 +01:00
16, &codecs.OpusPacket{}, codec.ClockRate,
samplebuilder.WithPartitionHeadChecker(
&codecs.OpusPartitionHeadChecker{},
),
samplebuilder.WithPartitionTailChecker(
truePartitionTailChecker,
),
)
} else if strings.EqualFold(codec.MimeType, "video/vp8") {
builder = samplebuilder.New(
2020-12-04 01:15:52 +01:00
128, &codecs.VP8Packet{}, codec.ClockRate,
samplebuilder.WithPartitionHeadChecker(
&codecs.VP8PartitionHeadChecker{},
),
samplebuilder.WithPartitionTailChecker(
markerPartitionTailChecker,
),
)
conn.hasVideo = true
} else if strings.EqualFold(codec.MimeType, "video/vp9") {
2021-01-03 21:02:53 +01:00
builder = samplebuilder.New(
128, &codecs.VP9Packet{}, codec.ClockRate,
samplebuilder.WithPartitionHeadChecker(
&codecs.VP9PartitionHeadChecker{},
),
samplebuilder.WithPartitionTailChecker(
markerPartitionTailChecker,
),
2021-01-03 21:02:53 +01:00
)
conn.hasVideo = true
} else {
2021-05-12 16:39:46 +02:00
// this shouldn't happen
return nil, errors.New(
"cannot record codec " + codec.MimeType,
)
2020-05-23 01:48:36 +02:00
}
track := &diskTrack{
remote: remote,
builder: builder,
conn: &conn,
}
conn.tracks = append(conn.tracks, track)
}
2021-02-28 16:26:56 +01:00
// 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 {
log.Printf("Couldn't add disk track: %v", err)
conn.warn("Couldn't add disk track: " + err.Error())
}
}
2020-09-13 11:04:16 +02:00
err := up.AddLocal(&conn)
2020-05-30 03:36:15 +02:00
if err != nil {
return nil, err
}
2020-05-23 01:48:36 +02:00
return &conn, nil
}
2020-09-13 11:04:16 +02:00
func (t *diskTrack) SetTimeOffset(ntp uint64, rtp uint32) {
}
2020-09-13 11:04:16 +02:00
func (t *diskTrack) SetCname(string) {
2020-09-03 15:29:18 +02:00
}
2021-01-03 21:02:53 +01:00
func isKeyframe(codec string, data []byte) bool {
if strings.EqualFold(codec, "video/vp8") {
2021-01-03 21:02:53 +01:00
if len(data) < 1 {
return false
}
return (data[0] & 0x1) == 0
} else if strings.EqualFold(codec, "video/vp9") {
2021-01-03 21:02:53 +01:00
if len(data) < 1 {
return false
}
if data[0]&0xC0 != 0x80 {
return false
}
profile := (data[0] >> 4) & 0x3
if profile != 3 {
return (data[0] & 0xC) == 0
}
return (data[0] & 0x6) == 0
} else {
2021-01-03 21:02:53 +01:00
panic("Eek!")
}
}
func keyframeDimensions(codec string, data []byte, packet *rtp.Packet) (uint32, uint32) {
if strings.EqualFold(codec, "video/vp8") {
2021-01-03 21:02:53 +01:00
if len(data) < 10 {
return 0, 0
}
raw := uint32(data[6]) | uint32(data[7])<<8 |
uint32(data[8])<<16 | uint32(data[9])<<24
width := raw & 0x3FFF
height := (raw >> 16) & 0x3FFF
return width, height
} else if strings.EqualFold(codec, "video/vp9") {
2021-01-03 21:02:53 +01:00
if packet == nil {
return 0, 0
}
var vp9 codecs.VP9Packet
_, err := vp9.Unmarshal(packet.Payload)
if err != nil {
return 0, 0
}
2021-02-28 16:26:56 +01:00
if !vp9.V {
2021-01-03 21:02:53 +01:00
return 0, 0
}
w := uint32(0)
h := uint32(0)
for i := range vp9.Width {
if i >= len(vp9.Height) {
break
}
if w < uint32(vp9.Width[i]) {
w = uint32(vp9.Width[i])
}
if h < uint32(vp9.Height[i]) {
h = uint32(vp9.Height[i])
}
}
return w, h
} else {
2021-01-03 21:02:53 +01:00
return 0, 0
}
}
func (t *diskTrack) Write(buf []byte) (int, error) {
2020-05-30 00:23:54 +02:00
t.conn.mu.Lock()
defer t.conn.mu.Unlock()
2020-05-23 01:48:36 +02:00
if t.builder == nil {
return 0, nil
2020-05-23 01:48:36 +02:00
}
2021-07-11 23:09:19 +02:00
// samplebuilder retains packets
data := make([]byte, len(buf))
copy(data, buf)
p := new(rtp.Packet)
err := p.Unmarshal(data)
if err != nil {
log.Printf("Diskwriter: %v", err)
return 0, nil
2020-05-23 01:48:36 +02:00
}
2021-07-11 23:09:19 +02:00
if valid(t.lastSeqno) {
lastSeqno := uint16(value(t.lastSeqno))
if ((p.SequenceNumber - lastSeqno) & 0x8000) == 0 {
count := p.SequenceNumber - lastSeqno
if count > 0 && count < 128 {
var nacks []uint16
for i := lastSeqno + 1; i != p.SequenceNumber; i++ {
// different buf each time
buf := make([]byte, 1504)
n := t.remote.GetRTP(i, buf)
if n > 0 {
p := new(rtp.Packet)
err := p.Unmarshal(buf)
if err == nil {
t.writeRTP(p)
}
} else {
nacks = append(nacks, i)
}
}
if len(nacks) > 0 {
t.remote.Nack(nacks)
2021-07-11 23:09:19 +02:00
}
}
}
}
t.lastSeqno = some(uint32(p.SequenceNumber))
err = t.writeRTP(p)
if err != nil {
return 0, err
}
return len(buf), nil
}
// writeRTP writes the packet without doing any loss recovery.
// Called locked.
func (t *diskTrack) writeRTP(p *rtp.Packet) error {
codec := t.remote.Codec()
if strings.EqualFold(codec.MimeType, "video/vp9") {
2021-01-03 21:02:53 +01:00
var vp9 codecs.VP9Packet
_, err := vp9.Unmarshal(p.Payload)
if err == nil && vp9.B && len(vp9.Payload) >= 1 {
profile := (vp9.Payload[0] >> 4) & 0x3
kf := false
if profile != 3 {
kf = (vp9.Payload[0] & 0xC) == 0
} else {
kf = (vp9.Payload[0] & 0x6) == 0
}
if kf {
t.savedKf = p
}
}
}
2020-05-23 01:48:36 +02:00
t.builder.Push(p)
return t.writeBuffered(false)
}
// writeBuffered writes any buffered samples to disk. If force is true,
// then samples will be flushed even if they are preceded by incomplete
// samples.
func (t *diskTrack) writeBuffered(force bool) error {
codec := t.remote.Codec()
2020-05-23 01:48:36 +02:00
for {
var sample *media.Sample
var ts uint32
if !force {
sample, ts = t.builder.PopWithTimestamp()
} else {
sample, ts = t.builder.ForcePopWithTimestamp()
}
2020-05-23 01:48:36 +02:00
if sample == nil {
2021-07-11 23:09:19 +02:00
return nil
2020-05-23 01:48:36 +02:00
}
keyframe := true
if strings.EqualFold(codec.MimeType, "video/vp8") ||
strings.EqualFold(codec.MimeType, "video/vp9") {
2021-01-03 21:02:53 +01:00
keyframe = isKeyframe(codec.MimeType, sample.Data)
2020-05-23 01:48:36 +02:00
if keyframe {
2021-01-03 21:02:53 +01:00
err := t.conn.initWriter(
keyframeDimensions(
codec.MimeType, sample.Data,
t.savedKf,
),
)
2020-05-23 01:48:36 +02:00
if err != nil {
2020-12-23 21:51:25 +01:00
t.conn.warn(
"Write to disk " + err.Error(),
)
2021-07-11 23:09:19 +02:00
return err
2020-05-23 01:48:36 +02:00
}
}
} else {
if t.writer == nil {
if !t.conn.hasVideo {
err := t.conn.initWriter(0, 0)
if err != nil {
2020-12-23 21:51:25 +01:00
t.conn.warn(
"Write to disk " +
err.Error(),
)
2021-07-11 23:09:19 +02:00
return err
}
}
}
2020-05-23 01:48:36 +02:00
}
now := time.Now()
if keyframe {
t.lastKf = now
} else if t.writer == nil || now.Sub(t.lastKf) > 4*time.Second {
if now.Sub(t.kfRequested) > time.Second {
t.remote.RequestKeyframe()
t.kfRequested = now
2020-05-23 01:48:36 +02:00
}
2021-07-11 23:09:19 +02:00
return nil
2020-05-23 01:48:36 +02:00
}
if t.writer == nil {
continue
}
if !valid(t.origin) {
t.origin = some(ts)
}
ts -= value(t.origin)
tm := ts / (t.remote.Codec().ClockRate / 1000)
2020-05-23 01:48:36 +02:00
_, err := t.writer.Write(keyframe, int64(tm), sample.Data)
if err != nil {
2021-07-11 23:09:19 +02:00
return err
2020-05-23 01:48:36 +02:00
}
}
}
2020-05-30 00:23:54 +02:00
// called locked
2020-05-23 01:48:36 +02:00
func (conn *diskConn) initWriter(width, height uint32) error {
if conn.file != nil && width == conn.width && height == conn.height {
return nil
}
var entries []webm.TrackEntry
for i, t := range conn.tracks {
var entry webm.TrackEntry
2020-12-04 01:15:52 +01:00
codec := t.remote.Codec()
if strings.EqualFold(codec.MimeType, "audio/opus") {
2020-05-23 01:48:36 +02:00
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") {
2020-05-23 01:48:36 +02:00
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") {
2021-01-03 21:02:53 +01:00
entry = webm.TrackEntry{
Name: "Video",
TrackNumber: uint64(i + 1),
CodecID: "V_VP9",
TrackType: 1,
Video: &webm.Video{
PixelWidth: uint64(width),
PixelHeight: uint64(height),
},
}
} else {
2020-05-23 01:48:36 +02:00
return errors.New("unknown track type")
}
entries = append(entries, entry)
}
err := conn.reopen()
if err != nil {
return err
}
writers, err := webm.NewSimpleBlockWriter(conn.file, entries)
if err != nil {
conn.file.Close()
conn.file = nil
return err
}
if len(writers) != len(conn.tracks) {
conn.file.Close()
conn.file = nil
return errors.New("unexpected number of writers")
}
conn.width = width
conn.height = height
for i, t := range conn.tracks {
t.writer = writers[i]
}
return nil
}
func (t *diskTrack) GetMaxBitrate() (uint64, int, int) {
return ^uint64(0), -1, -1
2020-05-23 01:48:36 +02:00
}