Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

153 wiersze
3.8KB

  1. package audio
  2. import (
  3. "encoding/binary"
  4. "fmt"
  5. "io"
  6. "os"
  7. )
  8. // WAVSource loads a PCM WAV file into memory and provides frame-by-frame access.
  9. type WAVSource struct {
  10. frames []Frame
  11. index int
  12. SampleRate int
  13. Channels int
  14. }
  15. // LoadWAVSource reads and decodes a WAV file. It properly scans for the "fmt "
  16. // and "data" chunks, handling files with extra metadata chunks (LIST, INFO,
  17. // bext, etc.) that appear between headers.
  18. func LoadWAVSource(path string) (*WAVSource, error) {
  19. f, err := os.Open(path)
  20. if err != nil {
  21. return nil, err
  22. }
  23. defer f.Close()
  24. // Read RIFF header (12 bytes)
  25. riffHeader := make([]byte, 12)
  26. if _, err := io.ReadFull(f, riffHeader); err != nil {
  27. return nil, fmt.Errorf("read riff header: %w", err)
  28. }
  29. if string(riffHeader[0:4]) != "RIFF" || string(riffHeader[8:12]) != "WAVE" {
  30. return nil, fmt.Errorf("not a RIFF/WAVE file")
  31. }
  32. var (
  33. audioFormat uint16
  34. channels uint16
  35. sampleRate uint32
  36. bitsPerSample uint16
  37. dataBytes []byte
  38. fmtFound bool
  39. dataFound bool
  40. )
  41. // Scan chunks
  42. for {
  43. var chunkID [4]byte
  44. var chunkSize uint32
  45. if _, err := io.ReadFull(f, chunkID[:]); err != nil {
  46. if err == io.EOF || err == io.ErrUnexpectedEOF {
  47. break
  48. }
  49. return nil, fmt.Errorf("read chunk id: %w", err)
  50. }
  51. if err := binary.Read(f, binary.LittleEndian, &chunkSize); err != nil {
  52. return nil, fmt.Errorf("read chunk size: %w", err)
  53. }
  54. switch string(chunkID[:]) {
  55. case "fmt ":
  56. if chunkSize < 16 {
  57. return nil, fmt.Errorf("fmt chunk too small: %d", chunkSize)
  58. }
  59. fmtData := make([]byte, chunkSize)
  60. if _, err := io.ReadFull(f, fmtData); err != nil {
  61. return nil, fmt.Errorf("read fmt chunk: %w", err)
  62. }
  63. audioFormat = binary.LittleEndian.Uint16(fmtData[0:2])
  64. channels = binary.LittleEndian.Uint16(fmtData[2:4])
  65. sampleRate = binary.LittleEndian.Uint32(fmtData[4:8])
  66. bitsPerSample = binary.LittleEndian.Uint16(fmtData[14:16])
  67. fmtFound = true
  68. case "data":
  69. dataBytes = make([]byte, chunkSize)
  70. if _, err := io.ReadFull(f, dataBytes); err != nil {
  71. return nil, fmt.Errorf("read data chunk: %w", err)
  72. }
  73. dataFound = true
  74. default:
  75. // Skip unknown chunks, respecting RIFF padding (chunks are word-aligned)
  76. skip := int64(chunkSize)
  77. if chunkSize%2 != 0 {
  78. skip++
  79. }
  80. if _, err := io.CopyN(io.Discard, f, skip); err != nil {
  81. // Could be EOF if this is the last chunk
  82. break
  83. }
  84. }
  85. if fmtFound && dataFound {
  86. break
  87. }
  88. }
  89. if !fmtFound {
  90. return nil, fmt.Errorf("no fmt chunk found")
  91. }
  92. if !dataFound {
  93. return nil, fmt.Errorf("no data chunk found")
  94. }
  95. if audioFormat != 1 {
  96. return nil, fmt.Errorf("only PCM wav supported (format=%d)", audioFormat)
  97. }
  98. if bitsPerSample != 16 {
  99. return nil, fmt.Errorf("only 16-bit PCM wav supported (bits=%d)", bitsPerSample)
  100. }
  101. if channels != 1 && channels != 2 {
  102. return nil, fmt.Errorf("only mono/stereo wav supported (channels=%d)", channels)
  103. }
  104. if sampleRate == 0 {
  105. return nil, fmt.Errorf("invalid wav sample rate")
  106. }
  107. step := int(channels) * 2
  108. frames := make([]Frame, 0, len(dataBytes)/step)
  109. for i := 0; i+step <= len(dataBytes); i += step {
  110. l := pcm16ToSample(int16(binary.LittleEndian.Uint16(dataBytes[i : i+2])))
  111. r := l
  112. if channels == 2 {
  113. r = pcm16ToSample(int16(binary.LittleEndian.Uint16(dataBytes[i+2 : i+4])))
  114. }
  115. frames = append(frames, NewFrame(l, r))
  116. }
  117. return &WAVSource{
  118. frames: frames,
  119. SampleRate: int(sampleRate),
  120. Channels: int(channels),
  121. }, nil
  122. }
  123. // NextFrame returns the next audio frame, looping at the end.
  124. func (s *WAVSource) NextFrame() Frame {
  125. if len(s.frames) == 0 {
  126. return NewFrame(0, 0)
  127. }
  128. frame := s.frames[s.index]
  129. s.index++
  130. if s.index >= len(s.frames) {
  131. s.index = 0
  132. }
  133. return frame
  134. }
  135. func pcm16ToSample(v int16) Sample {
  136. return Sample(float64(v) / 32768.0).Clamp()
  137. }