github.com/pion/webrtc/v3@v3.2.24/pkg/media/ivfwriter/ivfwriter.go (about)

     1  // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
     2  // SPDX-License-Identifier: MIT
     3  
     4  // Package ivfwriter implements IVF media container writer
     5  package ivfwriter
     6  
     7  import (
     8  	"encoding/binary"
     9  	"errors"
    10  	"io"
    11  	"os"
    12  
    13  	"github.com/pion/rtp"
    14  	"github.com/pion/rtp/codecs"
    15  	"github.com/pion/rtp/pkg/frame"
    16  )
    17  
    18  var (
    19  	errFileNotOpened    = errors.New("file not opened")
    20  	errInvalidNilPacket = errors.New("invalid nil packet")
    21  	errCodecAlreadySet  = errors.New("codec is already set")
    22  	errNoSuchCodec      = errors.New("no codec for this MimeType")
    23  )
    24  
    25  const (
    26  	mimeTypeVP8 = "video/VP8"
    27  	mimeTypeAV1 = "video/AV1"
    28  
    29  	ivfFileHeaderSignature = "DKIF"
    30  )
    31  
    32  // IVFWriter is used to take RTP packets and write them to an IVF on disk
    33  type IVFWriter struct {
    34  	ioWriter     io.Writer
    35  	count        uint64
    36  	seenKeyFrame bool
    37  
    38  	isVP8, isAV1 bool
    39  
    40  	// VP8
    41  	currentFrame []byte
    42  
    43  	// AV1
    44  	av1Frame frame.AV1
    45  }
    46  
    47  // New builds a new IVF writer
    48  func New(fileName string, opts ...Option) (*IVFWriter, error) {
    49  	f, err := os.Create(fileName) //nolint:gosec
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  	writer, err := NewWith(f, opts...)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	writer.ioWriter = f
    58  	return writer, nil
    59  }
    60  
    61  // NewWith initialize a new IVF writer with an io.Writer output
    62  func NewWith(out io.Writer, opts ...Option) (*IVFWriter, error) {
    63  	if out == nil {
    64  		return nil, errFileNotOpened
    65  	}
    66  
    67  	writer := &IVFWriter{
    68  		ioWriter:     out,
    69  		seenKeyFrame: false,
    70  	}
    71  
    72  	for _, o := range opts {
    73  		if err := o(writer); err != nil {
    74  			return nil, err
    75  		}
    76  	}
    77  
    78  	if !writer.isAV1 && !writer.isVP8 {
    79  		writer.isVP8 = true
    80  	}
    81  
    82  	if err := writer.writeHeader(); err != nil {
    83  		return nil, err
    84  	}
    85  	return writer, nil
    86  }
    87  
    88  func (i *IVFWriter) writeHeader() error {
    89  	header := make([]byte, 32)
    90  	copy(header[0:], ivfFileHeaderSignature)      // DKIF
    91  	binary.LittleEndian.PutUint16(header[4:], 0)  // Version
    92  	binary.LittleEndian.PutUint16(header[6:], 32) // Header size
    93  
    94  	// FOURCC
    95  	if i.isVP8 {
    96  		copy(header[8:], "VP80")
    97  	} else if i.isAV1 {
    98  		copy(header[8:], "AV01")
    99  	}
   100  
   101  	binary.LittleEndian.PutUint16(header[12:], 640) // Width in pixels
   102  	binary.LittleEndian.PutUint16(header[14:], 480) // Height in pixels
   103  	binary.LittleEndian.PutUint32(header[16:], 30)  // Framerate denominator
   104  	binary.LittleEndian.PutUint32(header[20:], 1)   // Framerate numerator
   105  	binary.LittleEndian.PutUint32(header[24:], 900) // Frame count, will be updated on first Close() call
   106  	binary.LittleEndian.PutUint32(header[28:], 0)   // Unused
   107  
   108  	_, err := i.ioWriter.Write(header)
   109  	return err
   110  }
   111  
   112  func (i *IVFWriter) writeFrame(frame []byte) error {
   113  	frameHeader := make([]byte, 12)
   114  	binary.LittleEndian.PutUint32(frameHeader[0:], uint32(len(frame))) // Frame length
   115  	binary.LittleEndian.PutUint64(frameHeader[4:], i.count)            // PTS
   116  	i.count++
   117  
   118  	if _, err := i.ioWriter.Write(frameHeader); err != nil {
   119  		return err
   120  	}
   121  	_, err := i.ioWriter.Write(frame)
   122  	return err
   123  }
   124  
   125  // WriteRTP adds a new packet and writes the appropriate headers for it
   126  func (i *IVFWriter) WriteRTP(packet *rtp.Packet) error {
   127  	if i.ioWriter == nil {
   128  		return errFileNotOpened
   129  	} else if len(packet.Payload) == 0 {
   130  		return nil
   131  	}
   132  
   133  	if i.isVP8 {
   134  		vp8Packet := codecs.VP8Packet{}
   135  		if _, err := vp8Packet.Unmarshal(packet.Payload); err != nil {
   136  			return err
   137  		}
   138  
   139  		isKeyFrame := vp8Packet.Payload[0] & 0x01
   140  		switch {
   141  		case !i.seenKeyFrame && isKeyFrame == 1:
   142  			return nil
   143  		case i.currentFrame == nil && vp8Packet.S != 1:
   144  			return nil
   145  		}
   146  
   147  		i.seenKeyFrame = true
   148  		i.currentFrame = append(i.currentFrame, vp8Packet.Payload[0:]...)
   149  
   150  		if !packet.Marker {
   151  			return nil
   152  		} else if len(i.currentFrame) == 0 {
   153  			return nil
   154  		}
   155  
   156  		if err := i.writeFrame(i.currentFrame); err != nil {
   157  			return err
   158  		}
   159  		i.currentFrame = nil
   160  	} else if i.isAV1 {
   161  		av1Packet := &codecs.AV1Packet{}
   162  		if _, err := av1Packet.Unmarshal(packet.Payload); err != nil {
   163  			return err
   164  		}
   165  
   166  		obus, err := i.av1Frame.ReadFrames(av1Packet)
   167  		if err != nil {
   168  			return err
   169  		}
   170  
   171  		for j := range obus {
   172  			if err := i.writeFrame(obus[j]); err != nil {
   173  				return err
   174  			}
   175  		}
   176  	}
   177  
   178  	return nil
   179  }
   180  
   181  // Close stops the recording
   182  func (i *IVFWriter) Close() error {
   183  	if i.ioWriter == nil {
   184  		// Returns no error as it may be convenient to call
   185  		// Close() multiple times
   186  		return nil
   187  	}
   188  
   189  	defer func() {
   190  		i.ioWriter = nil
   191  	}()
   192  
   193  	if ws, ok := i.ioWriter.(io.WriteSeeker); ok {
   194  		// Update the framecount
   195  		if _, err := ws.Seek(24, 0); err != nil {
   196  			return err
   197  		}
   198  		buff := make([]byte, 4)
   199  		binary.LittleEndian.PutUint32(buff, uint32(i.count))
   200  		if _, err := ws.Write(buff); err != nil {
   201  			return err
   202  		}
   203  	}
   204  
   205  	if closer, ok := i.ioWriter.(io.Closer); ok {
   206  		return closer.Close()
   207  	}
   208  
   209  	return nil
   210  }
   211  
   212  // An Option configures a SampleBuilder.
   213  type Option func(i *IVFWriter) error
   214  
   215  // WithCodec configures if IVFWriter is writing AV1 or VP8 packets to disk
   216  func WithCodec(mimeType string) Option {
   217  	return func(i *IVFWriter) error {
   218  		if i.isVP8 || i.isAV1 {
   219  			return errCodecAlreadySet
   220  		}
   221  
   222  		switch mimeType {
   223  		case mimeTypeVP8:
   224  			i.isVP8 = true
   225  		case mimeTypeAV1:
   226  			i.isAV1 = true
   227  		default:
   228  			return errNoSuchCodec
   229  		}
   230  
   231  		return nil
   232  	}
   233  }