最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

go - How to extract Parrot's Anafi AI drone protobuf metadata from RTP packet, using Golang? - Stack Overflow

programmeradmin0浏览0评论

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/gopacket/gopacket"
    "github/gopacket/gopacket/pcap"
    "github/pion/rtp"
    "<my_module>/vmeta"
    "google.golang/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 (.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 (.html#id4)

Has anyone ever worked on such a thing ?

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/gopacket/gopacket"
    "github/gopacket/gopacket/pcap"
    "github/pion/rtp"
    "<my_module>/vmeta"
    "google.golang./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/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/docs/groundsdk-tools/video-metadata.html#id4)

Has anyone ever worked on such a thing ?

Share Improve this question edited Mar 12 at 13:54 blackgreen 45.2k28 gold badges161 silver badges156 bronze badges asked Mar 11 at 14:41 user29960667user29960667 311 silver badge3 bronze badges 0
Add a comment  | 

1 Answer 1

Reset to default 1

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/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
}

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论