I really struggle extracting protobuf payload (Parrot TimedMetadata) from a recording of RTP packets. I'm using golang. I followed the parrot's dev guide without success.
This is my main.go:
package main
import (
"encoding/binary"
"fmt"
"log"
"os"
"github.com/gopacket/gopacket"
"github.com/gopacket/gopacket/pcap"
"github.com/pion/rtp"
"<my_module>/vmeta"
"google.golang.org/protobuf/proto"
)
func main() {
h, err := pcap.OpenOffline("data.pcapng")
if err != nil {
log.Fatalf("failed to open file: %v", err)
}
outputFile, err := os.Create("out.txt")
if err != nil {
log.Fatalf("failed to create save file: %v", err)
}
defer outputFile.Close()
ps := gopacket.NewPacketSource(h, h.LinkType())
i := 0
for packet := range ps.Packets() {
layer := packet.TransportLayer()
if layer == nil {
continue
}
payload := packet.TransportLayer().LayerPayload()
rtpPacket := rtp.Packet{}
err = rtpPacket.Unmarshal(payload)
if err != nil {
continue
}
i++
if !rtpPacket.Extension {
continue // no extension header
}
if err := parse(payload); err == nil {
log.Println("Success !")
os.Exit(0)
}
}
}
func parse(data []byte) error {
n := 12 // skip fixed size header
log.Printf("Initial buffer: %x", data)
version := data[n : n+2]
n += 2
log.Printf("Defined by profile: 0x%x\n", version) // 0x5062 here, so no pb
length := binary.BigEndian.Uint16(data[n : n+2])
n += 2
log.Printf("Length: %d\n", length)
packIndicator := binary.BigEndian.Uint16(data[n : n+2])
lastPack := (packIndicator >> 9) & 0x7f
curPack := (packIndicator >> 2) & 0x7f
padding := (packIndicator >> 0) & 0x03
n += 2
log.Printf("Packet: %d/%d\n", curPack, lastPack)
log.Printf("Padding: %d\n", padding)
offset := binary.BigEndian.Uint16(data[n : n+2])
n += 2
log.Printf("Offset: %d\n", offset)
rawData := data[n : uint16(n)+(4*length)]
log.Printf("RawData: %x", rawData)
model := vmeta.TimedMetadata{}
if err := proto.Unmarshal(rawData, &model); err != nil {
return fmt.Errorf("failed to deserialize: %v", err)
}
return nil
}
The problem is that it never succeed... We are expected to work with RTP Extensions Header but if I extract it using Wireshark and decode it, it fails. Even if I follow the source code of Parrot-Developers/libvideo-streaming (https://github.com/Parrot-Developers/libvideo-streaming/blob/master/src/vstrm_rtp_h264.h#L140) and skip the first two bytes of Extension Header it does not work. Does anyone know how the 'padding' field should be used ? It's not mentionned in the doc (https://developer.parrot.com/docs/groundsdk-tools/video-metadata.html#id4)
Has anyone ever worked on such a thing ?
I finally managed to find out a solution.
My issue came from a lack of documentation from Parrot. If we take a look at the C library they provide (https://github.com/Parrot-Developers/libvideo-streaming/blob/bfd02a6c385dcd4645dfe8a7973f2e426e75199a/src/vstrm_rtp_h264.h#L144) we can see a presence of an extension header's header (yep, that is a little bit intricate).
To wrap things up, you need to skip the 12 bytes RTP header, check if the following two bytes are equal to 0x5062 (protobuf format), get the length of the header extension with the following 2 bytes (multiple of 4 bytes), get information of packet splitting using the following code :
packIndicator := binary.BigEndian.Uint16(data[16 : 18])
lastPack := (packIndicator >> 9) & 0x7f
curPack := (packIndicator >> 2) & 0x7f
padding := (packIndicator >> 0) & 0x03
If curPack == lastPack we have a whole proto message. Otherwise append an intermediate buffer.
We can then skip the 2 following bytes (represent an offset for intermediate buffer, we shouldn't need this) and finally we arrive at our protobuf raw data.
The bounds for the protobuf message is the following:
data[20 : 20+(4*(length - 1)) - padding]
So really the protobuf section of the extension header can be found using the following parse function:
func parse(data []byte) error {
n := 12 // skip fixed size header
log.Printf("Initial buffer: %x", data)
version := data[n : n+2]
n += 2
log.Printf("Defined by profile: 0x%x\n", version) // 0x5062 here, so no pb
length := binary.BigEndian.Uint16(data[n : n+2])
n += 2
log.Printf("Length: %d\n", length)
packIndicator := binary.BigEndian.Uint16(data[n : n+2])
lastPack := (packIndicator >> 9) & 0x7f
curPack := (packIndicator >> 2) & 0x7f
padding := (packIndicator >> 0) & 0x03
n += 2
log.Printf("Packet: %d/%d\n", curPack, lastPack)
log.Printf("Padding: %d\n", padding)
offset := binary.BigEndian.Uint16(data[n : n+2])
n += 2
log.Printf("Offset: %d\n", offset)
rawData := data[n : int(n)+(4*int(length - 1)) - int(padding)]
log.Printf("RawData: %x", rawData)
model := vmeta.TimedMetadata{}
if err := proto.Unmarshal(rawData, &model); err != nil {
return fmt.Errorf("failed to deserialize: %v", err)
}
return nil
}