wav.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // Package wav reads the header of a WAV file: format, channels, sample rate,
  2. // bit depth and frame count. It does not decode audio.
  3. package wav
  4. import (
  5. "encoding/binary"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "os"
  10. "time"
  11. )
  12. // Format codes from the fmt chunk.
  13. const (
  14. FormatPCM = 1
  15. FormatFloat = 3
  16. FormatExtensible = 0xFFFE
  17. )
  18. // Limits for a sample MPCkit will assign (build plan 4.3 and section 7).
  19. const (
  20. MaxChannels = 2
  21. MaxDuration = 20 * time.Minute
  22. )
  23. // Info describes a WAV file.
  24. type Info struct {
  25. Format int // FormatPCM or FormatFloat (the sub-format of an extensible header)
  26. Extensible bool
  27. Channels int
  28. SampleRate int
  29. Bits int
  30. BlockAlign int
  31. Frames int64
  32. DataOffset int64 // byte offset of the first sample
  33. DataSize int64 // bytes of sample data actually present
  34. }
  35. // Duration returns the length of the audio.
  36. func (i Info) Duration() time.Duration {
  37. if i.SampleRate == 0 {
  38. return 0
  39. }
  40. return time.Duration(i.Frames) * time.Second / time.Duration(i.SampleRate)
  41. }
  42. // ReadFile reads the header of the WAV file at path.
  43. func ReadFile(path string) (Info, error) {
  44. f, err := os.Open(path)
  45. if err != nil {
  46. return Info{}, err
  47. }
  48. defer f.Close()
  49. st, err := f.Stat()
  50. if err != nil {
  51. return Info{}, err
  52. }
  53. return Read(f, st.Size())
  54. }
  55. // Read reads a WAV header from r, which holds size bytes.
  56. func Read(r io.ReaderAt, size int64) (Info, error) {
  57. var hdr [12]byte
  58. if _, err := r.ReadAt(hdr[:], 0); err != nil {
  59. return Info{}, errors.New("not a WAV file: too short")
  60. }
  61. if string(hdr[0:4]) != "RIFF" || string(hdr[8:12]) != "WAVE" {
  62. if string(hdr[0:4]) == "RF64" {
  63. return Info{}, errors.New("RF64 WAV files are not supported")
  64. }
  65. return Info{}, errors.New("not a WAV file")
  66. }
  67. var info Info
  68. var haveFmt, haveData bool
  69. pos := int64(12)
  70. for pos+8 <= size && !(haveFmt && haveData) {
  71. var ch [8]byte
  72. if _, err := r.ReadAt(ch[:], pos); err != nil {
  73. return Info{}, fmt.Errorf("reading chunk header: %w", err)
  74. }
  75. id := string(ch[0:4])
  76. n := int64(binary.LittleEndian.Uint32(ch[4:8]))
  77. body := pos + 8
  78. switch id {
  79. case "fmt ":
  80. if n < 16 || body+n > size {
  81. return Info{}, errors.New("fmt chunk is too short")
  82. }
  83. buf := make([]byte, n)
  84. if _, err := r.ReadAt(buf, body); err != nil {
  85. return Info{}, fmt.Errorf("reading fmt chunk: %w", err)
  86. }
  87. if err := info.parseFmt(buf); err != nil {
  88. return Info{}, err
  89. }
  90. haveFmt = true
  91. case "data":
  92. info.DataOffset = body
  93. // Some writers leave the size at 0 or 0xFFFFFFFF; count what is there.
  94. info.DataSize = min(n, size-body)
  95. if n == 0 || n == 0xFFFFFFFF {
  96. info.DataSize = size - body
  97. }
  98. haveData = true
  99. }
  100. pos = body + n + n&1
  101. }
  102. if !haveFmt {
  103. return Info{}, errors.New("WAV file has no fmt chunk")
  104. }
  105. if !haveData {
  106. return Info{}, errors.New("WAV file has no data chunk")
  107. }
  108. info.Frames = info.DataSize / int64(info.BlockAlign)
  109. return info, nil
  110. }
  111. func (i *Info) parseFmt(b []byte) error {
  112. le := binary.LittleEndian
  113. i.Format = int(le.Uint16(b[0:2]))
  114. i.Channels = int(le.Uint16(b[2:4]))
  115. i.SampleRate = int(le.Uint32(b[4:8]))
  116. i.BlockAlign = int(le.Uint16(b[12:14]))
  117. i.Bits = int(le.Uint16(b[14:16]))
  118. if i.Format == FormatExtensible {
  119. // cbSize(2) validBits(2) channelMask(4) subFormat GUID(16): the
  120. // first two bytes of the GUID are the real format code.
  121. if len(b) < 40 {
  122. return errors.New("extensible fmt chunk is too short")
  123. }
  124. i.Extensible = true
  125. i.Format = int(le.Uint16(b[24:26]))
  126. }
  127. if i.Channels == 0 || i.SampleRate == 0 || i.BlockAlign == 0 {
  128. return errors.New("WAV header has zero channels, rate or block size")
  129. }
  130. if i.BlockAlign != i.Channels*((i.Bits+7)/8) {
  131. return fmt.Errorf("WAV block size %d does not match %d channels of %d bits", i.BlockAlign, i.Channels, i.Bits)
  132. }
  133. return nil
  134. }
  135. // Validate reports why the device or MPCkit cannot use the file, or nil.
  136. // Accepted: uncompressed integer PCM (8, 16, 24, 32-bit) or 32-bit float,
  137. // mono or stereo, at most 20 minutes.
  138. func (i Info) Validate() error {
  139. switch i.Format {
  140. case FormatPCM:
  141. switch i.Bits {
  142. case 8, 16, 24, 32:
  143. default:
  144. return fmt.Errorf("%d-bit PCM is not supported (use 8, 16, 24 or 32-bit)", i.Bits)
  145. }
  146. case FormatFloat:
  147. if i.Bits != 32 {
  148. return fmt.Errorf("%d-bit float is not supported (use 32-bit float)", i.Bits)
  149. }
  150. default:
  151. return fmt.Errorf("compressed WAV (format code %#x) is not supported; use uncompressed PCM or float", i.Format)
  152. }
  153. if i.Channels > MaxChannels {
  154. return fmt.Errorf("%d channels: only mono and stereo are supported", i.Channels)
  155. }
  156. if i.Frames == 0 {
  157. return errors.New("WAV file holds no audio")
  158. }
  159. if i.Duration() > MaxDuration {
  160. return fmt.Errorf("sample is %s long; the limit is 20 minutes", i.Duration().Round(time.Second))
  161. }
  162. return nil
  163. }