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) } } }