8. VP8 PACKET DECODE¶
In previous chapter, we decoded the SRTP packet and decrypted it successfully, also determined that this packet contains a VP8 video data part.
-
If the packet's PayloadType is in PayloadTypeVP8 type, it forwards to the UDPClientSocket's "vp8Depacketizer" channel. This channel is listened by Run() function of VP8Decoder object in backend/src/transcoding/vp8.go, shown below
-
Our VP8Decoder aims to process incoming VP8 RTP packets, catch and concatenate keyframe packets then convert the result to an image object and save it as a JPEG image file.
VP8 Payload Descriptor
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|X|R|N|S|PartID | (REQUIRED)
+-+-+-+-+-+-+-+-+
X: |I|L|T|K| RSV | (OPTIONAL)
+-+-+-+-+-+-+-+-+
I: |M| PictureID | (OPTIONAL)
+-+-+-+-+-+-+-+-+
L: | TL0PICIDX | (OPTIONAL)
+-+-+-+-+-+-+-+-+
T/K: |TID|Y| KEYIDX | (OPTIONAL)
+-+-+-+-+-+-+-+-+
Our VP8 packet struct in backend/src/transcoding/vp8.go
from backend/src/transcoding/vp8.go
type VP8Packet struct {
// Required Header
X uint8 /* extended control bits present */
N uint8 /* when set to 1 this frame can be discarded */
S uint8 /* start of VP8 partition */
PID uint8 /* partition index */
// Extended control bits
I uint8 /* 1 if PictureID is present */
L uint8 /* 1 if TL0PICIDX is present */
T uint8 /* 1 if TID is present */
K uint8 /* 1 if KEYIDX is present */
// Optional extension
PictureID uint16 /* 8 or 16 bits, picture ID */
TL0PICIDX uint8 /* 8 bits temporal level zero index */
TID uint8 /* 2 bits temporal layer index */
Y uint8 /* 1 bit layer sync bit */
KEYIDX uint8 /* 5 bits temporal key frame index */
Payload []byte
}
- We used libvpx-go as codec, we didn't implement the VP8 codec.
from backend/src/transcoding/vp8.go
func (d *VP8Decoder) Run() {
newpath := filepath.Join(".", saveDir)
err := os.MkdirAll(newpath, os.ModePerm)
if err != nil {
panic(err)
}
packetCounter := 0
for rtpPacket := range d.src {
packetCounter++
vp8Packet := &VP8Packet{}
vp8Packet.Unmarshal(rtpPacket.Payload)
isKeyFrame := vp8Packet.Payload[0] & 0x01
switch {
case !seenKeyFrame && isKeyFrame == 1:
continue
case currentFrame == nil && vp8Packet.S != 1:
continue
}
seenKeyFrame = true
currentFrame = append(currentFrame, vp8Packet.Payload[0:]...)
if !rtpPacket.Header.Marker {
continue
} else if len(currentFrame) == 0 {
continue
}
err := vpx.Error(vpx.CodecDecode(d.context, string(currentFrame), uint32(len(currentFrame)), nil, 0))
if err != nil {
logging.Errorf(logging.ProtoVP8, "Error while decoding packet: %s", err)
currentFrame = nil
seenKeyFrame = false
continue
}
var iter vpx.CodecIter
img := vpx.CodecGetFrame(d.context, &iter)
if img != nil {
img.Deref()
outputImageFilePath, err := d.saveImageFile(img)
if err != nil {
logging.Errorf(logging.ProtoVP8, "Error while image saving: %s", err)
} else {
logging.Infof(logging.ProtoVP8, "Image file saved: %s\n", outputImageFilePath)
}
}
currentFrame = nil
seenKeyFrame = false
}
}
- If the function call succeeds,
- We call img.Deref() to convert decoded C structures as Go wrapper variables
- If everything has gone OK, save the image as a JPEG file by jpeg.Encode
- You can see your caught keyframes at /backend/output/ folder as shoot1.jpg, shoot2.jpg, etc... if multiple keyframes were caught.
- As you can see below, we received multiple RTP packets containing VP8 video data, in different packet lengths, then we caught a keyframe from these packets, concatenate them, then created and saved image.
- At the end of our journey, we saw the "[INFO] Image file saved: ../output/shoot1.jpg" log line! All of our these efforts are to achieve this....
Sources: