|
- package oggvorbis
-
- import (
- "context"
- "fmt"
- "io"
- "math"
- "time"
-
- "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 := dec.SampleRate()
- if sampleRate <= 0 {
- if meta.SampleRateHz > 0 {
- sampleRate = meta.SampleRateHz
- } else {
- sampleRate = 44100
- }
- }
-
- 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 := ingest.PCMChunk{
- Samples: float32ToPCM32(buf[:n]),
- Channels: channels,
- SampleRateHz: sampleRate,
- Sequence: seq,
- Timestamp: time.Now(),
- SourceID: 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)
- }
- }
- }
-
- func float32ToPCM32(in []float32) []int32 {
- out := make([]int32, len(in))
- for i, sample := range in {
- if sample > 1 {
- sample = 1
- } else if sample < -1 {
- sample = -1
- }
- if sample == -1 {
- out[i] = math.MinInt32
- continue
- }
- out[i] = int32(sample * math.MaxInt32)
- }
- return out
- }
|