/* * Copyright (c) 2011 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/rtp_format_vp8.h" #include // assert #include // memcpy #include #include #include #include "modules/rtp_rtcp/source/rtp_packet_to_send.h" #include "rtc_base/logging.h" namespace webrtc { namespace { int ParseVP8PictureID(RTPVideoHeaderVP8* vp8, const uint8_t** data, size_t* data_length, size_t* parsed_bytes) { assert(vp8 != NULL); if (*data_length == 0) return -1; vp8->pictureId = (**data & 0x7F); if (**data & 0x80) { (*data)++; (*parsed_bytes)++; if (--(*data_length) == 0) return -1; // PictureId is 15 bits vp8->pictureId = (vp8->pictureId << 8) + **data; } (*data)++; (*parsed_bytes)++; (*data_length)--; return 0; } int ParseVP8Tl0PicIdx(RTPVideoHeaderVP8* vp8, const uint8_t** data, size_t* data_length, size_t* parsed_bytes) { assert(vp8 != NULL); if (*data_length == 0) return -1; vp8->tl0PicIdx = **data; (*data)++; (*parsed_bytes)++; (*data_length)--; return 0; } int ParseVP8TIDAndKeyIdx(RTPVideoHeaderVP8* vp8, const uint8_t** data, size_t* data_length, size_t* parsed_bytes, bool has_tid, bool has_key_idx) { assert(vp8 != NULL); if (*data_length == 0) return -1; 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 0; } int ParseVP8Extension(RTPVideoHeaderVP8* vp8, const uint8_t* data, size_t data_length) { assert(vp8 != NULL); assert(data_length > 0); size_t parsed_bytes = 0; // 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 (ParseVP8PictureID(vp8, &data, &data_length, &parsed_bytes) != 0) { return -1; } } if (has_tl0_pic_idx) { if (ParseVP8Tl0PicIdx(vp8, &data, &data_length, &parsed_bytes) != 0) { return -1; } } if (has_tid || has_key_idx) { if (ParseVP8TIDAndKeyIdx( vp8, &data, &data_length, &parsed_bytes, has_tid, has_key_idx) != 0) { return -1; } } return static_cast(parsed_bytes); } int ParseVP8FrameSize(RtpDepacketizer::ParsedPayload* parsed_payload, const uint8_t* data, size_t data_length) { assert(parsed_payload != NULL); if (parsed_payload->frame_type != kVideoFrameKey) { // Included in payload header for I-frames. return 0; } if (data_length < 10) { // For an I-frame we should always have the uncompressed VP8 header // in the beginning of the partition. return -1; } parsed_payload->type.Video.width = ((data[7] << 8) + data[6]) & 0x3FFF; parsed_payload->type.Video.height = ((data[9] << 8) + data[8]) & 0x3FFF; return 0; } } // namespace RtpPacketizerVp8::RtpPacketizerVp8(const RTPVideoHeaderVP8& hdr_info, size_t max_payload_len, size_t last_packet_reduction_len, VP8PacketizerMode mode) : payload_data_(NULL), payload_size_(0), vp8_fixed_payload_descriptor_bytes_(1), mode_(mode), hdr_info_(hdr_info), num_partitions_(0), max_payload_len_(max_payload_len), last_packet_reduction_len_(last_packet_reduction_len) {} RtpPacketizerVp8::RtpPacketizerVp8(const RTPVideoHeaderVP8& hdr_info, size_t max_payload_len, size_t last_packet_reduction_len) : payload_data_(NULL), payload_size_(0), part_info_(), vp8_fixed_payload_descriptor_bytes_(1), mode_(kEqualSize), hdr_info_(hdr_info), num_partitions_(0), max_payload_len_(max_payload_len), last_packet_reduction_len_(last_packet_reduction_len) {} RtpPacketizerVp8::~RtpPacketizerVp8() { } size_t RtpPacketizerVp8::SetPayloadData( const uint8_t* payload_data, size_t payload_size, const RTPFragmentationHeader* fragmentation) { payload_data_ = payload_data; payload_size_ = payload_size; if (fragmentation) { part_info_.CopyFrom(*fragmentation); num_partitions_ = fragmentation->fragmentationVectorSize; } else { part_info_.VerifyAndAllocateFragmentationHeader(1); part_info_.fragmentationLength[0] = payload_size; part_info_.fragmentationOffset[0] = 0; num_partitions_ = part_info_.fragmentationVectorSize; } if (GeneratePackets() < 0) { return 0; } return packets_.size(); } bool RtpPacketizerVp8::NextPacket(RtpPacketToSend* packet) { RTC_DCHECK(packet); if (packets_.empty()) { return false; } InfoStruct packet_info = packets_.front(); packets_.pop(); uint8_t* buffer = packet->AllocatePayload( packets_.empty() ? max_payload_len_ - last_packet_reduction_len_ : max_payload_len_); int bytes = WriteHeaderAndPayload(packet_info, buffer, max_payload_len_); if (bytes < 0) { return false; } packet->SetPayloadSize(bytes); packet->SetMarker(packets_.empty()); return true; } std::string RtpPacketizerVp8::ToString() { return "RtpPacketizerVp8"; } int RtpPacketizerVp8::GeneratePackets() { if (max_payload_len_ < vp8_fixed_payload_descriptor_bytes_ + PayloadDescriptorExtraLength() + 1 + last_packet_reduction_len_) { // The provided payload length is not long enough for the payload // descriptor and one payload byte in the last packet. // Return an error. return -1; } size_t per_packet_capacity = max_payload_len_ - (vp8_fixed_payload_descriptor_bytes_ + PayloadDescriptorExtraLength()); if (mode_ == kEqualSize) { GeneratePacketsSplitPayloadBalanced(0, payload_size_, per_packet_capacity, true, 0); return 0; } size_t part_idx = 0; while (part_idx < num_partitions_) { size_t current_packet_capacity = per_packet_capacity; bool last_partition = (part_idx + 1) == num_partitions_; if (last_partition) current_packet_capacity -= last_packet_reduction_len_; // Check if the next partition fits in to single packet with some space // left to aggregate some partitions together. if (mode_ == kAggregate && part_info_.fragmentationLength[part_idx] < current_packet_capacity) { part_idx = GeneratePacketsAggregatePartitions(part_idx, per_packet_capacity); } else { GeneratePacketsSplitPayloadBalanced( part_info_.fragmentationOffset[part_idx], part_info_.fragmentationLength[part_idx], per_packet_capacity, last_partition, part_idx); ++part_idx; } } return 0; } void RtpPacketizerVp8::GeneratePacketsSplitPayloadBalanced(size_t payload_start, size_t payload_len, size_t capacity, bool last_partition, size_t part_idx) { // Last packet of the last partition is smaller. Pretend that it's the same // size, but we must write more payload to it. size_t total_bytes = payload_len; if (last_partition) total_bytes += last_packet_reduction_len_; // Integer divisions with rounding up. size_t num_packets_left = (total_bytes + capacity - 1) / capacity; size_t bytes_per_packet = total_bytes / num_packets_left; size_t num_larger_packets = total_bytes % num_packets_left; size_t remaining_data = payload_len; while (remaining_data > 0) { // Last num_larger_packets are 1 byte wider than the rest. Increase // per-packet payload size when needed. if (num_packets_left == num_larger_packets) ++bytes_per_packet; size_t current_packet_bytes = bytes_per_packet; if (current_packet_bytes > remaining_data) { current_packet_bytes = remaining_data; } // This is not the last packet in the whole payload, but there's no data // left for the last packet. Leave at least one byte for the last packet. if (num_packets_left == 2 && current_packet_bytes == remaining_data && last_partition) { --current_packet_bytes; } QueuePacket(payload_start + payload_len - remaining_data, current_packet_bytes, part_idx, remaining_data == payload_len); remaining_data -= current_packet_bytes; --num_packets_left; } } size_t RtpPacketizerVp8::GeneratePacketsAggregatePartitions(size_t part_idx, size_t capacity) { // Bloat the last partition by the reduction of the last packet. As it always // will be in the last packet we can pretend that the last packet is the same // size as the rest of the packets. Done temporary to simplify calculations. part_info_.fragmentationLength[num_partitions_ - 1] += last_packet_reduction_len_; // Current partition should fit into the packet. RTC_CHECK_LE(part_info_.fragmentationLength[part_idx], capacity); // Find all partitions, shorter than capacity. size_t end_part = part_idx + 1; while (end_part < num_partitions_ && part_info_.fragmentationLength[end_part] <= capacity) { ++end_part; } size_t total_partitions = end_part - part_idx; // Aggregate partitions |part_idx|..|end_part-1| to blocks of size at most // |capacity| minimizing the number of packets and then size of a largest // block using dynamic programming. |scores[i]| stores best score in the form // for last i partitions. Maximum index is // |total_partitions|, minimum index is 0, hence the length is // |total_partitions|+1. struct PartitionScore { size_t num_packets = std::numeric_limits::max(); size_t largest_packet_len = std::numeric_limits::max(); // Compare num_packets first then largest_packet_len bool operator <(const PartitionScore& other) const { if (num_packets < other.num_packets) return true; if (num_packets > other.num_packets) return false; return largest_packet_len < other.largest_packet_len; } }; std::vector scores(total_partitions + 1); // 0 partitions can be split into 0 packets with largest of size 0. scores[0].num_packets = 0; scores[0].largest_packet_len = 0; // best_block_size[i] stores optimal number of partitions to be aggregated // in the first packet if only last i partitions are considered. std::vector best_block_size(total_partitions + 1, 0); // Calculate scores and best_block_size iteratively. for (size_t partitions_left = 0; partitions_left < total_partitions; ++partitions_left) { // Here scores[paritions_left] is already calculated correctly. Update // possible score for every possible new_paritions_left > partitions_left by // aggregating all partitions in between into a single packet. size_t current_payload_len = 0; PartitionScore current_score = scores[partitions_left]; // Some next partitions are aggregated into one packet. current_score.num_packets += 1; // Calculate new score for last |new_partitions_left| partitions given // best score for |partitions_left| partitions. for (size_t new_partitions_left = partitions_left + 1; new_partitions_left <= total_partitions; ++new_partitions_left) { current_payload_len += part_info_.fragmentationLength[end_part - new_partitions_left]; if (current_payload_len > capacity) break; // Update maximum packet size. if (current_payload_len > current_score.largest_packet_len) current_score.largest_packet_len = current_payload_len; // Score with less num_packets is better. If equal, minimum largest packet // size is better. if (current_score < scores[new_partitions_left]) { scores[new_partitions_left] = current_score; best_block_size[new_partitions_left] = new_partitions_left - partitions_left; } } } // Undo temporary change. part_info_.fragmentationLength[num_partitions_ - 1] -= last_packet_reduction_len_; // Restore answer given sizes of aggregated blocks in |best_block_size| for // each possible left number of partitions. size_t partitions_left = total_partitions; while (partitions_left > 0) { size_t cur_parts = best_block_size[partitions_left]; size_t first_partition = end_part - partitions_left; size_t start_offset = part_info_.fragmentationOffset[first_partition]; size_t post_last_partition = first_partition + cur_parts; size_t finish_offset = (post_last_partition < num_partitions_) ? part_info_.fragmentationOffset[post_last_partition] : payload_size_; size_t current_payload_len = finish_offset - start_offset; QueuePacket(start_offset, current_payload_len, first_partition, true); // Go to next packet. partitions_left -= cur_parts; } return end_part; } void RtpPacketizerVp8::QueuePacket(size_t start_pos, size_t packet_size, size_t first_partition_in_packet, bool start_on_new_fragment) { // Write info to packet info struct and store in packet info queue. InfoStruct packet_info; packet_info.payload_start_pos = start_pos; packet_info.size = packet_size; packet_info.first_partition_ix = first_partition_in_packet; packet_info.first_fragment = start_on_new_fragment; packets_.push(packet_info); } int RtpPacketizerVp8::WriteHeaderAndPayload(const InfoStruct& packet_info, uint8_t* buffer, size_t buffer_length) const { // Write the VP8 payload descriptor. // 0 // 0 1 2 3 4 5 6 7 8 // +-+-+-+-+-+-+-+-+-+ // |X| |N|S| PART_ID | // +-+-+-+-+-+-+-+-+-+ // X: |I|L|T|K| | (mandatory if any of the below are used) // +-+-+-+-+-+-+-+-+-+ // I: |PictureID (8/16b)| (optional) // +-+-+-+-+-+-+-+-+-+ // L: | TL0PIC_IDX | (optional) // +-+-+-+-+-+-+-+-+-+ // T/K: |TID:Y| KEYIDX | (optional) // +-+-+-+-+-+-+-+-+-+ assert(packet_info.size > 0); buffer[0] = 0; if (XFieldPresent()) buffer[0] |= kXBit; if (hdr_info_.nonReference) buffer[0] |= kNBit; if (packet_info.first_fragment) buffer[0] |= kSBit; buffer[0] |= (packet_info.first_partition_ix & kPartIdField); const int extension_length = WriteExtensionFields(buffer, buffer_length); if (extension_length < 0) return -1; memcpy(&buffer[vp8_fixed_payload_descriptor_bytes_ + extension_length], &payload_data_[packet_info.payload_start_pos], packet_info.size); // Return total length of written data. return packet_info.size + vp8_fixed_payload_descriptor_bytes_ + extension_length; } int RtpPacketizerVp8::WriteExtensionFields(uint8_t* buffer, size_t buffer_length) const { size_t extension_length = 0; if (XFieldPresent()) { uint8_t* x_field = buffer + vp8_fixed_payload_descriptor_bytes_; *x_field = 0; extension_length = 1; // One octet for the X field. if (PictureIdPresent()) { if (WritePictureIDFields( x_field, buffer, buffer_length, &extension_length) < 0) { return -1; } } if (TL0PicIdxFieldPresent()) { if (WriteTl0PicIdxFields( x_field, buffer, buffer_length, &extension_length) < 0) { return -1; } } if (TIDFieldPresent() || KeyIdxFieldPresent()) { if (WriteTIDAndKeyIdxFields( x_field, buffer, buffer_length, &extension_length) < 0) { return -1; } } assert(extension_length == PayloadDescriptorExtraLength()); } return static_cast(extension_length); } int RtpPacketizerVp8::WritePictureIDFields(uint8_t* x_field, uint8_t* buffer, size_t buffer_length, size_t* extension_length) const { *x_field |= kIBit; assert(buffer_length >= vp8_fixed_payload_descriptor_bytes_ + *extension_length); const int pic_id_length = WritePictureID( buffer + vp8_fixed_payload_descriptor_bytes_ + *extension_length, buffer_length - vp8_fixed_payload_descriptor_bytes_ - *extension_length); if (pic_id_length < 0) return -1; *extension_length += pic_id_length; return 0; } int RtpPacketizerVp8::WritePictureID(uint8_t* buffer, size_t buffer_length) const { const uint16_t pic_id = static_cast(hdr_info_.pictureId); size_t picture_id_len = PictureIdLength(); if (picture_id_len > buffer_length) return -1; if (picture_id_len == 2) { buffer[0] = 0x80 | ((pic_id >> 8) & 0x7F); buffer[1] = pic_id & 0xFF; } else if (picture_id_len == 1) { buffer[0] = pic_id & 0x7F; } return static_cast(picture_id_len); } int RtpPacketizerVp8::WriteTl0PicIdxFields(uint8_t* x_field, uint8_t* buffer, size_t buffer_length, size_t* extension_length) const { if (buffer_length < vp8_fixed_payload_descriptor_bytes_ + *extension_length + 1) { return -1; } *x_field |= kLBit; buffer[vp8_fixed_payload_descriptor_bytes_ + *extension_length] = hdr_info_.tl0PicIdx; ++*extension_length; return 0; } int RtpPacketizerVp8::WriteTIDAndKeyIdxFields(uint8_t* x_field, uint8_t* buffer, size_t buffer_length, size_t* extension_length) const { if (buffer_length < vp8_fixed_payload_descriptor_bytes_ + *extension_length + 1) { return -1; } uint8_t* data_field = &buffer[vp8_fixed_payload_descriptor_bytes_ + *extension_length]; *data_field = 0; if (TIDFieldPresent()) { *x_field |= kTBit; assert(hdr_info_.temporalIdx <= 3); *data_field |= hdr_info_.temporalIdx << 6; *data_field |= hdr_info_.layerSync ? kYBit : 0; } if (KeyIdxFieldPresent()) { *x_field |= kKBit; *data_field |= (hdr_info_.keyIdx & kKeyIdxField); } ++*extension_length; return 0; } size_t RtpPacketizerVp8::PayloadDescriptorExtraLength() const { size_t length_bytes = PictureIdLength(); if (TL0PicIdxFieldPresent()) ++length_bytes; if (TIDFieldPresent() || KeyIdxFieldPresent()) ++length_bytes; if (length_bytes > 0) ++length_bytes; // Include the extension field. return length_bytes; } size_t RtpPacketizerVp8::PictureIdLength() const { if (hdr_info_.pictureId == kNoPictureId) { return 0; } return 2; } bool RtpPacketizerVp8::XFieldPresent() const { return (TIDFieldPresent() || TL0PicIdxFieldPresent() || PictureIdPresent() || KeyIdxFieldPresent()); } bool RtpPacketizerVp8::TIDFieldPresent() const { assert((hdr_info_.layerSync == false) || (hdr_info_.temporalIdx != kNoTemporalIdx)); return (hdr_info_.temporalIdx != kNoTemporalIdx); } bool RtpPacketizerVp8::KeyIdxFieldPresent() const { return (hdr_info_.keyIdx != kNoKeyIdx); } bool RtpPacketizerVp8::TL0PicIdxFieldPresent() const { return (hdr_info_.tl0PicIdx != kNoTl0PicIdx); } // // VP8 format: // // Payload descriptor // 0 1 2 3 4 5 6 7 // +-+-+-+-+-+-+-+-+ // |X|R|N|S|PartID | (REQUIRED) // +-+-+-+-+-+-+-+-+ // X: |I|L|T|K| RSV | (OPTIONAL) // +-+-+-+-+-+-+-+-+ // I: | PictureID | (OPTIONAL) // +-+-+-+-+-+-+-+-+ // L: | TL0PICIDX | (OPTIONAL) // +-+-+-+-+-+-+-+-+ // T/K: |TID:Y| KEYIDX | (OPTIONAL) // +-+-+-+-+-+-+-+-+ // // Payload header (considered part of the actual payload, sent to decoder) // 0 1 2 3 4 5 6 7 // +-+-+-+-+-+-+-+-+ // |Size0|H| VER |P| // +-+-+-+-+-+-+-+-+ // | ... | // + + bool RtpDepacketizerVp8::Parse(ParsedPayload* parsed_payload, const uint8_t* payload_data, size_t payload_data_length) { assert(parsed_payload != NULL); if (payload_data_length == 0) { LOG(LS_ERROR) << "Empty payload."; return false; } // Parse mandatory first byte of payload descriptor. bool extension = (*payload_data & 0x80) ? true : false; // X bit bool beginning_of_partition = (*payload_data & 0x10) ? true : false; // S bit int partition_id = (*payload_data & 0x0F); // PartID field parsed_payload->type.Video.width = 0; parsed_payload->type.Video.height = 0; parsed_payload->type.Video.is_first_packet_in_frame = beginning_of_partition && (partition_id == 0); parsed_payload->type.Video.simulcastIdx = 0; parsed_payload->type.Video.codec = kRtpVideoVp8; parsed_payload->type.Video.codecHeader.VP8.nonReference = (*payload_data & 0x20) ? true : false; // N bit parsed_payload->type.Video.codecHeader.VP8.partitionId = partition_id; parsed_payload->type.Video.codecHeader.VP8.beginningOfPartition = beginning_of_partition; parsed_payload->type.Video.codecHeader.VP8.pictureId = kNoPictureId; parsed_payload->type.Video.codecHeader.VP8.tl0PicIdx = kNoTl0PicIdx; parsed_payload->type.Video.codecHeader.VP8.temporalIdx = kNoTemporalIdx; parsed_payload->type.Video.codecHeader.VP8.layerSync = false; parsed_payload->type.Video.codecHeader.VP8.keyIdx = kNoKeyIdx; if (partition_id > 8) { // Weak check for corrupt payload_data: PartID MUST NOT be larger than 8. return false; } // Advance payload_data and decrease remaining payload size. payload_data++; if (payload_data_length <= 1) { LOG(LS_ERROR) << "Error parsing VP8 payload descriptor!"; return false; } payload_data_length--; if (extension) { const int parsed_bytes = ParseVP8Extension(&parsed_payload->type.Video.codecHeader.VP8, payload_data, payload_data_length); if (parsed_bytes < 0) return false; payload_data += parsed_bytes; payload_data_length -= parsed_bytes; if (payload_data_length == 0) { LOG(LS_ERROR) << "Error parsing VP8 payload descriptor!"; return false; } } // Read P bit from payload header (only at beginning of first partition). if (beginning_of_partition && partition_id == 0) { parsed_payload->frame_type = (*payload_data & 0x01) ? kVideoFrameDelta : kVideoFrameKey; } else { parsed_payload->frame_type = kVideoFrameDelta; } if (ParseVP8FrameSize(parsed_payload, payload_data, payload_data_length) != 0) { return false; } parsed_payload->payload = payload_data; parsed_payload->payload_length = payload_data_length; return true; } } // namespace webrtc