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

     1  // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
     2  // SPDX-License-Identifier: MIT
     3  
     4  // Package rtpdump implements the RTPDump file format documented at
     5  // https://www.cs.columbia.edu/irt/software/rtptools/
     6  package rtpdump
     7  
     8  import (
     9  	"encoding/binary"
    10  	"errors"
    11  	"net"
    12  	"time"
    13  )
    14  
    15  const (
    16  	pktHeaderLen = 8
    17  	headerLen    = 16
    18  	preambleLen  = 36
    19  )
    20  
    21  var errMalformed = errors.New("malformed rtpdump")
    22  
    23  // Header is the binary header at the top of the RTPDump file. It contains
    24  // information about the source and start time of the packet stream included
    25  // in the file.
    26  type Header struct {
    27  	// start of recording (GMT)
    28  	Start time.Time
    29  	// network source (multicast address)
    30  	Source net.IP
    31  	// UDP port
    32  	Port uint16
    33  }
    34  
    35  // Marshal encodes the Header as binary.
    36  func (h Header) Marshal() ([]byte, error) {
    37  	d := make([]byte, headerLen)
    38  
    39  	startNano := h.Start.UnixNano()
    40  	startSec := uint32(startNano / int64(time.Second))
    41  	startUsec := uint32(
    42  		(startNano % int64(time.Second)) / int64(time.Microsecond),
    43  	)
    44  	binary.BigEndian.PutUint32(d[0:], startSec)
    45  	binary.BigEndian.PutUint32(d[4:], startUsec)
    46  
    47  	source := h.Source.To4()
    48  	copy(d[8:], source)
    49  
    50  	binary.BigEndian.PutUint16(d[12:], h.Port)
    51  
    52  	return d, nil
    53  }
    54  
    55  // Unmarshal decodes the Header from binary.
    56  func (h *Header) Unmarshal(d []byte) error {
    57  	if len(d) < headerLen {
    58  		return errMalformed
    59  	}
    60  
    61  	// time as a `struct timeval`
    62  	startSec := binary.BigEndian.Uint32(d[0:])
    63  	startUsec := binary.BigEndian.Uint32(d[4:])
    64  	h.Start = time.Unix(int64(startSec), int64(startUsec)*1e3).UTC()
    65  
    66  	// ipv4 address
    67  	h.Source = net.IPv4(d[8], d[9], d[10], d[11])
    68  
    69  	h.Port = binary.BigEndian.Uint16(d[12:])
    70  
    71  	// 2 bytes of padding (ignored)
    72  
    73  	return nil
    74  }
    75  
    76  // Packet contains an RTP or RTCP packet along a time offset when it was logged
    77  // (relative to the Start of the recording in Header). The Payload may contain
    78  // truncated packets to support logging just the headers of RTP/RTCP packets.
    79  type Packet struct {
    80  	// Offset is the time since the start of recording in milliseconds
    81  	Offset time.Duration
    82  	// IsRTCP is true if the payload is RTCP, false if the payload is RTP
    83  	IsRTCP bool
    84  	// Payload is the binary RTP or RTCP payload. The contents may not parse
    85  	// as a valid packet if the contents have been truncated.
    86  	Payload []byte
    87  }
    88  
    89  // Marshal encodes the Packet as binary.
    90  func (p Packet) Marshal() ([]byte, error) {
    91  	packetLength := len(p.Payload)
    92  	if p.IsRTCP {
    93  		packetLength = 0
    94  	}
    95  
    96  	hdr := packetHeader{
    97  		Length:       uint16(len(p.Payload)) + 8,
    98  		PacketLength: uint16(packetLength),
    99  		Offset:       p.offsetMs(),
   100  	}
   101  	hdrData, err := hdr.Marshal()
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	return append(hdrData, p.Payload...), nil
   107  }
   108  
   109  // Unmarshal decodes the Packet from binary.
   110  func (p *Packet) Unmarshal(d []byte) error {
   111  	var hdr packetHeader
   112  	if err := hdr.Unmarshal(d); err != nil {
   113  		return err
   114  	}
   115  
   116  	p.Offset = hdr.offset()
   117  	p.IsRTCP = hdr.Length != 0 && hdr.PacketLength == 0
   118  
   119  	if hdr.Length < 8 {
   120  		return errMalformed
   121  	}
   122  	if len(d) < int(hdr.Length) {
   123  		return errMalformed
   124  	}
   125  	p.Payload = d[8:hdr.Length]
   126  
   127  	return nil
   128  }
   129  
   130  func (p *Packet) offsetMs() uint32 {
   131  	return uint32(p.Offset / time.Millisecond)
   132  }
   133  
   134  type packetHeader struct {
   135  	// length of packet, including this header (may be smaller than
   136  	// plen if not whole packet recorded)
   137  	Length uint16
   138  	// Actual header+payload length for RTP, 0 for RTCP
   139  	PacketLength uint16
   140  	// milliseconds since the start of recording
   141  	Offset uint32
   142  }
   143  
   144  func (p packetHeader) Marshal() ([]byte, error) {
   145  	d := make([]byte, pktHeaderLen)
   146  
   147  	binary.BigEndian.PutUint16(d[0:], p.Length)
   148  	binary.BigEndian.PutUint16(d[2:], p.PacketLength)
   149  	binary.BigEndian.PutUint32(d[4:], p.Offset)
   150  
   151  	return d, nil
   152  }
   153  
   154  func (p *packetHeader) Unmarshal(d []byte) error {
   155  	if len(d) < pktHeaderLen {
   156  		return errMalformed
   157  	}
   158  
   159  	p.Length = binary.BigEndian.Uint16(d[0:])
   160  	p.PacketLength = binary.BigEndian.Uint16(d[2:])
   161  	p.Offset = binary.BigEndian.Uint32(d[4:])
   162  
   163  	return nil
   164  }
   165  
   166  func (p packetHeader) offset() time.Duration {
   167  	return time.Duration(p.Offset) * time.Millisecond
   168  }