github.com/pion/webrtc/v4@v4.0.1/pkg/media/ivfreader/ivfreader.go (about)

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