| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
- // SPDX-License-Identifier: MIT
- // Package rtpdump implements the RTPDump file format documented at
- // https://www.cs.columbia.edu/irt/software/rtptools/
- package rtpdump
- import (
- "encoding/binary"
- "errors"
- "net"
- "time"
- )
- const (
- pktHeaderLen = 8
- headerLen = 16
- preambleLen = 36
- )
- var errMalformed = errors.New("malformed rtpdump")
- // Header is the binary header at the top of the RTPDump file. It contains
- // information about the source and start time of the packet stream included
- // in the file.
- type Header struct {
- // start of recording (GMT)
- Start time.Time
- // network source (multicast address)
- Source net.IP
- // UDP port
- Port uint16
- }
- // Marshal encodes the Header as binary.
- func (h Header) Marshal() ([]byte, error) {
- d := make([]byte, headerLen)
- startNano := h.Start.UnixNano()
- startSec := uint32(startNano / int64(time.Second))
- startUsec := uint32(
- (startNano % int64(time.Second)) / int64(time.Microsecond),
- )
- binary.BigEndian.PutUint32(d[0:], startSec)
- binary.BigEndian.PutUint32(d[4:], startUsec)
- source := h.Source.To4()
- copy(d[8:], source)
- binary.BigEndian.PutUint16(d[12:], h.Port)
- return d, nil
- }
- // Unmarshal decodes the Header from binary.
- func (h *Header) Unmarshal(d []byte) error {
- if len(d) < headerLen {
- return errMalformed
- }
- // time as a `struct timeval`
- startSec := binary.BigEndian.Uint32(d[0:])
- startUsec := binary.BigEndian.Uint32(d[4:])
- h.Start = time.Unix(int64(startSec), int64(startUsec)*1e3).UTC()
- // ipv4 address
- h.Source = net.IPv4(d[8], d[9], d[10], d[11])
- h.Port = binary.BigEndian.Uint16(d[12:])
- // 2 bytes of padding (ignored)
- return nil
- }
- // Packet contains an RTP or RTCP packet along a time offset when it was logged
- // (relative to the Start of the recording in Header). The Payload may contain
- // truncated packets to support logging just the headers of RTP/RTCP packets.
- type Packet struct {
- // Offset is the time since the start of recording in millseconds
- Offset time.Duration
- // IsRTCP is true if the payload is RTCP, false if the payload is RTP
- IsRTCP bool
- // Payload is the binary RTP or or RTCP payload. The contents may not parse
- // as a valid packet if the contents have been truncated.
- Payload []byte
- }
- // Marshal encodes the Packet as binary.
- func (p Packet) Marshal() ([]byte, error) {
- packetLength := len(p.Payload)
- if p.IsRTCP {
- packetLength = 0
- }
- hdr := packetHeader{
- Length: uint16(len(p.Payload)) + 8,
- PacketLength: uint16(packetLength),
- Offset: p.offsetMs(),
- }
- hdrData, err := hdr.Marshal()
- if err != nil {
- return nil, err
- }
- return append(hdrData, p.Payload...), nil
- }
- // Unmarshal decodes the Packet from binary.
- func (p *Packet) Unmarshal(d []byte) error {
- var hdr packetHeader
- if err := hdr.Unmarshal(d); err != nil {
- return err
- }
- p.Offset = hdr.offset()
- p.IsRTCP = hdr.Length != 0 && hdr.PacketLength == 0
- if hdr.Length < 8 {
- return errMalformed
- }
- if len(d) < int(hdr.Length) {
- return errMalformed
- }
- p.Payload = d[8:hdr.Length]
- return nil
- }
- func (p *Packet) offsetMs() uint32 {
- return uint32(p.Offset / time.Millisecond)
- }
- type packetHeader struct {
- // length of packet, including this header (may be smaller than
- // plen if not whole packet recorded)
- Length uint16
- // Actual header+payload length for RTP, 0 for RTCP
- PacketLength uint16
- // milliseconds since the start of recording
- Offset uint32
- }
- func (p packetHeader) Marshal() ([]byte, error) {
- d := make([]byte, pktHeaderLen)
- binary.BigEndian.PutUint16(d[0:], p.Length)
- binary.BigEndian.PutUint16(d[2:], p.PacketLength)
- binary.BigEndian.PutUint32(d[4:], p.Offset)
- return d, nil
- }
- func (p *packetHeader) Unmarshal(d []byte) error {
- if len(d) < pktHeaderLen {
- return errMalformed
- }
- p.Length = binary.BigEndian.Uint16(d[0:])
- p.PacketLength = binary.BigEndian.Uint16(d[2:])
- p.Offset = binary.BigEndian.Uint32(d[4:])
- return nil
- }
- func (p packetHeader) offset() time.Duration {
- return time.Duration(p.Offset) * time.Millisecond
- }
|