ivfreader.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
  2. // SPDX-License-Identifier: MIT
  3. // Package ivfreader implements IVF media container reader
  4. package ivfreader
  5. import (
  6. "encoding/binary"
  7. "errors"
  8. "fmt"
  9. "io"
  10. )
  11. const (
  12. ivfFileHeaderSignature = "DKIF"
  13. ivfFileHeaderSize = 32
  14. ivfFrameHeaderSize = 12
  15. )
  16. var (
  17. errNilStream = errors.New("stream is nil")
  18. errIncompleteFrameHeader = errors.New("incomplete frame header")
  19. errIncompleteFrameData = errors.New("incomplete frame data")
  20. errIncompleteFileHeader = errors.New("incomplete file header")
  21. errSignatureMismatch = errors.New("IVF signature mismatch")
  22. errUnknownIVFVersion = errors.New("IVF version unknown, parser may not parse correctly")
  23. )
  24. // IVFFileHeader 32-byte header for IVF files
  25. // https://wiki.multimedia.cx/index.php/IVF
  26. type IVFFileHeader struct {
  27. signature string // 0-3
  28. version uint16 // 4-5
  29. headerSize uint16 // 6-7
  30. FourCC string // 8-11
  31. Width uint16 // 12-13
  32. Height uint16 // 14-15
  33. TimebaseDenominator uint32 // 16-19
  34. TimebaseNumerator uint32 // 20-23
  35. NumFrames uint32 // 24-27
  36. unused uint32 // 28-31
  37. }
  38. // IVFFrameHeader 12-byte header for IVF frames
  39. // https://wiki.multimedia.cx/index.php/IVF
  40. type IVFFrameHeader struct {
  41. FrameSize uint32 // 0-3
  42. Timestamp uint64 // 4-11
  43. }
  44. // IVFReader is used to read IVF files and return frame payloads
  45. type IVFReader struct {
  46. stream io.Reader
  47. bytesReadSuccesfully int64
  48. }
  49. // NewWith returns a new IVF reader and IVF file header
  50. // with an io.Reader input
  51. func NewWith(in io.Reader) (*IVFReader, *IVFFileHeader, error) {
  52. if in == nil {
  53. return nil, nil, errNilStream
  54. }
  55. reader := &IVFReader{
  56. stream: in,
  57. }
  58. header, err := reader.parseFileHeader()
  59. if err != nil {
  60. return nil, nil, err
  61. }
  62. return reader, header, nil
  63. }
  64. // ResetReader resets the internal stream of IVFReader. This is useful
  65. // for live streams, where the end of the file might be read without the
  66. // data being finished.
  67. func (i *IVFReader) ResetReader(reset func(bytesRead int64) io.Reader) {
  68. i.stream = reset(i.bytesReadSuccesfully)
  69. }
  70. // ParseNextFrame reads from stream and returns IVF frame payload, header,
  71. // and an error if there is incomplete frame data.
  72. // Returns all nil values when no more frames are available.
  73. func (i *IVFReader) ParseNextFrame() ([]byte, *IVFFrameHeader, error) {
  74. buffer := make([]byte, ivfFrameHeaderSize)
  75. var header *IVFFrameHeader
  76. bytesRead, err := io.ReadFull(i.stream, buffer)
  77. headerBytesRead := bytesRead
  78. if errors.Is(err, io.ErrUnexpectedEOF) {
  79. return nil, nil, errIncompleteFrameHeader
  80. } else if err != nil {
  81. return nil, nil, err
  82. }
  83. header = &IVFFrameHeader{
  84. FrameSize: binary.LittleEndian.Uint32(buffer[:4]),
  85. Timestamp: binary.LittleEndian.Uint64(buffer[4:12]),
  86. }
  87. payload := make([]byte, header.FrameSize)
  88. bytesRead, err = io.ReadFull(i.stream, payload)
  89. if errors.Is(err, io.ErrUnexpectedEOF) {
  90. return nil, nil, errIncompleteFrameData
  91. } else if err != nil {
  92. return nil, nil, err
  93. }
  94. i.bytesReadSuccesfully += int64(headerBytesRead) + int64(bytesRead)
  95. return payload, header, nil
  96. }
  97. // parseFileHeader reads 32 bytes from stream and returns
  98. // IVF file header. This is always called before ParseNextFrame()
  99. func (i *IVFReader) parseFileHeader() (*IVFFileHeader, error) {
  100. buffer := make([]byte, ivfFileHeaderSize)
  101. bytesRead, err := io.ReadFull(i.stream, buffer)
  102. if errors.Is(err, io.ErrUnexpectedEOF) {
  103. return nil, errIncompleteFileHeader
  104. } else if err != nil {
  105. return nil, err
  106. }
  107. header := &IVFFileHeader{
  108. signature: string(buffer[:4]),
  109. version: binary.LittleEndian.Uint16(buffer[4:6]),
  110. headerSize: binary.LittleEndian.Uint16(buffer[6:8]),
  111. FourCC: string(buffer[8:12]),
  112. Width: binary.LittleEndian.Uint16(buffer[12:14]),
  113. Height: binary.LittleEndian.Uint16(buffer[14:16]),
  114. TimebaseDenominator: binary.LittleEndian.Uint32(buffer[16:20]),
  115. TimebaseNumerator: binary.LittleEndian.Uint32(buffer[20:24]),
  116. NumFrames: binary.LittleEndian.Uint32(buffer[24:28]),
  117. unused: binary.LittleEndian.Uint32(buffer[28:32]),
  118. }
  119. if header.signature != ivfFileHeaderSignature {
  120. return nil, errSignatureMismatch
  121. } else if header.version != uint16(0) {
  122. return nil, fmt.Errorf("%w: expected(0) got(%d)", errUnknownIVFVersion, header.version)
  123. }
  124. i.bytesReadSuccesfully += int64(bytesRead)
  125. return header, nil
  126. }