MeteorLight/mount.go

257 lines
6.0 KiB
Go

package main
import (
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/aac"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/flac"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/mp3"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/opus"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/packetizer"
"io"
"log"
"sync"
)
type HeaderEntry struct {
Name string `json:"name"`
Value string `json:"value"`
}
type ListenerInformation struct {
Mount string `json:"mount"`
Path string `json:"path"`
Headers []HeaderEntry `json:"headers"`
}
type StreamListener struct {
Information ListenerInformation
Start func(packets []packetizer.Packet) error
Write func(packet packetizer.Packet) error
Close func()
}
type StreamMount struct {
Mount string
MimeType string
Packetizer packetizer.Packetizer
listeners []*StreamListener
listenersLock sync.Mutex
keepBuffer []packetizer.Packet
}
func NewStreamMount(source audio.Source, mount string, codec string, container string, bitrate interface{}) *StreamMount {
var encoderFormat format.Encoder
options := make(map[string]interface{})
var mimeType string
reader, writer := io.Pipe()
var packetizerEntry packetizer.Packetizer
switch codec {
case "opus":
encoderFormat = opus.NewFormat()
if bitrate != nil {
options["bitrate"] = bitrate
}
mimeType = "audio/ogg;codecs=opus"
packetizerEntry = packetizer.NewOggPacketizer(reader)
case "mp3":
encoderFormat = mp3.NewFormat()
if bitrate != nil {
options["bitrate"] = bitrate
}
mimeType = "audio/mpeg;codecs=mp3"
packetizerEntry = packetizer.NewMp3Packetizer(reader)
case "flac":
encoderFormat = flac.NewFormat()
if bitrate != nil {
options["bitdepth"] = bitrate
}
options["compression_level"] = 8
mimeType = "audio/flac"
packetizerEntry = packetizer.NewFLACPacketizer(reader)
case "aac":
encoderFormat = aac.NewFormat()
if bitrate != nil {
options["bitrate"] = bitrate
}
mimeType = "audio/aac"
packetizerEntry = packetizer.NewAdtsPacketizer(reader)
case "he-aacv2":
encoderFormat = aac.NewFormat()
if bitrate != nil {
options["bitrate"] = bitrate
}
options["mode"] = "hev2"
mimeType = "audio/aac"
packetizerEntry = packetizer.NewAdtsPacketizer(reader)
}
if encoderFormat == nil {
switch container {
case "ogg":
encoderFormat = opus.NewFormat()
if bitrate != nil {
options["bitrate"] = bitrate
}
mimeType = "audio/ogg;codecs=opus"
packetizerEntry = packetizer.NewOggPacketizer(reader)
case "mp3":
encoderFormat = mp3.NewFormat()
if bitrate != nil {
options["bitrate"] = bitrate
}
mimeType = "audio/mpeg;codecs=mp3"
packetizerEntry = packetizer.NewMp3Packetizer(reader)
case "flac":
encoderFormat = flac.NewFormat()
if bitrate != nil {
options["bitdepth"] = bitrate
}
options["compression_level"] = 8
mimeType = "audio/flac"
packetizerEntry = packetizer.NewFLACPacketizer(reader)
case "adts", "aac":
encoderFormat = aac.NewFormat()
if bitrate != nil {
options["bitrate"] = bitrate
}
mimeType = "audio/aac"
packetizerEntry = packetizer.NewAdtsPacketizer(reader)
}
}
if encoderFormat == nil || packetizerEntry == nil {
return nil
}
if opusFormat, ok := encoderFormat.(opus.Format); ok {
go func() {
defer writer.Close()
if err := opusFormat.Encode(audio.NewResampleFilter(opus.FixedSampleRate, audio.Linear, 0).Process(source), writer, options); err != nil {
log.Panic(err)
}
}()
} else {
go func() {
defer writer.Close()
if err := encoderFormat.Encode(source, writer, options); err != nil {
log.Panic(err)
}
}()
}
return &StreamMount{
Mount: mount,
MimeType: mimeType,
Packetizer: packetizerEntry,
}
}
func (m *StreamMount) removeDiscard() {
for i, p := range m.keepBuffer {
if p.KeepMode() == packetizer.Discard {
m.keepBuffer = append(m.keepBuffer[:i], m.keepBuffer[i+1:]...)
m.removeDiscard()
break
}
}
}
func (m *StreamMount) removeKeepLast() {
for i, p := range m.keepBuffer {
if p.KeepMode() == packetizer.KeepLast {
m.keepBuffer = append(m.keepBuffer[:i], m.keepBuffer[i+1:]...)
m.removeKeepLast()
break
}
}
}
func (m *StreamMount) removeGroupKeep() {
for i, p := range m.keepBuffer {
if p.KeepMode() == packetizer.GroupKeep {
m.keepBuffer = append(m.keepBuffer[:i], m.keepBuffer[i+1:]...)
m.removeGroupKeep()
break
}
}
}
func (m *StreamMount) AddListener(listener *StreamListener) {
m.listenersLock.Lock()
defer m.listenersLock.Unlock()
m.listeners = append(m.listeners, listener)
}
func (m *StreamMount) GetListeners() (entries []*ListenerInformation) {
m.listenersLock.Lock()
defer m.listenersLock.Unlock()
for _, l := range m.listeners {
entries = append(entries, &l.Information)
}
return
}
func (m *StreamMount) Process() {
defer func() {
m.listenersLock.Lock()
for _, l := range m.listeners {
l.Close()
}
m.listeners = m.listeners[:0]
m.listenersLock.Unlock()
}()
var toRemove []int
for {
packet := m.Packetizer.GetPacket()
if packet == nil {
return
}
//TODO: do this via goroutine messaging?
for i, l := range m.listeners {
if l.Start != nil {
l.Start(m.keepBuffer)
l.Start = nil
}
if l.Write(packet) != nil {
toRemove = append(toRemove, i)
l.Close()
}
}
if len(toRemove) > 0 {
m.listenersLock.Lock()
//TODO: remove more than one per iteration
for _, i := range toRemove {
m.listeners = append(m.listeners[:i], m.listeners[i+1:]...)
break
}
m.listenersLock.Unlock()
toRemove = toRemove[:0]
}
m.removeDiscard() //always remove discards
switch packet.KeepMode() {
case packetizer.KeepLast:
m.removeKeepLast()
fallthrough
case packetizer.Keep:
m.keepBuffer = append(m.keepBuffer, packet)
case packetizer.GroupKeep:
m.keepBuffer = append(m.keepBuffer, packet)
case packetizer.GroupDiscard:
m.removeGroupKeep()
case packetizer.Discard:
m.keepBuffer = append(m.keepBuffer, packet)
}
}
}