diff options
author | eero.hakkinen <eero.hakkinen@intel.com> | 2016-03-23 04:19:02 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-03-23 11:20:08 +0000 |
commit | 208650d9e239e6d8119ba9fb75234c168df177d6 (patch) | |
tree | de23b899b977f6169c9c62629747f87ce288a156 /extensions/renderer | |
parent | 28a88d7f04bb212108adb63002666ead2606bead (diff) | |
download | chromium_src-208650d9e239e6d8119ba9fb75234c168df177d6.zip chromium_src-208650d9e239e6d8119ba9fb75234c168df177d6.tar.gz chromium_src-208650d9e239e6d8119ba9fb75234c168df177d6.tar.bz2 |
[chrome.displaySource] Implement media packetizer.
The WiFi Display media packetizer extends a WiFi Display transport stream
packetizer and packetizes Transport Stream (TS) packets further to media
datagram (RTP) packets which can be passed to a remote sink over an UDP
connection.
This is part of a WiFi Display packetizer patch series:
* https://codereview.chromium.org/1796123002/
WiFi Display elementary stream packetizer
* https://codereview.chromium.org/1800493003/
WiFi Display elementary stream descriptors
* https://codereview.chromium.org/1797953002/
WiFi Display transport stream packetizer
* https://codereview.chromium.org/1796073003/ <-- this CL
WiFi Display media packetizer
BUG=242107
Review URL: https://codereview.chromium.org/1796073003
Cr-Commit-Position: refs/heads/master@{#382829}
Diffstat (limited to 'extensions/renderer')
3 files changed, 362 insertions, 0 deletions
diff --git a/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.cc b/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.cc new file mode 100644 index 0000000..f8f9cf23 --- /dev/null +++ b/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.cc @@ -0,0 +1,110 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.h" + +#include <utility> + +#include "base/logging.h" +#include "base/rand_util.h" +#include "crypto/random.h" +#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h" + +namespace extensions { +namespace { +const size_t kMaxTransportStreamPacketCount = 7u; +const uint8_t kProtocolPayloadTypeMP2T = 33u; +const uint8_t kProtocolVersion = 2u; +} // namespace + +WiFiDisplayMediaDatagramPacket::WiFiDisplayMediaDatagramPacket() = default; + +WiFiDisplayMediaDatagramPacket::WiFiDisplayMediaDatagramPacket( + WiFiDisplayMediaDatagramPacket&&) = default; + +WiFiDisplayMediaPacketizer::WiFiDisplayMediaPacketizer( + const base::TimeDelta& delay_for_unit_time_stamps, + std::vector<WiFiDisplayElementaryStreamInfo> stream_infos, + const PacketizedCallback& on_packetized) + : WiFiDisplayTransportStreamPacketizer(delay_for_unit_time_stamps, + std::move(stream_infos)), + on_packetized_media_datagram_packet_(on_packetized) { + // Sequence numbers are mainly used for detecting lossed packets within one + // RTP session. The initial value SHOULD be random (unpredictable) to make + // known-plaintext attacks on encryption more difficult, in case the packets + // flow through a translator that encrypts them. + crypto::RandBytes(&sequence_number_, sizeof(sequence_number_)); + base::RandBytes(&synchronization_source_identifier_, + sizeof(synchronization_source_identifier_)); +} + +WiFiDisplayMediaPacketizer::~WiFiDisplayMediaPacketizer() {} + +bool WiFiDisplayMediaPacketizer::OnPacketizedTransportStreamPacket( + const WiFiDisplayTransportStreamPacket& transport_stream_packet, + bool flush) { + DCHECK(CalledOnValidThread()); + + if (media_datagram_packet_.empty()) { + // Convert time to the number of 90 kHz ticks since some epoch. + const uint64_t us = static_cast<uint64_t>( + (base::TimeTicks::Now() - base::TimeTicks()).InMicroseconds()); + const uint64_t time_stamp = (us * 90u + 500u) / 1000u; + const uint8_t header_without_identifiers[] = { + (kProtocolVersion << 6 | // Version (2 bits) + 0x0u << 5 | // Padding (no) + 0x0u << 4 | // Extension (no) + 0u << 0), // CSRC count (4 bits) + (0x0u << 7 | // Marker (no) + kProtocolPayloadTypeMP2T << 0), // Payload type (7 bits) + sequence_number_ >> 8, // Sequence number (16 bits) + sequence_number_ & 0xFFu, // + (time_stamp >> 24) & 0xFFu, // Time stamp (32 bits) + (time_stamp >> 16) & 0xFFu, // + (time_stamp >> 8) & 0xFFu, // + time_stamp & 0xFFu}; // + ++sequence_number_; + media_datagram_packet_.reserve( + sizeof(header_without_identifiers) + + sizeof(synchronization_source_identifier_) + + kMaxTransportStreamPacketCount * + WiFiDisplayTransportStreamPacket::kPacketSize); + media_datagram_packet_.insert(media_datagram_packet_.end(), + std::begin(header_without_identifiers), + std::end(header_without_identifiers)); + media_datagram_packet_.insert( + media_datagram_packet_.end(), + std::begin(synchronization_source_identifier_), + std::end(synchronization_source_identifier_)); + DCHECK_EQ(0u, media_datagram_packet_.size() / + WiFiDisplayTransportStreamPacket::kPacketSize); + } + + // Append header and payload data. + media_datagram_packet_.insert(media_datagram_packet_.end(), + transport_stream_packet.header().begin(), + transport_stream_packet.header().end()); + media_datagram_packet_.insert(media_datagram_packet_.end(), + transport_stream_packet.payload().begin(), + transport_stream_packet.payload().end()); + media_datagram_packet_.insert(media_datagram_packet_.end(), + transport_stream_packet.filler().size(), + transport_stream_packet.filler().value()); + + // Combine multiple transport stream packets into one datagram packet + // by delaying delegation whenever possible. + if (!flush) { + const size_t transport_stream_packet_count = + media_datagram_packet_.size() / + WiFiDisplayTransportStreamPacket::kPacketSize; + if (transport_stream_packet_count < kMaxTransportStreamPacketCount) + return true; + } + + WiFiDisplayMediaDatagramPacket packet; + packet.swap(media_datagram_packet_); + return on_packetized_media_datagram_packet_.Run(std::move(packet)); +} + +} // namespace extensions diff --git a/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.h b/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.h new file mode 100644 index 0000000..23c07e0 --- /dev/null +++ b/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.h @@ -0,0 +1,61 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_MEDIA_PACKETIZER_H_ +#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_MEDIA_PACKETIZER_H_ + +#include <vector> + +#include "base/callback.h" +#include "base/move.h" +#include "extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h" + +namespace extensions { + +// This class represents an RTP datagram packet containing MPEG Transport +// Stream (MPEG-TS) packets as a payload. +class WiFiDisplayMediaDatagramPacket : public std::vector<uint8_t> { + public: + WiFiDisplayMediaDatagramPacket(); + WiFiDisplayMediaDatagramPacket(WiFiDisplayMediaDatagramPacket&&); + + private: + DISALLOW_COPY_AND_ASSIGN_WITH_MOVE_FOR_BIND(WiFiDisplayMediaDatagramPacket); +}; + +// The WiFi Display media packetizer packetizes unit buffers to media datagram +// packets containing MPEG Transport Stream (MPEG-TS) packets containing either +// meta information or Packetized Elementary Stream (PES) packets containing +// unit data. +// +// Whenever a media datagram packet is fully created and thus ready for further +// processing, a callback is called. +class WiFiDisplayMediaPacketizer : public WiFiDisplayTransportStreamPacketizer { + public: + using PacketizedCallback = + base::Callback<bool(WiFiDisplayMediaDatagramPacket)>; + + WiFiDisplayMediaPacketizer( + const base::TimeDelta& delay_for_unit_time_stamps, + std::vector<WiFiDisplayElementaryStreamInfo> stream_infos, + const PacketizedCallback& on_packetized); + ~WiFiDisplayMediaPacketizer() override; + + protected: + bool OnPacketizedTransportStreamPacket( + const WiFiDisplayTransportStreamPacket& transport_stream_packet, + bool flush) override; + + private: + using SourceIdentifier = uint8_t[4]; + + WiFiDisplayMediaDatagramPacket media_datagram_packet_; + PacketizedCallback on_packetized_media_datagram_packet_; + uint16_t sequence_number_; + SourceIdentifier synchronization_source_identifier_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_MEDIA_PACKETIZER_H_ diff --git a/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer_unittest.cc b/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer_unittest.cc index 4ba1969..5253a1b 100644 --- a/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer_unittest.cc +++ b/extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer_unittest.cc @@ -2,12 +2,16 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <algorithm> +#include <array> #include <list> #include "base/big_endian.h" +#include "base/bind.h" #include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h" #include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h" #include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.h" +#include "extensions/renderer/api/display_source/wifi_display/wifi_display_media_packetizer.h" #include "extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h" #include "testing/gtest/include/gtest/gtest.h" @@ -42,6 +46,17 @@ const unsigned kPtsFlag = 0x0080u; const size_t kUnitDataAlignment = sizeof(uint32_t); } +namespace rtp { +const unsigned kVersionMask = 0xC000u; +const unsigned kVersion2 = 0x8000u; +const unsigned kPaddingFlag = 0x2000u; +const unsigned kExtensionFlag = 0x1000u; +const unsigned kContributingSourceCountMask = 0x0F00u; +const unsigned kMarkerFlag = 0x0010u; +const unsigned kPayloadTypeMask = 0x007Fu; +const unsigned kPayloadTypeMP2T = 0x0021u; +} // namespace rtp + namespace ts { const uint64_t kTimeStampMask = (static_cast<uint64_t>(1u) << 33) - 1u; const uint64_t kTimeStampSecond = 90000u; // 90 kHz @@ -70,6 +85,7 @@ const unsigned kProgramMapTablePacketId = 0x0100u; const unsigned kProgramClockReferencePacketId = 0x1000u; const unsigned kVideoStreamPacketId = 0x1011u; const unsigned kFirstAudioStreamPacketId = 0x1100u; +const size_t kMaxTransportStreamPacketCountPerDatagramPacket = 7u; } // namespace widi template <typename PacketContainer> @@ -85,6 +101,36 @@ class PacketCollector { PacketContainer packets_; }; +class FakeMediaPacketizer + : public WiFiDisplayMediaPacketizer, + public PacketCollector<std::vector<std::vector<uint8_t>>> { + public: + FakeMediaPacketizer(const base::TimeDelta& delay_for_unit_time_stamps, + std::vector<WiFiDisplayElementaryStreamInfo> stream_infos) + : WiFiDisplayMediaPacketizer( + delay_for_unit_time_stamps, + std::move(stream_infos), + base::Bind(&FakeMediaPacketizer::OnPacketizedMediaDatagramPacket, + base::Unretained(this))) {} + + // Extend the interface in order to allow to bypass packetization of units to + // Packetized Elementary Stream (PES) packets and further to Transport Stream + // (TS) packets and to test only packetization of TS packets to media + // datagram packets. + bool EncodeTransportStreamPacket( + const WiFiDisplayTransportStreamPacket& transport_stream_packet, + bool flush) { + return OnPacketizedTransportStreamPacket(transport_stream_packet, flush); + } + + private: + bool OnPacketizedMediaDatagramPacket( + WiFiDisplayMediaDatagramPacket media_datagram_packet) { + packets_.emplace_back(std::move(media_datagram_packet)); + return true; + } +}; + class FakeTransportStreamPacketizer : public WiFiDisplayTransportStreamPacketizer, public PacketCollector<std::list<WiFiDisplayTransportStreamPacket>> { @@ -710,5 +756,150 @@ INSTANTIATE_TEST_CASE_P( base::TimeDelta::FromMicroseconds( 1000 * INT64_C(0x123456789) / 90)))); +TEST(WiFiDisplayTransportStreamPacketizationTest, EncodeToMediaDatagramPacket) { + const size_t kPacketHeaderSize = 12u; + + // Create fake units. + const size_t kUnitCount = 12u; + const size_t kUnitSize = + WiFiDisplayTransportStreamPacket::kPacketSize - 4u - 12u; + std::vector<std::array<uint8_t, kUnitSize>> units(kUnitCount); + for (auto& unit : units) + unit.fill(static_cast<uint8_t>(&unit - units.data())); + + // Create transport stream packets. + std::vector<WiFiDisplayElementaryStreamInfo> stream_infos; + stream_infos.emplace_back(WiFiDisplayElementaryStreamInfo::VIDEO_H264); + FakeTransportStreamPacketizer transport_stream_packetizer( + base::TimeDelta::FromMilliseconds(0), std::move(stream_infos)); + for (const auto& unit : units) { + EXPECT_TRUE(transport_stream_packetizer.EncodeElementaryStreamUnit( + 0u, unit.data(), unit.size(), false, base::TimeTicks(), + base::TimeTicks(), &unit == &units.back())); + } + auto transport_stream_packets = transport_stream_packetizer.FetchPackets(); + // There should be exactly one transport stream payload packet for each unit. + // There should also be some but not too many transport stream meta + // information packets. + EXPECT_EQ(1u, transport_stream_packets.size() / kUnitCount); + + // Encode transport stream packets to datagram packets. + FakeMediaPacketizer packetizer( + base::TimeDelta::FromMilliseconds(0), + std::vector<WiFiDisplayElementaryStreamInfo>()); + for (const auto& transport_stream_packet : transport_stream_packets) { + EXPECT_TRUE(packetizer.EncodeTransportStreamPacket( + transport_stream_packet, + &transport_stream_packet == &transport_stream_packets.back())); + } + auto packets = packetizer.FetchPackets(); + + // Check datagram packets. + ProgramClockReference pcr = {ProgramClockReference::kInvalidBase, 0u}; + uint16_t sequence_number; + uint32_t synchronization_source_identifier; + auto transport_stream_packet_it = transport_stream_packets.cbegin(); + for (const auto& packet : packets) { + base::BigEndianReader header_reader( + reinterpret_cast<const char*>(packet.data()), + std::min(kPacketHeaderSize, packet.size())); + + // Packet flags. + uint16_t parsed_u16; + EXPECT_TRUE(header_reader.ReadU16(&parsed_u16)); + EXPECT_EQ(rtp::kVersion2, parsed_u16 & rtp::kVersionMask); + EXPECT_FALSE(parsed_u16 & rtp::kPaddingFlag); + EXPECT_FALSE(parsed_u16 & rtp::kExtensionFlag); + EXPECT_EQ(0u, parsed_u16 & rtp::kContributingSourceCountMask); + EXPECT_FALSE(parsed_u16 & rtp::kMarkerFlag); + EXPECT_EQ(rtp::kPayloadTypeMP2T, parsed_u16 & rtp::kPayloadTypeMask); + + // Packet sequence number. + uint16_t parsed_sequence_number; + EXPECT_TRUE(header_reader.ReadU16(&parsed_sequence_number)); + if (&packet == &packets.front()) + sequence_number = parsed_sequence_number; + EXPECT_EQ(sequence_number++, parsed_sequence_number); + + // Packet time stamp. + uint32_t parsed_time_stamp; + EXPECT_TRUE(header_reader.ReadU32(&parsed_time_stamp)); + if (pcr.base == ProgramClockReference::kInvalidBase) { + // This happens only for the first datagram packet. + EXPECT_TRUE(&packet == &packets.front()); + // Ensure that the next datagram packet reaches the else branch. + EXPECT_FALSE(&packet == &packets.back()); + } else { + // Check that + // 0 <= parsed_time_stamp - pcr.base <= kTimeStampSecond + // but allow pcr.base and parsed_time_stamp to wrap around in 32 bits. + EXPECT_LE((parsed_time_stamp - pcr.base) & 0xFFFFFFFFu, + ts::kTimeStampSecond) + << " Time stamp must not be smaller than PCR!"; + } + + // Packet synchronization source identifier. + uint32_t parsed_synchronization_source_identifier; + EXPECT_TRUE( + header_reader.ReadU32(&parsed_synchronization_source_identifier)); + if (&packet == &packets.front()) { + synchronization_source_identifier = + parsed_synchronization_source_identifier; + } + EXPECT_EQ(synchronization_source_identifier, + parsed_synchronization_source_identifier); + + EXPECT_EQ(0, header_reader.remaining()); + + // Packet payload. + size_t offset = kPacketHeaderSize; + while (offset + WiFiDisplayTransportStreamPacket::kPacketSize <= + packet.size() && + transport_stream_packet_it != transport_stream_packets.end()) { + const auto& transport_stream_packet = *transport_stream_packet_it++; + const PacketPart parsed_transport_stream_packet_header( + packet.data() + offset, transport_stream_packet.header().size()); + const PacketPart parsed_transport_stream_packet_payload( + parsed_transport_stream_packet_header.end(), + transport_stream_packet.payload().size()); + const PacketPart parsed_transport_stream_packet_filler( + parsed_transport_stream_packet_payload.end(), + transport_stream_packet.filler().size()); + offset += WiFiDisplayTransportStreamPacket::kPacketSize; + + // Check bytes. + EXPECT_EQ(transport_stream_packet.header(), + parsed_transport_stream_packet_header); + EXPECT_EQ(transport_stream_packet.payload(), + parsed_transport_stream_packet_payload); + EXPECT_EQ(transport_stream_packet.filler().size(), + std::count(parsed_transport_stream_packet_filler.begin(), + parsed_transport_stream_packet_filler.end(), + transport_stream_packet.filler().value())); + + if (ParseTransportStreamPacketId(transport_stream_packet) == + widi::kProgramClockReferencePacketId) { + pcr = ParseProgramClockReference( + &transport_stream_packet.header().begin()[6]); + } + } + EXPECT_EQ(offset, packet.size()) << " Extra packet payload bytes."; + + // Check that the payload contains a correct number of transport stream + // packets. + const size_t transport_stream_packet_count_in_datagram_packet = + packet.size() / WiFiDisplayTransportStreamPacket::kPacketSize; + if (&packet == &packets.back()) { + EXPECT_GE(transport_stream_packet_count_in_datagram_packet, 1u); + EXPECT_LE(transport_stream_packet_count_in_datagram_packet, + widi::kMaxTransportStreamPacketCountPerDatagramPacket); + } else { + EXPECT_EQ(widi::kMaxTransportStreamPacketCountPerDatagramPacket, + transport_stream_packet_count_in_datagram_packet); + } + } + EXPECT_EQ(transport_stream_packets.end(), transport_stream_packet_it); +} + } // namespace } // namespace extensions |