DataHoarder
7b3e844f6d
All checks were successful
continuous-integration/drone/push Build is passing
258 lines
6.1 KiB
Go
258 lines
6.1 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(group *sync.WaitGroup) {
|
|
defer group.Done()
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|