| /* |
| * Copyright (c) 2014 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 <string.h> |
| |
| #include "webrtc/modules/interface/module_common_types.h" |
| #include "webrtc/modules/rtp_rtcp/source/rtp_format_h264.h" |
| #include "webrtc/modules/rtp_rtcp/source/rtp_utility.h" |
| |
| namespace webrtc { |
| namespace { |
| |
| enum Nalu { |
| kSlice = 1, |
| kIdr = 5, |
| kSei = 6, |
| kSps = 7, |
| kPps = 8, |
| kStapA = 24, |
| kFuA = 28 |
| }; |
| |
| static const size_t kNalHeaderSize = 1; |
| static const size_t kFuAHeaderSize = 2; |
| static const size_t kLengthFieldSize = 2; |
| |
| // Bit masks for FU (A and B) indicators. |
| enum NalDefs { kFBit = 0x80, kNriMask = 0x60, kTypeMask = 0x1F }; |
| |
| // Bit masks for FU (A and B) headers. |
| enum FuDefs { kSBit = 0x80, kEBit = 0x40, kRBit = 0x20 }; |
| |
| void ParseSingleNalu(RtpDepacketizer::ParsedPayload* parsed_payload, |
| const uint8_t* payload_data, |
| size_t payload_data_length) { |
| parsed_payload->type.Video.width = 0; |
| parsed_payload->type.Video.height = 0; |
| parsed_payload->type.Video.codec = kRtpVideoH264; |
| parsed_payload->type.Video.isFirstPacket = true; |
| RTPVideoHeaderH264* h264_header = |
| &parsed_payload->type.Video.codecHeader.H264; |
| h264_header->single_nalu = true; |
| h264_header->stap_a = false; |
| |
| uint8_t nal_type = payload_data[0] & kTypeMask; |
| if (nal_type == kStapA) { |
| nal_type = payload_data[3] & kTypeMask; |
| h264_header->stap_a = true; |
| } |
| |
| switch (nal_type) { |
| case kSps: |
| case kPps: |
| case kIdr: |
| parsed_payload->frame_type = kVideoFrameKey; |
| break; |
| default: |
| parsed_payload->frame_type = kVideoFrameDelta; |
| break; |
| } |
| } |
| |
| void ParseFuaNalu(RtpDepacketizer::ParsedPayload* parsed_payload, |
| const uint8_t* payload_data, |
| size_t payload_data_length, |
| size_t* offset) { |
| uint8_t fnri = payload_data[0] & (kFBit | kNriMask); |
| uint8_t original_nal_type = payload_data[1] & kTypeMask; |
| bool first_fragment = (payload_data[1] & kSBit) > 0; |
| |
| uint8_t original_nal_header = fnri | original_nal_type; |
| if (first_fragment) { |
| *offset = kNalHeaderSize; |
| uint8_t* payload = const_cast<uint8_t*>(payload_data + *offset); |
| payload[0] = original_nal_header; |
| } else { |
| *offset = kFuAHeaderSize; |
| } |
| |
| if (original_nal_type == kIdr) { |
| parsed_payload->frame_type = kVideoFrameKey; |
| } else { |
| parsed_payload->frame_type = kVideoFrameDelta; |
| } |
| parsed_payload->type.Video.width = 0; |
| parsed_payload->type.Video.height = 0; |
| parsed_payload->type.Video.codec = kRtpVideoH264; |
| parsed_payload->type.Video.isFirstPacket = first_fragment; |
| RTPVideoHeaderH264* h264_header = |
| &parsed_payload->type.Video.codecHeader.H264; |
| h264_header->single_nalu = false; |
| h264_header->stap_a = false; |
| } |
| } // namespace |
| |
| RtpPacketizerH264::RtpPacketizerH264(FrameType frame_type, |
| size_t max_payload_len) |
| : payload_data_(NULL), |
| payload_size_(0), |
| max_payload_len_(max_payload_len), |
| frame_type_(frame_type) { |
| } |
| |
| RtpPacketizerH264::~RtpPacketizerH264() { |
| } |
| |
| void RtpPacketizerH264::SetPayloadData( |
| const uint8_t* payload_data, |
| size_t payload_size, |
| const RTPFragmentationHeader* fragmentation) { |
| assert(packets_.empty()); |
| assert(fragmentation); |
| payload_data_ = payload_data; |
| payload_size_ = payload_size; |
| fragmentation_.CopyFrom(*fragmentation); |
| GeneratePackets(); |
| } |
| |
| void RtpPacketizerH264::GeneratePackets() { |
| for (size_t i = 0; i < fragmentation_.fragmentationVectorSize;) { |
| size_t fragment_offset = fragmentation_.fragmentationOffset[i]; |
| size_t fragment_length = fragmentation_.fragmentationLength[i]; |
| if (fragment_length > max_payload_len_) { |
| PacketizeFuA(fragment_offset, fragment_length); |
| ++i; |
| } else { |
| i = PacketizeStapA(i, fragment_offset, fragment_length); |
| } |
| } |
| } |
| |
| void RtpPacketizerH264::PacketizeFuA(size_t fragment_offset, |
| size_t fragment_length) { |
| // Fragment payload into packets (FU-A). |
| // Strip out the original header and leave room for the FU-A header. |
| fragment_length -= kNalHeaderSize; |
| size_t offset = fragment_offset + kNalHeaderSize; |
| size_t bytes_available = max_payload_len_ - kFuAHeaderSize; |
| size_t fragments = |
| (fragment_length + (bytes_available - 1)) / bytes_available; |
| size_t avg_size = (fragment_length + fragments - 1) / fragments; |
| while (fragment_length > 0) { |
| size_t packet_length = avg_size; |
| if (fragment_length < avg_size) |
| packet_length = fragment_length; |
| uint8_t header = payload_data_[fragment_offset]; |
| packets_.push(Packet(offset, |
| packet_length, |
| offset - kNalHeaderSize == fragment_offset, |
| fragment_length == packet_length, |
| false, |
| header)); |
| offset += packet_length; |
| fragment_length -= packet_length; |
| } |
| } |
| |
| int RtpPacketizerH264::PacketizeStapA(size_t fragment_index, |
| size_t fragment_offset, |
| size_t fragment_length) { |
| // Aggregate fragments into one packet (STAP-A). |
| size_t payload_size_left = max_payload_len_; |
| int aggregated_fragments = 0; |
| size_t fragment_headers_length = 0; |
| assert(payload_size_left >= fragment_length); |
| while (payload_size_left >= fragment_length + fragment_headers_length) { |
| assert(fragment_length > 0); |
| uint8_t header = payload_data_[fragment_offset]; |
| packets_.push(Packet(fragment_offset, |
| fragment_length, |
| aggregated_fragments == 0, |
| false, |
| true, |
| header)); |
| payload_size_left -= fragment_length; |
| payload_size_left -= fragment_headers_length; |
| |
| // Next fragment. |
| ++fragment_index; |
| if (fragment_index == fragmentation_.fragmentationVectorSize) |
| break; |
| fragment_offset = fragmentation_.fragmentationOffset[fragment_index]; |
| fragment_length = fragmentation_.fragmentationLength[fragment_index]; |
| |
| fragment_headers_length = kLengthFieldSize; |
| // If we are going to try to aggregate more fragments into this packet |
| // we need to add the STAP-A NALU header and a length field for the first |
| // NALU of this packet. |
| if (aggregated_fragments == 0) |
| fragment_headers_length += kNalHeaderSize + kLengthFieldSize; |
| ++aggregated_fragments; |
| } |
| packets_.back().last_fragment = true; |
| return fragment_index; |
| } |
| |
| bool RtpPacketizerH264::NextPacket(uint8_t* buffer, |
| size_t* bytes_to_send, |
| bool* last_packet) { |
| *bytes_to_send = 0; |
| if (packets_.empty()) { |
| *bytes_to_send = 0; |
| *last_packet = true; |
| return false; |
| } |
| |
| Packet packet = packets_.front(); |
| |
| if (packet.first_fragment && packet.last_fragment) { |
| // Single NAL unit packet. |
| *bytes_to_send = packet.size; |
| memcpy(buffer, &payload_data_[packet.offset], packet.size); |
| packets_.pop(); |
| assert(*bytes_to_send <= max_payload_len_); |
| } else if (packet.aggregated) { |
| NextAggregatePacket(buffer, bytes_to_send); |
| assert(*bytes_to_send <= max_payload_len_); |
| } else { |
| NextFragmentPacket(buffer, bytes_to_send); |
| assert(*bytes_to_send <= max_payload_len_); |
| } |
| *last_packet = packets_.empty(); |
| return true; |
| } |
| |
| void RtpPacketizerH264::NextAggregatePacket(uint8_t* buffer, |
| size_t* bytes_to_send) { |
| Packet packet = packets_.front(); |
| assert(packet.first_fragment); |
| // STAP-A NALU header. |
| buffer[0] = (packet.header & (kFBit | kNriMask)) | kStapA; |
| int index = kNalHeaderSize; |
| *bytes_to_send += kNalHeaderSize; |
| while (packet.aggregated) { |
| // Add NAL unit length field. |
| RtpUtility::AssignUWord16ToBuffer(&buffer[index], packet.size); |
| index += kLengthFieldSize; |
| *bytes_to_send += kLengthFieldSize; |
| // Add NAL unit. |
| memcpy(&buffer[index], &payload_data_[packet.offset], packet.size); |
| index += packet.size; |
| *bytes_to_send += packet.size; |
| packets_.pop(); |
| if (packet.last_fragment) |
| break; |
| packet = packets_.front(); |
| } |
| assert(packet.last_fragment); |
| } |
| |
| void RtpPacketizerH264::NextFragmentPacket(uint8_t* buffer, |
| size_t* bytes_to_send) { |
| Packet packet = packets_.front(); |
| // NAL unit fragmented over multiple packets (FU-A). |
| // We do not send original NALU header, so it will be replaced by the |
| // FU indicator header of the first packet. |
| uint8_t fu_indicator = (packet.header & (kFBit | kNriMask)) | kFuA; |
| uint8_t fu_header = 0; |
| |
| // S | E | R | 5 bit type. |
| fu_header |= (packet.first_fragment ? kSBit : 0); |
| fu_header |= (packet.last_fragment ? kEBit : 0); |
| uint8_t type = packet.header & kTypeMask; |
| fu_header |= type; |
| buffer[0] = fu_indicator; |
| buffer[1] = fu_header; |
| |
| if (packet.last_fragment) { |
| *bytes_to_send = packet.size + kFuAHeaderSize; |
| memcpy(buffer + kFuAHeaderSize, &payload_data_[packet.offset], packet.size); |
| } else { |
| *bytes_to_send = packet.size + kFuAHeaderSize; |
| memcpy(buffer + kFuAHeaderSize, &payload_data_[packet.offset], packet.size); |
| } |
| packets_.pop(); |
| } |
| |
| ProtectionType RtpPacketizerH264::GetProtectionType() { |
| return (frame_type_ == kVideoFrameKey) ? kProtectedPacket |
| : kUnprotectedPacket; |
| } |
| |
| StorageType RtpPacketizerH264::GetStorageType( |
| uint32_t retransmission_settings) { |
| return kAllowRetransmission; |
| } |
| |
| std::string RtpPacketizerH264::ToString() { |
| return "RtpPacketizerH264"; |
| } |
| |
| bool RtpDepacketizerH264::Parse(ParsedPayload* parsed_payload, |
| const uint8_t* payload_data, |
| size_t payload_data_length) { |
| assert(parsed_payload != NULL); |
| uint8_t nal_type = payload_data[0] & kTypeMask; |
| size_t offset = 0; |
| if (nal_type == kFuA) { |
| // Fragmented NAL units (FU-A). |
| ParseFuaNalu(parsed_payload, payload_data, payload_data_length, &offset); |
| } else { |
| // We handle STAP-A and single NALU's the same way here. The jitter buffer |
| // will depacketize the STAP-A into NAL units later. |
| ParseSingleNalu(parsed_payload, payload_data, payload_data_length); |
| } |
| |
| parsed_payload->payload = payload_data + offset; |
| parsed_payload->payload_length = payload_data_length - offset; |
| return true; |
| } |
| } // namespace webrtc |