oggreader.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
  2. // SPDX-License-Identifier: MIT
  3. // Package oggreader implements the Ogg media container reader
  4. package oggreader
  5. import (
  6. "encoding/binary"
  7. "errors"
  8. "io"
  9. )
  10. const (
  11. pageHeaderTypeBeginningOfStream = 0x02
  12. pageHeaderSignature = "OggS"
  13. idPageSignature = "OpusHead"
  14. pageHeaderLen = 27
  15. idPagePayloadLength = 19
  16. )
  17. var (
  18. errNilStream = errors.New("stream is nil")
  19. errBadIDPageSignature = errors.New("bad header signature")
  20. errBadIDPageType = errors.New("wrong header, expected beginning of stream")
  21. errBadIDPageLength = errors.New("payload for id page must be 19 bytes")
  22. errBadIDPagePayloadSignature = errors.New("bad payload signature")
  23. errShortPageHeader = errors.New("not enough data for payload header")
  24. errChecksumMismatch = errors.New("expected and actual checksum do not match")
  25. )
  26. // OggReader is used to read Ogg files and return page payloads
  27. type OggReader struct {
  28. stream io.Reader
  29. bytesReadSuccesfully int64
  30. checksumTable *[256]uint32
  31. doChecksum bool
  32. }
  33. // OggHeader is the metadata from the first two pages
  34. // in the file (ID and Comment)
  35. //
  36. // https://tools.ietf.org/html/rfc7845.html#section-3
  37. type OggHeader struct {
  38. ChannelMap uint8
  39. Channels uint8
  40. OutputGain uint16
  41. PreSkip uint16
  42. SampleRate uint32
  43. Version uint8
  44. }
  45. // OggPageHeader is the metadata for a Page
  46. // Pages are the fundamental unit of multiplexing in an Ogg stream
  47. //
  48. // https://tools.ietf.org/html/rfc7845.html#section-1
  49. type OggPageHeader struct {
  50. GranulePosition uint64
  51. sig [4]byte
  52. version uint8
  53. headerType uint8
  54. serial uint32
  55. index uint32
  56. segmentsCount uint8
  57. }
  58. // NewWith returns a new Ogg reader and Ogg header
  59. // with an io.Reader input
  60. func NewWith(in io.Reader) (*OggReader, *OggHeader, error) {
  61. return newWith(in /* doChecksum */, true)
  62. }
  63. func newWith(in io.Reader, doChecksum bool) (*OggReader, *OggHeader, error) {
  64. if in == nil {
  65. return nil, nil, errNilStream
  66. }
  67. reader := &OggReader{
  68. stream: in,
  69. checksumTable: generateChecksumTable(),
  70. doChecksum: doChecksum,
  71. }
  72. header, err := reader.readHeaders()
  73. if err != nil {
  74. return nil, nil, err
  75. }
  76. return reader, header, nil
  77. }
  78. func (o *OggReader) readHeaders() (*OggHeader, error) {
  79. payload, pageHeader, err := o.ParseNextPage()
  80. if err != nil {
  81. return nil, err
  82. }
  83. header := &OggHeader{}
  84. if string(pageHeader.sig[:]) != pageHeaderSignature {
  85. return nil, errBadIDPageSignature
  86. }
  87. if pageHeader.headerType != pageHeaderTypeBeginningOfStream {
  88. return nil, errBadIDPageType
  89. }
  90. if len(payload) != idPagePayloadLength {
  91. return nil, errBadIDPageLength
  92. }
  93. if s := string(payload[:8]); s != idPageSignature {
  94. return nil, errBadIDPagePayloadSignature
  95. }
  96. header.Version = payload[8]
  97. header.Channels = payload[9]
  98. header.PreSkip = binary.LittleEndian.Uint16(payload[10:12])
  99. header.SampleRate = binary.LittleEndian.Uint32(payload[12:16])
  100. header.OutputGain = binary.LittleEndian.Uint16(payload[16:18])
  101. header.ChannelMap = payload[18]
  102. return header, nil
  103. }
  104. // ParseNextPage reads from stream and returns Ogg page payload, header,
  105. // and an error if there is incomplete page data.
  106. func (o *OggReader) ParseNextPage() ([]byte, *OggPageHeader, error) {
  107. h := make([]byte, pageHeaderLen)
  108. n, err := io.ReadFull(o.stream, h)
  109. if err != nil {
  110. return nil, nil, err
  111. } else if n < len(h) {
  112. return nil, nil, errShortPageHeader
  113. }
  114. pageHeader := &OggPageHeader{
  115. sig: [4]byte{h[0], h[1], h[2], h[3]},
  116. }
  117. pageHeader.version = h[4]
  118. pageHeader.headerType = h[5]
  119. pageHeader.GranulePosition = binary.LittleEndian.Uint64(h[6 : 6+8])
  120. pageHeader.serial = binary.LittleEndian.Uint32(h[14 : 14+4])
  121. pageHeader.index = binary.LittleEndian.Uint32(h[18 : 18+4])
  122. pageHeader.segmentsCount = h[26]
  123. sizeBuffer := make([]byte, pageHeader.segmentsCount)
  124. if _, err = io.ReadFull(o.stream, sizeBuffer); err != nil {
  125. return nil, nil, err
  126. }
  127. payloadSize := 0
  128. for _, s := range sizeBuffer {
  129. payloadSize += int(s)
  130. }
  131. payload := make([]byte, payloadSize)
  132. if _, err = io.ReadFull(o.stream, payload); err != nil {
  133. return nil, nil, err
  134. }
  135. if o.doChecksum {
  136. var checksum uint32
  137. updateChecksum := func(v byte) {
  138. checksum = (checksum << 8) ^ o.checksumTable[byte(checksum>>24)^v]
  139. }
  140. for index := range h {
  141. // Don't include expected checksum in our generation
  142. if index > 21 && index < 26 {
  143. updateChecksum(0)
  144. continue
  145. }
  146. updateChecksum(h[index])
  147. }
  148. for _, s := range sizeBuffer {
  149. updateChecksum(s)
  150. }
  151. for index := range payload {
  152. updateChecksum(payload[index])
  153. }
  154. if binary.LittleEndian.Uint32(h[22:22+4]) != checksum {
  155. return nil, nil, errChecksumMismatch
  156. }
  157. }
  158. return payload, pageHeader, nil
  159. }
  160. // ResetReader resets the internal stream of OggReader. This is useful
  161. // for live streams, where the end of the file might be read without the
  162. // data being finished.
  163. func (o *OggReader) ResetReader(reset func(bytesRead int64) io.Reader) {
  164. o.stream = reset(o.bytesReadSuccesfully)
  165. }
  166. func generateChecksumTable() *[256]uint32 {
  167. var table [256]uint32
  168. const poly = 0x04c11db7
  169. for i := range table {
  170. r := uint32(i) << 24
  171. for j := 0; j < 8; j++ {
  172. if (r & 0x80000000) != 0 {
  173. r = (r << 1) ^ poly
  174. } else {
  175. r <<= 1
  176. }
  177. table[i] = (r & 0xffffffff)
  178. }
  179. }
  180. return &table
  181. }