wav.go 4.8 KB

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