2022-03-01 23:31:29 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
2022-05-15 14:42:28 +00:00
|
|
|
"git.gammaspectra.live/S.O.N.G/Kirika/audio/filter"
|
2022-03-01 23:31:29 +00:00
|
|
|
"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"
|
2022-03-04 12:25:59 +00:00
|
|
|
"github.com/enriquebris/goconcurrentqueue"
|
2022-03-01 23:31:29 +00:00
|
|
|
"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 {
|
2022-05-15 14:42:28 +00:00
|
|
|
Mount string
|
|
|
|
MimeType string
|
|
|
|
FormatDescription string
|
|
|
|
Packetizer packetizer.Packetizer
|
|
|
|
Options map[string]interface{}
|
|
|
|
SampleRate int
|
|
|
|
Channels int
|
2022-07-16 13:06:21 +00:00
|
|
|
OffsetStart bool
|
2022-05-15 14:42:28 +00:00
|
|
|
MetadataQueue *goconcurrentqueue.FIFO
|
|
|
|
listeners []*StreamListener
|
|
|
|
listenersLock sync.Mutex
|
|
|
|
keepBuffer []packetizer.Packet
|
2022-03-01 23:31:29 +00:00
|
|
|
}
|
|
|
|
|
2022-05-15 14:42:28 +00:00
|
|
|
func NewStreamMount(source audio.Source, config *StreamConfig) *StreamMount {
|
2022-03-01 23:31:29 +00:00
|
|
|
var encoderFormat format.Encoder
|
|
|
|
options := make(map[string]interface{})
|
|
|
|
var mimeType string
|
|
|
|
|
|
|
|
reader, writer := io.Pipe()
|
|
|
|
var packetizerEntry packetizer.Packetizer
|
|
|
|
|
2022-05-15 14:42:28 +00:00
|
|
|
bitrate := config.GetOption("bitrate", nil)
|
2022-03-03 14:00:34 +00:00
|
|
|
|
2022-07-19 15:21:51 +00:00
|
|
|
sampleRate := config.GetIntOption("samplerate", source.GetSampleRate())
|
2022-05-15 14:42:28 +00:00
|
|
|
|
2022-07-19 15:21:51 +00:00
|
|
|
channels := config.GetIntOption("channels", source.GetChannels())
|
2022-05-15 14:42:28 +00:00
|
|
|
|
|
|
|
switch config.Codec {
|
|
|
|
case "vorbis":
|
|
|
|
return nil
|
2022-03-01 23:31:29 +00:00
|
|
|
case "opus":
|
|
|
|
encoderFormat = opus.NewFormat()
|
|
|
|
if bitrate != nil {
|
|
|
|
options["bitrate"] = bitrate
|
|
|
|
}
|
2022-05-15 14:42:28 +00:00
|
|
|
sampleRate = opus.FixedSampleRate
|
2022-03-01 23:31:29 +00:00
|
|
|
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()
|
2022-05-15 14:42:28 +00:00
|
|
|
|
|
|
|
options["bitdepth"] = config.GetIntOption("bitdepth", 16)
|
|
|
|
options["compression_level"] = config.GetIntOption("compression_level", 8)
|
|
|
|
options["block_size"] = config.GetIntOption("block_size", 0)
|
|
|
|
|
2022-03-01 23:31:29 +00:00
|
|
|
mimeType = "audio/flac"
|
2022-05-15 14:42:28 +00:00
|
|
|
if config.Container == "ogg" {
|
|
|
|
options["ogg"] = true
|
|
|
|
mimeType = "audio/ogg;codecs=flac"
|
2022-07-14 21:25:03 +00:00
|
|
|
packetizerEntry = packetizer.NewOggPacketizer(reader)
|
|
|
|
} else {
|
|
|
|
packetizerEntry = packetizer.NewFLACPacketizer(reader)
|
2022-05-15 14:42:28 +00:00
|
|
|
}
|
2022-03-01 23:31:29 +00:00
|
|
|
case "aac":
|
|
|
|
encoderFormat = aac.NewFormat()
|
|
|
|
if bitrate != nil {
|
|
|
|
options["bitrate"] = bitrate
|
|
|
|
}
|
2022-05-15 17:44:35 +00:00
|
|
|
options["mode"] = config.GetStringOption("mode", "lc")
|
2022-07-16 13:06:21 +00:00
|
|
|
options["afterburner"] = config.GetBoolOption("afterburner", true)
|
2022-05-15 14:42:28 +00:00
|
|
|
mimeType = "audio/aac;codecs=mp4a.40.2"
|
2022-05-15 17:44:35 +00:00
|
|
|
if options["mode"] == "he" || options["mode"] == "hev1" || options["bitrate"] == "vbr2" {
|
|
|
|
mimeType = "audio/aac;codecs=mp4a.40.5"
|
|
|
|
} else if options["mode"] == "hev2" || options["bitrate"] == "vbr1" {
|
|
|
|
mimeType = "audio/aac;codecs=mp4a.40.29"
|
2022-03-01 23:31:29 +00:00
|
|
|
}
|
|
|
|
packetizerEntry = packetizer.NewAdtsPacketizer(reader)
|
|
|
|
}
|
|
|
|
|
|
|
|
if encoderFormat == nil {
|
2022-05-15 14:42:28 +00:00
|
|
|
switch config.Container {
|
2022-03-01 23:31:29 +00:00
|
|
|
case "ogg":
|
|
|
|
encoderFormat = opus.NewFormat()
|
|
|
|
if bitrate != nil {
|
|
|
|
options["bitrate"] = bitrate
|
|
|
|
}
|
2022-05-15 14:42:28 +00:00
|
|
|
sampleRate = opus.FixedSampleRate
|
2022-03-01 23:31:29 +00:00
|
|
|
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()
|
2022-05-15 14:42:28 +00:00
|
|
|
|
|
|
|
options["bitdepth"] = config.GetIntOption("bitdepth", 16)
|
|
|
|
options["compression_level"] = config.GetIntOption("compression_level", 8)
|
|
|
|
options["block_size"] = config.GetIntOption("block_size", 0)
|
|
|
|
|
2022-03-01 23:31:29 +00:00
|
|
|
mimeType = "audio/flac"
|
|
|
|
packetizerEntry = packetizer.NewFLACPacketizer(reader)
|
|
|
|
case "adts", "aac":
|
|
|
|
encoderFormat = aac.NewFormat()
|
|
|
|
if bitrate != nil {
|
|
|
|
options["bitrate"] = bitrate
|
|
|
|
}
|
2022-05-15 17:44:35 +00:00
|
|
|
options["mode"] = config.GetStringOption("mode", "lc")
|
2022-07-16 13:06:21 +00:00
|
|
|
options["afterburner"] = config.GetBoolOption("afterburner", true)
|
2022-05-15 14:42:28 +00:00
|
|
|
mimeType = "audio/aac;codecs=mp4a.40.2"
|
2022-05-15 17:44:35 +00:00
|
|
|
if options["mode"] == "he" || options["mode"] == "hev1" || options["bitrate"] == "vbr2" {
|
|
|
|
mimeType = "audio/aac;codecs=mp4a.40.5"
|
|
|
|
} else if options["mode"] == "hev2" || options["bitrate"] == "vbr1" {
|
|
|
|
mimeType = "audio/aac;codecs=mp4a.40.29"
|
|
|
|
}
|
2022-03-01 23:31:29 +00:00
|
|
|
packetizerEntry = packetizer.NewAdtsPacketizer(reader)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if encoderFormat == nil || packetizerEntry == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-05-15 14:42:28 +00:00
|
|
|
go func() {
|
|
|
|
defer writer.Close()
|
|
|
|
|
2022-07-19 15:21:51 +00:00
|
|
|
if channels != source.GetChannels() {
|
2022-05-15 14:42:28 +00:00
|
|
|
if channels == 1 {
|
|
|
|
source = filter.MonoFilter{}.Process(source)
|
|
|
|
} else if channels == 2 {
|
|
|
|
source = filter.StereoFilter{}.Process(source)
|
2022-03-01 23:31:29 +00:00
|
|
|
}
|
2022-05-15 14:42:28 +00:00
|
|
|
}
|
|
|
|
|
2022-07-19 15:21:51 +00:00
|
|
|
if sampleRate != source.GetSampleRate() {
|
2022-05-20 15:34:43 +00:00
|
|
|
source = filter.NewResampleFilter(sampleRate, filter.QualityFastest, 0).Process(source)
|
2022-05-15 14:42:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := encoderFormat.Encode(source, writer, options); err != nil {
|
|
|
|
log.Panic(err)
|
|
|
|
}
|
|
|
|
}()
|
2022-03-01 23:31:29 +00:00
|
|
|
|
|
|
|
return &StreamMount{
|
2022-05-15 14:42:28 +00:00
|
|
|
Mount: config.MountPath,
|
|
|
|
MimeType: mimeType,
|
|
|
|
FormatDescription: encoderFormat.Description(),
|
|
|
|
Packetizer: packetizerEntry,
|
|
|
|
SampleRate: sampleRate,
|
2022-07-16 13:06:21 +00:00
|
|
|
OffsetStart: config.GetBoolOption("offset_start", true),
|
2022-05-15 14:42:28 +00:00
|
|
|
Channels: channels,
|
|
|
|
Options: options,
|
|
|
|
MetadataQueue: goconcurrentqueue.NewFIFO(),
|
2022-03-01 23:31:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-03 14:00:34 +00:00
|
|
|
func (m *StreamMount) removeDiscard(sampleNumber int64) {
|
2022-03-01 23:31:29 +00:00
|
|
|
for i, p := range m.keepBuffer {
|
2022-03-04 12:25:59 +00:00
|
|
|
if p.KeepMode() == packetizer.Discard && p.GetEndSampleNumber() <= sampleNumber {
|
2022-03-01 23:31:29 +00:00
|
|
|
m.keepBuffer = append(m.keepBuffer[:i], m.keepBuffer[i+1:]...)
|
2022-03-03 14:00:34 +00:00
|
|
|
m.removeDiscard(sampleNumber)
|
|
|
|
break
|
2022-03-04 12:25:59 +00:00
|
|
|
} else if p.GetEndSampleNumber() > sampleNumber {
|
2022-03-03 14:00:34 +00:00
|
|
|
//they are placed in order
|
2022-03-01 23:31:29 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-04 12:25:59 +00:00
|
|
|
func (m *StreamMount) removeKeepLast(category int64) {
|
2022-03-01 23:31:29 +00:00
|
|
|
for i, p := range m.keepBuffer {
|
2022-03-04 12:25:59 +00:00
|
|
|
if p.Category() == category && p.KeepMode() == packetizer.KeepLast {
|
2022-03-01 23:31:29 +00:00
|
|
|
m.keepBuffer = append(m.keepBuffer[:i], m.keepBuffer[i+1:]...)
|
2022-03-04 12:25:59 +00:00
|
|
|
m.removeKeepLast(category)
|
2022-03-01 23:31:29 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-04 12:25:59 +00:00
|
|
|
func (m *StreamMount) removeGroupKeep(category int64) {
|
2022-03-01 23:31:29 +00:00
|
|
|
for i, p := range m.keepBuffer {
|
2022-03-04 12:25:59 +00:00
|
|
|
if p.Category() == category && p.KeepMode() == packetizer.GroupKeep {
|
2022-03-01 23:31:29 +00:00
|
|
|
m.keepBuffer = append(m.keepBuffer[:i], m.keepBuffer[i+1:]...)
|
2022-03-04 12:25:59 +00:00
|
|
|
m.removeGroupKeep(category)
|
2022-03-01 23:31:29 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-03-04 12:25:59 +00:00
|
|
|
func (m *StreamMount) handlePacket(packet packetizer.Packet) {
|
|
|
|
var toRemove []int
|
|
|
|
//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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(toRemove) > 0 {
|
|
|
|
m.listenersLock.Lock()
|
|
|
|
//TODO: remove more than one per iteration
|
|
|
|
for _, i := range toRemove {
|
|
|
|
l := m.listeners[i]
|
|
|
|
m.listeners = append(m.listeners[:i], m.listeners[i+1:]...)
|
|
|
|
l.Close()
|
|
|
|
break
|
|
|
|
}
|
|
|
|
m.listenersLock.Unlock()
|
|
|
|
toRemove = toRemove[:0]
|
|
|
|
}
|
|
|
|
|
|
|
|
sampleLimit := packet.GetEndSampleNumber() - int64(maxBufferSize*m.SampleRate)
|
|
|
|
|
|
|
|
m.removeDiscard(sampleLimit) //always remove discards
|
|
|
|
|
|
|
|
switch packet.KeepMode() {
|
|
|
|
case packetizer.KeepLast:
|
|
|
|
m.removeKeepLast(packet.Category())
|
|
|
|
fallthrough
|
|
|
|
case packetizer.Keep:
|
|
|
|
m.keepBuffer = append(m.keepBuffer, packet)
|
|
|
|
case packetizer.GroupKeep:
|
|
|
|
m.keepBuffer = append(m.keepBuffer, packet)
|
|
|
|
case packetizer.GroupDiscard:
|
|
|
|
m.removeGroupKeep(packet.Category())
|
|
|
|
case packetizer.Discard:
|
|
|
|
m.keepBuffer = append(m.keepBuffer, packet)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-02 17:54:56 +00:00
|
|
|
func (m *StreamMount) Process(group *sync.WaitGroup) {
|
|
|
|
defer group.Done()
|
2022-03-01 23:31:29 +00:00
|
|
|
defer func() {
|
|
|
|
m.listenersLock.Lock()
|
|
|
|
for _, l := range m.listeners {
|
|
|
|
l.Close()
|
|
|
|
}
|
|
|
|
m.listeners = m.listeners[:0]
|
|
|
|
m.listenersLock.Unlock()
|
|
|
|
}()
|
2022-03-04 12:25:59 +00:00
|
|
|
|
2022-03-01 23:31:29 +00:00
|
|
|
for {
|
|
|
|
packet := m.Packetizer.GetPacket()
|
|
|
|
if packet == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-03-04 12:25:59 +00:00
|
|
|
if item, err := m.MetadataQueue.Get(0); err == nil {
|
|
|
|
if metadataPacket, ok := item.(*QueueMetadataPacket); ok {
|
|
|
|
if packet.GetEndSampleNumber() > metadataPacket.GetStartSampleNumber() {
|
|
|
|
m.MetadataQueue.Dequeue()
|
|
|
|
m.handlePacket(metadataPacket)
|
|
|
|
}
|
2022-03-01 23:31:29 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-04 12:25:59 +00:00
|
|
|
m.handlePacket(packet)
|
2022-03-01 23:31:29 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|