Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

91 lines
2.1KB

  1. package mp3
  2. import (
  3. "context"
  4. "encoding/binary"
  5. "fmt"
  6. "io"
  7. "time"
  8. gomp3 "github.com/hajimehoshi/go-mp3"
  9. "github.com/jan/fm-rds-tx/internal/ingest"
  10. "github.com/jan/fm-rds-tx/internal/ingest/decoder"
  11. )
  12. type Decoder struct{}
  13. func New() *Decoder { return &Decoder{} }
  14. func (d *Decoder) Name() string { return "mp3-native" }
  15. func (d *Decoder) DecodeStream(ctx context.Context, r io.Reader, meta decoder.StreamMeta, emit func(ingest.PCMChunk) error) error {
  16. if r == nil {
  17. return fmt.Errorf("%w: mp3 decoder stream reader is nil", decoder.ErrUnsupported)
  18. }
  19. if emit == nil {
  20. return fmt.Errorf("%w: mp3 decoder emit callback is nil", decoder.ErrUnsupported)
  21. }
  22. dec, err := gomp3.NewDecoder(r)
  23. if err != nil {
  24. return fmt.Errorf("%w: mp3 decoder init: %v", decoder.ErrUnsupported, err)
  25. }
  26. const channels = 2 // go-mp3 always decodes to stereo s16le
  27. sampleRate := dec.SampleRate()
  28. if sampleRate <= 0 {
  29. if meta.SampleRateHz > 0 {
  30. sampleRate = meta.SampleRateHz
  31. } else {
  32. sampleRate = 44100
  33. }
  34. }
  35. const chunkFrames = 1024
  36. const frameBytes = channels * 2
  37. buf := make([]byte, chunkFrames*frameBytes)
  38. seq := uint64(0)
  39. for {
  40. select {
  41. case <-ctx.Done():
  42. return nil
  43. default:
  44. }
  45. n, readErr := io.ReadAtLeast(dec, buf, frameBytes)
  46. if readErr != nil {
  47. if readErr == io.EOF || readErr == io.ErrUnexpectedEOF {
  48. if n > 0 {
  49. if err := emitChunk(buf[:n], seq, sampleRate, meta.SourceID, emit); err != nil {
  50. return err
  51. }
  52. }
  53. return nil
  54. }
  55. return fmt.Errorf("mp3 decoder read pcm: %w", readErr)
  56. }
  57. if err := emitChunk(buf[:n], seq, sampleRate, meta.SourceID, emit); err != nil {
  58. return err
  59. }
  60. seq++
  61. }
  62. }
  63. func emitChunk(data []byte, seq uint64, sampleRate int, sourceID string, emit func(ingest.PCMChunk) error) error {
  64. samples := make([]int32, 0, len(data)/2)
  65. for i := 0; i+1 < len(data); i += 2 {
  66. v := int16(binary.LittleEndian.Uint16(data[i : i+2]))
  67. samples = append(samples, int32(v)<<16)
  68. }
  69. return emit(ingest.PCMChunk{
  70. Samples: samples,
  71. Channels: 2,
  72. SampleRateHz: sampleRate,
  73. Sequence: seq,
  74. Timestamp: time.Now(),
  75. SourceID: sourceID,
  76. })
  77. }