webrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.cc
Emil Lundmark 575498ffc2 Tweak VP8 payload to comply with RFC 7741
This updates the VP8 payload diagrams to be compliant with RFC 7741. It
also fixes some minor inconsistencies with PID, previously referred to
as PartID.

Bug: None
Change-Id: I33eb57d96f3d95b01ef5f0afa21a9dc54b41db2d
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/230243
Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
Commit-Queue: Emil Lundmark <lndmrk@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#34859}
2021-08-30 09:01:47 +00:00

201 lines
5.8 KiB
C++

/*
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.h"
#include <stddef.h>
#include <stdint.h>
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "modules/rtp_rtcp/source/rtp_video_header.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
// VP8 payload descriptor
// https://datatracker.ietf.org/doc/html/rfc7741#section-4.2
//
// 0 1 2 3 4 5 6 7
// +-+-+-+-+-+-+-+-+
// |X|R|N|S|R| PID | (REQUIRED)
// +-+-+-+-+-+-+-+-+
// X: |I|L|T|K| RSV | (OPTIONAL)
// +-+-+-+-+-+-+-+-+
// I: |M| PictureID | (OPTIONAL)
// +-+-+-+-+-+-+-+-+
// | PictureID |
// +-+-+-+-+-+-+-+-+
// L: | TL0PICIDX | (OPTIONAL)
// +-+-+-+-+-+-+-+-+
// T/K: |TID|Y| KEYIDX | (OPTIONAL)
// +-+-+-+-+-+-+-+-+
//
// VP8 payload header. Considered part of the actual payload, sent to decoder.
// https://datatracker.ietf.org/doc/html/rfc7741#section-4.3
//
// 0 1 2 3 4 5 6 7
// +-+-+-+-+-+-+-+-+
// |Size0|H| VER |P|
// +-+-+-+-+-+-+-+-+
// : ... :
// +-+-+-+-+-+-+-+-+
namespace webrtc {
namespace {
constexpr int kFailedToParse = 0;
int ParseVP8Descriptor(RTPVideoHeaderVP8* vp8,
const uint8_t* data,
size_t data_length) {
RTC_DCHECK_GT(data_length, 0);
int parsed_bytes = 0;
// Parse mandatory first byte of payload descriptor.
bool extension = (*data & 0x80) ? true : false; // X bit
vp8->nonReference = (*data & 0x20) ? true : false; // N bit
vp8->beginningOfPartition = (*data & 0x10) ? true : false; // S bit
vp8->partitionId = (*data & 0x07); // PID field
data++;
parsed_bytes++;
data_length--;
if (!extension)
return parsed_bytes;
if (data_length == 0)
return kFailedToParse;
// Optional X field is present.
bool has_picture_id = (*data & 0x80) ? true : false; // I bit
bool has_tl0_pic_idx = (*data & 0x40) ? true : false; // L bit
bool has_tid = (*data & 0x20) ? true : false; // T bit
bool has_key_idx = (*data & 0x10) ? true : false; // K bit
// Advance data and decrease remaining payload size.
data++;
parsed_bytes++;
data_length--;
if (has_picture_id) {
if (data_length == 0)
return kFailedToParse;
vp8->pictureId = (*data & 0x7F);
if (*data & 0x80) {
data++;
parsed_bytes++;
if (--data_length == 0)
return kFailedToParse;
// PictureId is 15 bits
vp8->pictureId = (vp8->pictureId << 8) + *data;
}
data++;
parsed_bytes++;
data_length--;
}
if (has_tl0_pic_idx) {
if (data_length == 0)
return kFailedToParse;
vp8->tl0PicIdx = *data;
data++;
parsed_bytes++;
data_length--;
}
if (has_tid || has_key_idx) {
if (data_length == 0)
return kFailedToParse;
if (has_tid) {
vp8->temporalIdx = ((*data >> 6) & 0x03);
vp8->layerSync = (*data & 0x20) ? true : false; // Y bit
}
if (has_key_idx) {
vp8->keyIdx = *data & 0x1F;
}
data++;
parsed_bytes++;
data_length--;
}
return parsed_bytes;
}
} // namespace
absl::optional<VideoRtpDepacketizer::ParsedRtpPayload>
VideoRtpDepacketizerVp8::Parse(rtc::CopyOnWriteBuffer rtp_payload) {
rtc::ArrayView<const uint8_t> payload(rtp_payload.cdata(),
rtp_payload.size());
absl::optional<ParsedRtpPayload> result(absl::in_place);
int offset = ParseRtpPayload(payload, &result->video_header);
if (offset == kFailedToParse)
return absl::nullopt;
RTC_DCHECK_LT(offset, rtp_payload.size());
result->video_payload =
rtp_payload.Slice(offset, rtp_payload.size() - offset);
return result;
}
int VideoRtpDepacketizerVp8::ParseRtpPayload(
rtc::ArrayView<const uint8_t> rtp_payload,
RTPVideoHeader* video_header) {
RTC_DCHECK(video_header);
if (rtp_payload.empty()) {
RTC_LOG(LS_ERROR) << "Empty rtp payload.";
return kFailedToParse;
}
video_header->simulcastIdx = 0;
video_header->codec = kVideoCodecVP8;
auto& vp8_header =
video_header->video_type_header.emplace<RTPVideoHeaderVP8>();
vp8_header.InitRTPVideoHeaderVP8();
const int descriptor_size =
ParseVP8Descriptor(&vp8_header, rtp_payload.data(), rtp_payload.size());
if (descriptor_size == kFailedToParse)
return kFailedToParse;
RTC_DCHECK_LT(vp8_header.partitionId, 8);
video_header->is_first_packet_in_frame =
vp8_header.beginningOfPartition && vp8_header.partitionId == 0;
int vp8_payload_size = rtp_payload.size() - descriptor_size;
if (vp8_payload_size == 0) {
RTC_LOG(LS_WARNING) << "Empty vp8 payload.";
return kFailedToParse;
}
const uint8_t* vp8_payload = rtp_payload.data() + descriptor_size;
// Read P bit from payload header (only at beginning of first partition).
if (video_header->is_first_packet_in_frame && (*vp8_payload & 0x01) == 0) {
video_header->frame_type = VideoFrameType::kVideoFrameKey;
if (vp8_payload_size < 10) {
// For an I-frame we should always have the uncompressed VP8 header
// in the beginning of the partition.
return kFailedToParse;
}
video_header->width = ((vp8_payload[7] << 8) + vp8_payload[6]) & 0x3FFF;
video_header->height = ((vp8_payload[9] << 8) + vp8_payload[8]) & 0x3FFF;
} else {
video_header->frame_type = VideoFrameType::kVideoFrameDelta;
video_header->width = 0;
video_header->height = 0;
}
return descriptor_size;
}
} // namespace webrtc