package oggvorbis import ( "context" "fmt" "io" "github.com/jan/fm-rds-tx/internal/ingest" "github.com/jan/fm-rds-tx/internal/ingest/decoder" libvorbis "github.com/jfreymuth/oggvorbis" ) type Decoder struct{} func New() *Decoder { return &Decoder{} } func (d *Decoder) Name() string { return "oggvorbis-native" } func (d *Decoder) DecodeStream(ctx context.Context, r io.Reader, meta decoder.StreamMeta, emit func(ingest.PCMChunk) error) error { if r == nil { return fmt.Errorf("%w: ogg/vorbis decoder stream reader is nil", decoder.ErrUnsupported) } if emit == nil { return fmt.Errorf("%w: ogg/vorbis decoder emit callback is nil", decoder.ErrUnsupported) } dec, err := libvorbis.NewReader(r) if err != nil { return fmt.Errorf("%w: ogg/vorbis decoder init: %v", decoder.ErrUnsupported, err) } channels := dec.Channels() if channels <= 0 { if meta.Channels > 0 { channels = meta.Channels } else { return fmt.Errorf("%w: ogg/vorbis decoder invalid channel count", decoder.ErrUnsupported) } } sampleRate := decoder.ResolveSampleRate(dec.SampleRate(), meta) const chunkFrames = 1024 buf := make([]float32, chunkFrames*channels) seq := uint64(0) for { select { case <-ctx.Done(): return nil default: } n, readErr := dec.Read(buf) if n > 0 { chunk := decoder.BuildChunk( decoder.Float32ToPCM32(buf[:n]), channels, sampleRate, seq, meta.SourceID, ) if err := emit(chunk); err != nil { return err } seq++ } if readErr != nil { if readErr == io.EOF { return nil } return fmt.Errorf("ogg/vorbis decoder read pcm: %w", readErr) } } }