From db33431413fe39fca0a34381092301d91ca66f40 Mon Sep 17 00:00:00 2001 From: "eero.hakkinen" Date: Tue, 22 Mar 2016 15:15:16 -0700 Subject: [chrome.displaySource] Implement transport stream packetizer. The WiFi Display transport stream packetizer packetizes unit buffers first to Packetized Elementary Stream (PES) packets (using a WiFi Display elementary stream packetizer) and then to Transport Stream (TS) packets. It will be used as a base class for a WiFi Display media packetizer. 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/ <-- this CL WiFi Display transport stream packetizer * https://codereview.chromium.org/1796073003/ WiFi Display media packetizer BUG=242107 Review URL: https://codereview.chromium.org/1797953002 Cr-Commit-Position: refs/heads/master@{#382706} --- extensions/extensions.gypi | 4 + .../wifi_display_elementary_stream_info.cc | 46 ++ .../wifi_display_elementary_stream_info.h | 58 ++ .../wifi_display_media_packetizer_unittest.cc | 548 ++++++++++++++++ .../wifi_display_transport_stream_packetizer.cc | 727 +++++++++++++++++++++ .../wifi_display_transport_stream_packetizer.h | 176 +++++ 6 files changed, 1559 insertions(+) create mode 100644 extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.cc create mode 100644 extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h create mode 100644 extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.cc create mode 100644 extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h (limited to 'extensions') diff --git a/extensions/extensions.gypi b/extensions/extensions.gypi index e1c30bf..4ecff0d 100644 --- a/extensions/extensions.gypi +++ b/extensions/extensions.gypi @@ -1038,6 +1038,8 @@ 'extensions_renderer_sources_wifi_display': [ 'renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.cc', 'renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h', + 'renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.cc', + 'renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h', 'renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.cc', 'renderer/api/display_source/wifi_display/wifi_display_elementary_stream_packetizer.h', 'renderer/api/display_source/wifi_display/wifi_display_media_manager.cc', @@ -1045,6 +1047,8 @@ 'renderer/api/display_source/wifi_display/wifi_display_session.cc', 'renderer/api/display_source/wifi_display/wifi_display_session.h', 'renderer/api/display_source/wifi_display/wifi_display_stream_packet_part.h', + 'renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.cc', + 'renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h', ], 'extensions_utility_sources': [ 'utility/unpacker.cc', diff --git a/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.cc b/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.cc new file mode 100644 index 0000000..2c84885 --- /dev/null +++ b/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.cc @@ -0,0 +1,46 @@ +// 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_elementary_stream_info.h" + +#include + +namespace extensions { + +WiFiDisplayElementaryStreamInfo::WiFiDisplayElementaryStreamInfo( + ElementaryStreamType type) + : type_(type) {} + +WiFiDisplayElementaryStreamInfo::WiFiDisplayElementaryStreamInfo( + ElementaryStreamType type, + DescriptorVector descriptors) + : descriptors_(std::move(descriptors)), type_(type) {} + +WiFiDisplayElementaryStreamInfo::WiFiDisplayElementaryStreamInfo( + const WiFiDisplayElementaryStreamInfo&) = default; + +WiFiDisplayElementaryStreamInfo::WiFiDisplayElementaryStreamInfo( + WiFiDisplayElementaryStreamInfo&&) = default; + +WiFiDisplayElementaryStreamInfo::~WiFiDisplayElementaryStreamInfo() {} + +WiFiDisplayElementaryStreamInfo& WiFiDisplayElementaryStreamInfo::operator=( + WiFiDisplayElementaryStreamInfo&&) = default; + +void WiFiDisplayElementaryStreamInfo::AddDescriptor( + WiFiDisplayElementaryStreamDescriptor descriptor) { + descriptors_.emplace_back(std::move(descriptor)); +} + +const WiFiDisplayElementaryStreamDescriptor* +WiFiDisplayElementaryStreamInfo::FindDescriptor( + DescriptorTag descriptor_tag) const { + for (const auto& descriptor : descriptors()) { + if (descriptor.tag() == descriptor_tag) + return &descriptor; + } + return nullptr; +} + +} // namespace extensions diff --git a/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h b/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h new file mode 100644 index 0000000..a0e63a8 --- /dev/null +++ b/extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_info.h @@ -0,0 +1,58 @@ +// 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_ELEMENTARY_STREAM_INFO_H_ +#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_INFO_H_ + +#include +#include + +#include "extensions/renderer/api/display_source/wifi_display/wifi_display_elementary_stream_descriptor.h" + +namespace extensions { + +// WiFi Display elementary stream info is a container for elementary stream +// information and is used for passing that information to a WiFi Display +// transport stream packetizer. +class WiFiDisplayElementaryStreamInfo { + public: + using DescriptorVector = std::vector; + using DescriptorTag = WiFiDisplayElementaryStreamDescriptor::DescriptorTag; + + enum ElementaryStreamType : uint8_t { + AUDIO_AAC = 0x0Fu, + AUDIO_AC3 = 0x81u, + AUDIO_LPCM = 0x83u, + VIDEO_H264 = 0x1Bu, + }; + + explicit WiFiDisplayElementaryStreamInfo(ElementaryStreamType type); + WiFiDisplayElementaryStreamInfo(ElementaryStreamType type, + DescriptorVector descriptors); + WiFiDisplayElementaryStreamInfo(const WiFiDisplayElementaryStreamInfo&); + WiFiDisplayElementaryStreamInfo(WiFiDisplayElementaryStreamInfo&&); + ~WiFiDisplayElementaryStreamInfo(); + + WiFiDisplayElementaryStreamInfo& operator=(WiFiDisplayElementaryStreamInfo&&); + + const DescriptorVector& descriptors() const { return descriptors_; } + ElementaryStreamType type() const { return type_; } + + void AddDescriptor(WiFiDisplayElementaryStreamDescriptor descriptor); + const WiFiDisplayElementaryStreamDescriptor* FindDescriptor( + DescriptorTag descriptor_tag) const; + template + const Descriptor* FindDescriptor() const { + return static_cast( + FindDescriptor(static_cast(Descriptor::kTag))); + } + + private: + DescriptorVector descriptors_; + ElementaryStreamType type_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_ELEMENTARY_STREAM_INFO_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 a8b769a..4ba1969 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,37 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include + #include "base/big_endian.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_transport_stream_packetizer.h" #include "testing/gtest/include/gtest/gtest.h" +using PacketPart = extensions::WiFiDisplayStreamPacketPart; + namespace extensions { +std::ostream& operator<<(std::ostream& os, const PacketPart& part) { + const auto flags = os.flags(); + os << "{" << std::hex << std::noshowbase; + for (const auto& item : part) { + if (&item != &*part.begin()) + os << ", "; + os << "0x" << static_cast(item); + } + os.setf(flags, std::ios::basefield | std::ios::showbase); + return os << "}"; +} + +bool operator==(const PacketPart& a, const PacketPart& b) { + if (a.size() != b.size()) + return false; + return std::equal(a.begin(), a.end(), b.begin()); +} + namespace { namespace pes { @@ -17,6 +42,108 @@ const unsigned kPtsFlag = 0x0080u; const size_t kUnitDataAlignment = sizeof(uint32_t); } +namespace ts { +const uint64_t kTimeStampMask = (static_cast(1u) << 33) - 1u; +const uint64_t kTimeStampSecond = 90000u; // 90 kHz +const uint64_t kProgramClockReferenceSecond = + 300u * kTimeStampSecond; // 27 MHz + +// Packet header: +const size_t kPacketHeaderSize = 4u; +const unsigned kSyncByte = 0x47u; +const uint32_t kSyncByteMask = 0xFF000000u; +const uint32_t kTransportErrorIndicator = 0x00800000u; +const uint32_t kPayloadUnitStartIndicator = 0x00400000u; +const uint32_t kTransportPriority = 0x00200000u; +const uint32_t kScramblingControlMask = 0x000000C0u; +const uint32_t kAdaptationFieldFlag = 0x00000020u; +const uint32_t kPayloadFlag = 0x00000010u; + +// Adaptation field: +const unsigned kRandomAccessFlag = 0x40u; +const unsigned kPcrFlag = 0x10u; +} // namespace ts + +namespace widi { +const unsigned kProgramAssociationTablePacketId = 0x0000u; +const unsigned kProgramMapTablePacketId = 0x0100u; +const unsigned kProgramClockReferencePacketId = 0x1000u; +const unsigned kVideoStreamPacketId = 0x1011u; +const unsigned kFirstAudioStreamPacketId = 0x1100u; +} // namespace widi + +template +class PacketCollector { + public: + PacketContainer FetchPackets() { + PacketContainer container; + container.swap(packets_); + return container; + } + + protected: + PacketContainer packets_; +}; + +class FakeTransportStreamPacketizer + : public WiFiDisplayTransportStreamPacketizer, + public PacketCollector> { + public: + FakeTransportStreamPacketizer( + const base::TimeDelta& delay_for_unit_time_stamps, + std::vector stream_infos) + : WiFiDisplayTransportStreamPacketizer(delay_for_unit_time_stamps, + std::move(stream_infos)) {} + + using WiFiDisplayTransportStreamPacketizer::NormalizeUnitTimeStamps; + + protected: + bool OnPacketizedTransportStreamPacket( + const WiFiDisplayTransportStreamPacket& transport_stream_packet, + bool flush) override { + // Make a copy of header bytes as they are in stack. + headers_.emplace_back(transport_stream_packet.header().begin(), + transport_stream_packet.header().end()); + const auto& header = headers_.back(); + if (transport_stream_packet.payload().empty()) { + packets_.emplace_back(header.data(), header.size()); + } else { + packets_.emplace_back(header.data(), header.size(), + transport_stream_packet.payload().begin()); + } + EXPECT_EQ(transport_stream_packet.header().size(), + packets_.back().header().size()); + EXPECT_EQ(transport_stream_packet.payload().size(), + packets_.back().payload().size()); + EXPECT_EQ(transport_stream_packet.filler().size(), + packets_.back().filler().size()); + return true; + } + + private: + std::vector> headers_; +}; + +struct ProgramClockReference { + enum { kInvalidBase = ~static_cast(0u) }; + uint64_t base; + uint16_t extension; +}; + +ProgramClockReference ParseProgramClockReference(const uint8_t pcr_bytes[6]) { + const uint8_t reserved_pcr_bits = pcr_bytes[4] & 0x7Eu; + EXPECT_EQ(0x7Eu, reserved_pcr_bits); + ProgramClockReference pcr; + pcr.base = pcr_bytes[0]; + pcr.base = (pcr.base << 8) | pcr_bytes[1]; + pcr.base = (pcr.base << 8) | pcr_bytes[2]; + pcr.base = (pcr.base << 8) | pcr_bytes[3]; + pcr.base = (pcr.base << 1) | ((pcr_bytes[4] & 0x80u) >> 7); + pcr.extension = pcr_bytes[4] & 0x01u; + pcr.extension = (pcr.extension << 8) | pcr_bytes[5]; + return pcr; +} + uint64_t ParseTimeStamp(const uint8_t ts_bytes[5], uint8_t pts_dts_indicator) { EXPECT_EQ(pts_dts_indicator, (ts_bytes[0] & 0xF0u) >> 4); EXPECT_EQ(0x01u, ts_bytes[0] & 0x01u); @@ -31,6 +158,14 @@ uint64_t ParseTimeStamp(const uint8_t ts_bytes[5], uint8_t pts_dts_indicator) { return ts; } +unsigned ParseTransportStreamPacketId( + const WiFiDisplayTransportStreamPacket& packet) { + if (packet.header().size() < ts::kPacketHeaderSize) + return ~0u; + return (((packet.header().begin()[1] & 0x001Fu) << 8) | + packet.header().begin()[2]); +} + class WiFiDisplayElementaryStreamUnitPacketizationTest : public testing::TestWithParam< testing::tuple> { @@ -120,12 +255,268 @@ class WiFiDisplayElementaryStreamUnitPacketizationTest EXPECT_EQ(unit_.size(), packet.unit().size()); } + void CheckTransportStreamPacketHeader( + base::BigEndianReader* header_reader, + bool expected_payload_unit_start_indicator, + unsigned expected_packet_id, + bool* adaptation_field_flag, + uint8_t expected_continuity_counter) { + uint32_t parsed_u32; + EXPECT_TRUE(header_reader->ReadU32(&parsed_u32)); + EXPECT_EQ(ts::kSyncByte << 24u, parsed_u32 & ts::kSyncByteMask); + EXPECT_EQ(0x0u, parsed_u32 & ts::kTransportErrorIndicator); + EXPECT_EQ(expected_payload_unit_start_indicator, + (parsed_u32 & ts::kPayloadUnitStartIndicator) != 0u); + EXPECT_EQ(0x0u, parsed_u32 & ts::kTransportPriority); + EXPECT_EQ(expected_packet_id, (parsed_u32 & 0x001FFF00) >> 8); + EXPECT_EQ(0x0u, parsed_u32 & ts::kScramblingControlMask); + if (!adaptation_field_flag) { + EXPECT_EQ(0x0u, parsed_u32 & ts::kAdaptationFieldFlag); + } else { + *adaptation_field_flag = (parsed_u32 & ts::kAdaptationFieldFlag) != 0u; + } + EXPECT_EQ(ts::kPayloadFlag, parsed_u32 & ts::kPayloadFlag); + EXPECT_EQ(expected_continuity_counter & 0xFu, parsed_u32 & 0x0000000Fu); + } + + void CheckTransportStreamAdaptationField( + base::BigEndianReader* header_reader, + const WiFiDisplayTransportStreamPacket& packet, + uint8_t* adaptation_field_flags) { + uint8_t parsed_adaptation_field_length; + EXPECT_TRUE(header_reader->ReadU8(&parsed_adaptation_field_length)); + if (parsed_adaptation_field_length > 0u) { + const int initial_remaining = header_reader->remaining(); + uint8_t parsed_adaptation_field_flags; + EXPECT_TRUE(header_reader->ReadU8(&parsed_adaptation_field_flags)); + if (!adaptation_field_flags) { + EXPECT_EQ(0x0u, parsed_adaptation_field_flags); + } else { + *adaptation_field_flags = parsed_adaptation_field_flags; + if (parsed_adaptation_field_flags & ts::kPcrFlag) { + uint8_t parsed_pcr_bytes[6]; + EXPECT_TRUE(header_reader->ReadBytes(parsed_pcr_bytes, + sizeof(parsed_pcr_bytes))); + parsed_pcr_ = ParseProgramClockReference(parsed_pcr_bytes); + } + } + size_t remaining_stuffing_length = + parsed_adaptation_field_length - + static_cast(initial_remaining - header_reader->remaining()); + while (remaining_stuffing_length > 0u && header_reader->remaining() > 0) { + // Adaptation field stuffing byte in header_reader. + uint8_t parsed_stuffing_byte; + EXPECT_TRUE(header_reader->ReadU8(&parsed_stuffing_byte)); + EXPECT_EQ(0xFFu, parsed_stuffing_byte); + --remaining_stuffing_length; + } + if (packet.payload().empty()) { + // Adaptation field stuffing bytes in packet.filler(). + EXPECT_EQ(remaining_stuffing_length, packet.filler().size()); + EXPECT_EQ(0xFFu, packet.filler().value()); + } else { + EXPECT_EQ(0u, remaining_stuffing_length); + } + } + } + + void CheckTransportStreamProgramAssociationTablePacket( + const WiFiDisplayTransportStreamPacket& packet) { + static const uint8_t kProgramAssicationTable[4u + 13u] = { + // Pointer: + 0u, // Pointer field + // Table header: + 0x00u, // Table ID (PAT) + 0x80u | // Section syntax indicator (0b1 for PAT) + 0x00u | // Private bit (0b0 for PAT) + 0x30u | // Reserved bits (0b11) + 0x00u | // Section length unused bits (0b00) + 0u, // Section length (10 bits) + 13u, // + // Table syntax: + 0x00u, // Table ID extension (transport stream ID) + 0x01u, // + 0xC0u | // Reserved bits (0b11) + 0x00u | // Version (0b00000) + 0x01u, // Current indicator (0b1) + 0u, // Section number + 0u, // Last section number + // Program association table specific data: + 0x00u, // Program number + 0x01u, // + 0xE0 | // Reserved bits (0b111) + 0x01u, // Program map packet ID (13 bits) + 0x00, // + // CRC: + 0xE8u, + 0xF9u, 0x5Eu, 0x7Du}; + + base::BigEndianReader header_reader( + reinterpret_cast(packet.header().begin()), + packet.header().size()); + + CheckTransportStreamPacketHeader( + &header_reader, true, widi::kProgramAssociationTablePacketId, nullptr, + continuity_.program_assication_table++); + + EXPECT_EQ(PacketPart(kProgramAssicationTable), + PacketPart(packet.header().end() - header_reader.remaining(), + static_cast(header_reader.remaining()))); + EXPECT_TRUE(header_reader.Skip(header_reader.remaining())); + + EXPECT_EQ(0, header_reader.remaining()); + EXPECT_EQ(0u, packet.payload().size()); + } + + void CheckTransportStreamProgramMapTablePacket( + const WiFiDisplayTransportStreamPacket& packet, + const PacketPart& program_map_table) { + base::BigEndianReader header_reader( + reinterpret_cast(packet.header().begin()), + packet.header().size()); + + CheckTransportStreamPacketHeader(&header_reader, true, + widi::kProgramMapTablePacketId, nullptr, + continuity_.program_map_table++); + + EXPECT_EQ(program_map_table, + PacketPart(packet.header().end() - header_reader.remaining(), + static_cast(header_reader.remaining()))); + EXPECT_TRUE(header_reader.Skip(header_reader.remaining())); + + EXPECT_EQ(0, header_reader.remaining()); + EXPECT_EQ(0u, packet.payload().size()); + } + + void CheckTransportStreamProgramClockReferencePacket( + const WiFiDisplayTransportStreamPacket& packet) { + base::BigEndianReader header_reader( + reinterpret_cast(packet.header().begin()), + packet.header().size()); + + bool parsed_adaptation_field_flag; + CheckTransportStreamPacketHeader( + &header_reader, true, widi::kProgramClockReferencePacketId, + &parsed_adaptation_field_flag, continuity_.program_clock_reference++); + EXPECT_TRUE(parsed_adaptation_field_flag); + + uint8_t parsed_adaptation_field_flags; + CheckTransportStreamAdaptationField(&header_reader, packet, + &parsed_adaptation_field_flags); + EXPECT_EQ(ts::kPcrFlag, parsed_adaptation_field_flags); + + EXPECT_EQ(0, header_reader.remaining()); + EXPECT_EQ(0u, packet.payload().size()); + } + + void CheckTransportStreamElementaryStreamPacket( + const WiFiDisplayTransportStreamPacket& packet, + const WiFiDisplayElementaryStreamPacket& elementary_stream_packet, + unsigned stream_index, + unsigned expected_packet_id, + bool expected_random_access, + const uint8_t** unit_data_pos) { + const bool first_transport_stream_packet_for_current_unit = + packet.payload().begin() == unit_.data(); + const bool last_transport_stream_packet_for_current_unit = + packet.payload().end() == unit_.data() + unit_.size(); + base::BigEndianReader header_reader( + reinterpret_cast(packet.header().begin()), + packet.header().size()); + + bool parsed_adaptation_field_flag; + CheckTransportStreamPacketHeader( + &header_reader, first_transport_stream_packet_for_current_unit, + expected_packet_id, &parsed_adaptation_field_flag, + continuity_.elementary_streams[stream_index]++); + + if (first_transport_stream_packet_for_current_unit) { + // Random access can only be signified by adaptation field. + if (expected_random_access) + EXPECT_TRUE(parsed_adaptation_field_flag); + // If there is no need for padding nor for a random access indicator, + // then there is no need for an adaptation field, either. + if (!last_transport_stream_packet_for_current_unit && + !expected_random_access) { + EXPECT_FALSE(parsed_adaptation_field_flag); + } + if (parsed_adaptation_field_flag) { + uint8_t parsed_adaptation_field_flags; + CheckTransportStreamAdaptationField(&header_reader, packet, + &parsed_adaptation_field_flags); + EXPECT_EQ(expected_random_access ? ts::kRandomAccessFlag : 0u, + parsed_adaptation_field_flags); + } + + // Elementary stream header. + PacketPart parsed_elementary_stream_packet_header( + packet.header().end() - header_reader.remaining(), + std::min(elementary_stream_packet.header().size(), + static_cast(header_reader.remaining()))); + EXPECT_EQ(elementary_stream_packet.header(), + parsed_elementary_stream_packet_header); + EXPECT_TRUE( + header_reader.Skip(parsed_elementary_stream_packet_header.size())); + + // Elementary stream unit header. + PacketPart parsed_unit_header( + packet.header().end() - header_reader.remaining(), + std::min(elementary_stream_packet.unit_header().size(), + static_cast(header_reader.remaining()))); + EXPECT_EQ(elementary_stream_packet.unit_header(), parsed_unit_header); + EXPECT_TRUE(header_reader.Skip(parsed_unit_header.size())); + + // Time stamps. + if (parsed_elementary_stream_packet_header.size() >= 19u) { + uint64_t parsed_dts = ParseTimeStamp( + &parsed_elementary_stream_packet_header.begin()[14], 0x1u); + // Check that + // 0 <= 300 * parsed_dts - parsed_pcr_value <= + // kProgramClockReferenceSecond + // where + // parsed_pcr_value = 300 * parsed_pcr_.base + parsed_pcr_.extension + // but allow parsed_pcr_.base and parsed_dts to wrap around in 33 bits. + EXPECT_NE(ProgramClockReference::kInvalidBase, parsed_pcr_.base); + EXPECT_LE( + 300u * ((parsed_dts - parsed_pcr_.base) & ts::kTimeStampMask) - + parsed_pcr_.extension, + ts::kProgramClockReferenceSecond) + << " DTS must be not smaller than PCR!"; + } + } else { + // If there is no need for padding, then there is no need for + // an adaptation field, either. + if (!last_transport_stream_packet_for_current_unit) + EXPECT_FALSE(parsed_adaptation_field_flag); + if (parsed_adaptation_field_flag) { + CheckTransportStreamAdaptationField(&header_reader, packet, nullptr); + } + } + EXPECT_EQ(0, header_reader.remaining()); + + // Transport stream packet payload. + EXPECT_EQ(*unit_data_pos, packet.payload().begin()); + if (*unit_data_pos == packet.payload().begin()) + *unit_data_pos += packet.payload().size(); + + // Transport stream packet filler. + EXPECT_EQ(0u, packet.filler().size()); + } + enum { kVideoOnlyUnitSize = 0x8000u }; // Not exact. Be on the safe side. const std::vector unit_; const base::TimeTicks now_; const base::TimeTicks dts_; const base::TimeTicks pts_; + + struct { + size_t program_assication_table; + size_t program_map_table; + size_t program_clock_reference; + size_t elementary_streams[3]; + } continuity_ = {0u, 0u, 0u, {0u, 0u, 0u}}; + ProgramClockReference parsed_pcr_ = {ProgramClockReference::kInvalidBase, 0u}; }; TEST_P(WiFiDisplayElementaryStreamUnitPacketizationTest, @@ -152,6 +543,163 @@ TEST_P(WiFiDisplayElementaryStreamUnitPacketizationTest, } } +TEST_P(WiFiDisplayElementaryStreamUnitPacketizationTest, + EncodeToTransportStreamPackets) { + enum { kStreamCount = 3u }; + static const bool kBoolValues[] = {false, true}; + static const unsigned kPacketIds[kStreamCount] = { + widi::kVideoStreamPacketId, widi::kFirstAudioStreamPacketId + 0u, + widi::kFirstAudioStreamPacketId + 1u}; + static const uint8_t kProgramMapTable[4u + 42u] = { + // Pointer: + 0u, // Pointer field + // Table header: + 0x02u, // Table ID (PMT) + 0x80u | // Section syntax indicator (0b1 for PMT) + 0x00u | // Private bit (0b0 for PMT) + 0x30u | // Reserved bits (0b11) + 0x00u | // Section length unused bits (0b00) + 0u, // Section length (10 bits) + 42u, // + // Table syntax: + 0x00u, // Table ID extension (program number) + 0x01u, // + 0xC0u | // Reserved bits (0b11) + 0x00u | // Version (0b00000) + 0x01u, // Current indicator (0b1) + 0u, // Section number + 0u, // Last section number + // Program map table specific data: + 0xE0u | // Reserved bits (0b111) + 0x10u, // Program clock reference packet ID (13 bits) + 0x00u, // + 0xF0u | // Reserved bits (0b11) + 0x00u | // Program info length unused bits + 0u, // Program info length (10 bits) + 0u, // + // Elementary stream specific data: + 0x1Bu, // Stream type (H.264 in a packetized stream) + 0xE0u | // Reserved bits (0b111) + 0x10u, // Elementary packet ID (13 bits) + 0x11u, // + 0xF0u | // Reserved bits (0b1111) + 0x00u | // Elementary stream info length unused bits + 0u, // Elementary stream info length (10 bits) + 10u, // + 0x28u, // AVC video descriptor tag + 4u, // Descriptor length + 0xA5u, + 0xF5u, 0xBDu, 0xBFu, + 0x2Au, // AVC timing and HRD descriptor tag + 2u, // Descriptor length + 0x7Eu, 0x1Fu, + // Elementary stream specific data: + 0x83u, // Stream type (lossless audio in a packetized stream) + 0xE0u | // Reserved bits (0b111) + 0x11u, // Elementary packet ID (13 bits) + 0x00u, // + 0xF0u | // Reserved bits (0b1111) + 0x00u | // Elementary stream info length unused bits + 0u, // Elementary stream info length (10 bits) + 4u, // + 0x83u, // LPCM audio stream descriptor tag + 2u, // Descriptor length + 0x26u, + 0x2Fu, + // Elementary stream specific data: + 0x0Fu, // Stream type (AAC in a packetized stream) + 0xE0u | // Reserved bits (0b111) + 0x11u, // Elementary packet ID (13 bits) + 0x01u, // + 0xF0u | // Reserved bits (0b1111) + 0x00u | // Elementary stream info length unused bits + 0u, // Elementary stream info length (10 bits) + 0u, // + // CRC: + 0x4Fu, + 0x63u, 0xABu, 0x6Eu}; + static const uint8_t kStreamIds[] = { + WiFiDisplayElementaryStreamPacketizer::kFirstVideoStreamId, + WiFiDisplayElementaryStreamPacketizer::kPrivateStream1Id, + WiFiDisplayElementaryStreamPacketizer::kFirstAudioStreamId}; + + using ESDescriptor = WiFiDisplayElementaryStreamDescriptor; + std::vector lpcm_descriptors; + lpcm_descriptors.emplace_back(ESDescriptor::LPCMAudioStream::Create( + ESDescriptor::LPCMAudioStream::SAMPLING_FREQUENCY_44_1K, + ESDescriptor::LPCMAudioStream::BITS_PER_SAMPLE_16, false, + ESDescriptor::LPCMAudioStream::NUMBER_OF_CHANNELS_STEREO)); + std::vector video_desciptors; + video_desciptors.emplace_back(ESDescriptor::AVCVideo::Create( + 0xA5u, true, true, true, 0x15u, 0xBDu, true)); + video_desciptors.emplace_back(ESDescriptor::AVCTimingAndHRD::Create()); + std::vector stream_infos; + stream_infos.emplace_back(WiFiDisplayElementaryStreamInfo::VIDEO_H264, + std::move(video_desciptors)); + stream_infos.emplace_back(WiFiDisplayElementaryStreamInfo::AUDIO_LPCM, + std::move(lpcm_descriptors)); + stream_infos.emplace_back(WiFiDisplayElementaryStreamInfo::AUDIO_AAC); + WiFiDisplayElementaryStreamPacketizer elementary_stream_packetizer; + FakeTransportStreamPacketizer packetizer( + base::TimeDelta::FromMilliseconds(200), std::move(stream_infos)); + + size_t packet_index = 0u; + for (unsigned stream_index = 0; stream_index < kStreamCount; ++stream_index) { + const uint8_t* unit_header_data = nullptr; + size_t unit_header_size = 0u; + if (stream_index > 0u) { // Audio stream. + if (unit_.size() >= kVideoOnlyUnitSize) + continue; + if (stream_index == 1u) { // LPCM + unit_header_data = reinterpret_cast("\xA0\x06\x00\x09"); + unit_header_size = 4u; + } + } + for (const bool random_access : kBoolValues) { + EXPECT_TRUE(packetizer.EncodeElementaryStreamUnit( + stream_index, unit_.data(), unit_.size(), random_access, pts_, dts_, + true)); + auto normalized_pts = pts_; + auto normalized_dts = dts_; + packetizer.NormalizeUnitTimeStamps(&normalized_pts, &normalized_dts); + WiFiDisplayElementaryStreamPacket elementary_stream_packet = + elementary_stream_packetizer.EncodeElementaryStreamUnit( + kStreamIds[stream_index], unit_header_data, unit_header_size, + unit_.data(), unit_.size(), normalized_pts, normalized_dts); + + const uint8_t* unit_data_pos = unit_.data(); + for (const auto& packet : packetizer.FetchPackets()) { + switch (ParseTransportStreamPacketId(packet)) { + case widi::kProgramAssociationTablePacketId: + if (packet_index < 4u) + EXPECT_EQ(0u, packet_index); + CheckTransportStreamProgramAssociationTablePacket(packet); + break; + case widi::kProgramMapTablePacketId: + if (packet_index < 4u) + EXPECT_EQ(1u, packet_index); + CheckTransportStreamProgramMapTablePacket( + packet, PacketPart(kProgramMapTable)); + break; + case widi::kProgramClockReferencePacketId: + if (packet_index < 4u) + EXPECT_EQ(2u, packet_index); + CheckTransportStreamProgramClockReferencePacket(packet); + break; + default: + if (packet_index < 4u) + EXPECT_EQ(3u, packet_index); + CheckTransportStreamElementaryStreamPacket( + packet, elementary_stream_packet, stream_index, + kPacketIds[stream_index], random_access, &unit_data_pos); + } + ++packet_index; + } + EXPECT_EQ(unit_.data() + unit_.size(), unit_data_pos); + } + } +} + INSTANTIATE_TEST_CASE_P( WiFiDisplayElementaryStreamUnitPacketizationTests, WiFiDisplayElementaryStreamUnitPacketizationTest, diff --git a/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.cc b/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.cc new file mode 100644 index 0000000..eee0a52 --- /dev/null +++ b/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.cc @@ -0,0 +1,727 @@ +// 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_transport_stream_packetizer.h" + +#include +#include +#include + +#include "base/logging.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" + +namespace extensions { +namespace { + +uint32_t crc32(uint32_t crc, const uint8_t* data, size_t len) { + static const uint32_t table[256] = { + 0x00000000, 0xb71dc104, 0x6e3b8209, 0xd926430d, 0xdc760413, 0x6b6bc517, + 0xb24d861a, 0x0550471e, 0xb8ed0826, 0x0ff0c922, 0xd6d68a2f, 0x61cb4b2b, + 0x649b0c35, 0xd386cd31, 0x0aa08e3c, 0xbdbd4f38, 0x70db114c, 0xc7c6d048, + 0x1ee09345, 0xa9fd5241, 0xacad155f, 0x1bb0d45b, 0xc2969756, 0x758b5652, + 0xc836196a, 0x7f2bd86e, 0xa60d9b63, 0x11105a67, 0x14401d79, 0xa35ddc7d, + 0x7a7b9f70, 0xcd665e74, 0xe0b62398, 0x57abe29c, 0x8e8da191, 0x39906095, + 0x3cc0278b, 0x8bdde68f, 0x52fba582, 0xe5e66486, 0x585b2bbe, 0xef46eaba, + 0x3660a9b7, 0x817d68b3, 0x842d2fad, 0x3330eea9, 0xea16ada4, 0x5d0b6ca0, + 0x906d32d4, 0x2770f3d0, 0xfe56b0dd, 0x494b71d9, 0x4c1b36c7, 0xfb06f7c3, + 0x2220b4ce, 0x953d75ca, 0x28803af2, 0x9f9dfbf6, 0x46bbb8fb, 0xf1a679ff, + 0xf4f63ee1, 0x43ebffe5, 0x9acdbce8, 0x2dd07dec, 0x77708634, 0xc06d4730, + 0x194b043d, 0xae56c539, 0xab068227, 0x1c1b4323, 0xc53d002e, 0x7220c12a, + 0xcf9d8e12, 0x78804f16, 0xa1a60c1b, 0x16bbcd1f, 0x13eb8a01, 0xa4f64b05, + 0x7dd00808, 0xcacdc90c, 0x07ab9778, 0xb0b6567c, 0x69901571, 0xde8dd475, + 0xdbdd936b, 0x6cc0526f, 0xb5e61162, 0x02fbd066, 0xbf469f5e, 0x085b5e5a, + 0xd17d1d57, 0x6660dc53, 0x63309b4d, 0xd42d5a49, 0x0d0b1944, 0xba16d840, + 0x97c6a5ac, 0x20db64a8, 0xf9fd27a5, 0x4ee0e6a1, 0x4bb0a1bf, 0xfcad60bb, + 0x258b23b6, 0x9296e2b2, 0x2f2bad8a, 0x98366c8e, 0x41102f83, 0xf60dee87, + 0xf35da999, 0x4440689d, 0x9d662b90, 0x2a7bea94, 0xe71db4e0, 0x500075e4, + 0x892636e9, 0x3e3bf7ed, 0x3b6bb0f3, 0x8c7671f7, 0x555032fa, 0xe24df3fe, + 0x5ff0bcc6, 0xe8ed7dc2, 0x31cb3ecf, 0x86d6ffcb, 0x8386b8d5, 0x349b79d1, + 0xedbd3adc, 0x5aa0fbd8, 0xeee00c69, 0x59fdcd6d, 0x80db8e60, 0x37c64f64, + 0x3296087a, 0x858bc97e, 0x5cad8a73, 0xebb04b77, 0x560d044f, 0xe110c54b, + 0x38368646, 0x8f2b4742, 0x8a7b005c, 0x3d66c158, 0xe4408255, 0x535d4351, + 0x9e3b1d25, 0x2926dc21, 0xf0009f2c, 0x471d5e28, 0x424d1936, 0xf550d832, + 0x2c769b3f, 0x9b6b5a3b, 0x26d61503, 0x91cbd407, 0x48ed970a, 0xfff0560e, + 0xfaa01110, 0x4dbdd014, 0x949b9319, 0x2386521d, 0x0e562ff1, 0xb94beef5, + 0x606dadf8, 0xd7706cfc, 0xd2202be2, 0x653deae6, 0xbc1ba9eb, 0x0b0668ef, + 0xb6bb27d7, 0x01a6e6d3, 0xd880a5de, 0x6f9d64da, 0x6acd23c4, 0xddd0e2c0, + 0x04f6a1cd, 0xb3eb60c9, 0x7e8d3ebd, 0xc990ffb9, 0x10b6bcb4, 0xa7ab7db0, + 0xa2fb3aae, 0x15e6fbaa, 0xccc0b8a7, 0x7bdd79a3, 0xc660369b, 0x717df79f, + 0xa85bb492, 0x1f467596, 0x1a163288, 0xad0bf38c, 0x742db081, 0xc3307185, + 0x99908a5d, 0x2e8d4b59, 0xf7ab0854, 0x40b6c950, 0x45e68e4e, 0xf2fb4f4a, + 0x2bdd0c47, 0x9cc0cd43, 0x217d827b, 0x9660437f, 0x4f460072, 0xf85bc176, + 0xfd0b8668, 0x4a16476c, 0x93300461, 0x242dc565, 0xe94b9b11, 0x5e565a15, + 0x87701918, 0x306dd81c, 0x353d9f02, 0x82205e06, 0x5b061d0b, 0xec1bdc0f, + 0x51a69337, 0xe6bb5233, 0x3f9d113e, 0x8880d03a, 0x8dd09724, 0x3acd5620, + 0xe3eb152d, 0x54f6d429, 0x7926a9c5, 0xce3b68c1, 0x171d2bcc, 0xa000eac8, + 0xa550add6, 0x124d6cd2, 0xcb6b2fdf, 0x7c76eedb, 0xc1cba1e3, 0x76d660e7, + 0xaff023ea, 0x18ede2ee, 0x1dbda5f0, 0xaaa064f4, 0x738627f9, 0xc49be6fd, + 0x09fdb889, 0xbee0798d, 0x67c63a80, 0xd0dbfb84, 0xd58bbc9a, 0x62967d9e, + 0xbbb03e93, 0x0cadff97, 0xb110b0af, 0x060d71ab, 0xdf2b32a6, 0x6836f3a2, + 0x6d66b4bc, 0xda7b75b8, 0x035d36b5, 0xb440f7b1}; + for (; len; ++data, --len) + crc = (crc >> 8) ^ table[(crc & 0xFFu) ^ *data]; + return crc; +} + +// Code and parameters related to the Program Specific Information (PSI) +// specification. +namespace psi { + +const uint8_t kProgramAssociationTableId = 0x00u; +const uint8_t kProgramMapTableId = 0x02u; + +const uint16_t kFirstProgramNumber = 0x0001u; + +const size_t kCrcSize = 4u; +const size_t kProgramMapTableElementaryStreamEntryBaseSize = 5u; +const size_t kTableHeaderSize = 3u; + +size_t FillInTablePointer(uint8_t* dst, size_t min_size) { + size_t i = 1u; + if (i < min_size) { + std::memset(&dst[i], 0xFF, min_size - i); // Pointer filler bytes + i = min_size; + } + dst[0] = i - 1u; // Pointer field + return i; +} + +size_t FillInTableHeaderAndCrc(uint8_t* header_dst, + uint8_t* crc_dst, + uint8_t table_id) { + size_t i; + const uint8_t* const header_end = header_dst + kTableHeaderSize; + const uint8_t* const crc_end = crc_dst + kCrcSize; + const size_t section_length = static_cast(crc_end - header_end); + DCHECK_LE(section_length, 1021u); + + // Table header. + i = 0u; + header_dst[i++] = table_id; + header_dst[i++] = + (0x1u << 7) | // Section syntax indicator (1 for PAT and PMT) + (0x0u << 6) | // Private bit (0 for PAT and PMT) + (0x3u << 4) | // Reserved bits (both bits on) + (0x0u << 2) | // Section length unused bits (both bits off) + ((section_length >> 8) & 0x03u); // Section length (10 bits) + header_dst[i++] = section_length & 0xFFu; // + DCHECK_EQ(kTableHeaderSize, i); + + // CRC. + uint32_t crc = + crc32(0xFFFFFFFFu, header_dst, static_cast(crc_dst - header_dst)); + i = 0u; + // Avoid swapping the crc by reversing write order. + crc_dst[i++] = crc & 0xFFu; + crc_dst[i++] = (crc >> 8) & 0xFFu; + crc_dst[i++] = (crc >> 16) & 0xFFu; + crc_dst[i++] = (crc >> 24) & 0xFFu; + DCHECK_EQ(kCrcSize, i); + return i; +} + +size_t FillInTableSyntax(uint8_t* dst, + uint16_t table_id_extension, + uint8_t version_number) { + size_t i = 0u; + dst[i++] = table_id_extension >> 8; + dst[i++] = table_id_extension & 0xFFu; + dst[i++] = (0x3u << 6) | // Reserved bits (both bits on) + ((version_number & 0x1Fu) << 1) | // Version number (5 bits) + (0x1u << 0); // Current indicator + dst[i++] = 0u; // Section number + dst[i++] = 0u; // Last section number + return i; +} + +size_t FillInProgramAssociationTableEntry(uint8_t* dst, + uint16_t program_number, + unsigned pmt_packet_id) { + size_t i = 0u; + dst[i++] = program_number >> 8; + dst[i++] = program_number & 0xFFu; + dst[i++] = (0x7u << 5) | // Reserved bits (all 3 bits on) + ((pmt_packet_id >> 8) & 0x1Fu); // Packet identifier (13 bits) + dst[i++] = pmt_packet_id & 0xFFu; // + return i; +} + +size_t FillInProgramMapTableData(uint8_t* dst, unsigned pcr_packet_id) { + size_t i = 0u; + dst[i++] = (0x7u << 5) | // Reserved bits (all 3 bits on) + ((pcr_packet_id >> 8) & 0x1Fu); // Packet identifier (13 bits) + dst[i++] = pcr_packet_id & 0xFFu; // + dst[i++] = (0xFu << 4) | // Reserved bits (all 4 bits on) + (0x0u << 2) | // Program info length unused bits (both bits off) + ((0u >> 8) & 0x3u); // Program info length (10 bits) + dst[i++] = 0u & 0xFFu; // + // No program descriptors + return i; +} + +size_t CalculateElementaryStreamInfoLength( + const std::vector& es_descriptors) { + size_t es_info_length = 0u; + for (const auto& es_descriptor : es_descriptors) + es_info_length += es_descriptor.size(); + DCHECK_EQ(0u, es_info_length >> 8); + return es_info_length; +} + +size_t FillInProgramMapTableElementaryStreamEntry( + uint8_t* dst, + uint8_t stream_type, + unsigned es_packet_id, + size_t es_info_length, + const std::vector& es_descriptors) { + DCHECK_EQ(CalculateElementaryStreamInfoLength(es_descriptors), + es_info_length); + DCHECK_EQ(0u, es_info_length >> 10); + size_t i = 0u; + dst[i++] = stream_type; + dst[i++] = (0x7u << 5) | // Reserved bits (all 3 bits on) + ((es_packet_id >> 8) & 0x1Fu); // Packet identifier (13 bits) + dst[i++] = es_packet_id & 0xFFu; // + dst[i++] = (0xFu << 4) | // Reserved bits (all 4 bits on) + (0x0u << 2) | // ES info length unused bits (both bits off) + ((es_info_length >> 8) & 0x3u); // ES info length (10 bits) + dst[i++] = es_info_length & 0xFFu; // + for (const auto& es_descriptor : es_descriptors) { + std::memcpy(&dst[i], es_descriptor.data(), es_descriptor.size()); + i += es_descriptor.size(); + } + return i; +} + +} // namespace psi + +// Code and parameters related to the MPEG Transport Stream (MPEG-TS) +// specification. +namespace ts { + +const size_t kAdaptationFieldLengthSize = 1u; +const size_t kAdaptationFieldFlagsSize = 1u; +const size_t kPacketHeaderSize = 4u; +const size_t kProgramClockReferenceSize = 6u; + +size_t FillInProgramClockReference(uint8_t* dst, const base::TimeTicks& pcr) { + // Convert to the number of 27 MHz ticks since some epoch. + const uint64_t us = + static_cast((pcr - base::TimeTicks()).InMicroseconds()); + const uint64_t n = 27u * us; + const uint64_t base = n / 300u; // 90 kHz + const uint64_t extension = n % 300u; + + size_t i = 0u; + dst[i++] = (base >> 25) & 0xFFu; // Base (33 bits) + dst[i++] = (base >> 17) & 0xFFu; // + dst[i++] = (base >> 9) & 0xFFu; // + dst[i++] = (base >> 1) & 0xFFu; // + dst[i++] = ((base & 0x01u) << 7) | // + (0x3Fu << 1) | // Reserved bits (all 6 bits on) + ((extension >> 8) & 0x1u); // Extension (9 bits) + dst[i++] = extension & 0xFFu; // + DCHECK_EQ(kProgramClockReferenceSize, i); + return i; +} + +size_t FillInAdaptationFieldLengthFromSize(uint8_t* dst, size_t size) { + size_t i = 0u; + dst[i++] = size - 1u; + DCHECK_EQ(kAdaptationFieldLengthSize, i); + return i; +} + +size_t FillInAdaptationFieldFlags(uint8_t* dst, + bool random_access_indicator, + const base::TimeTicks& pcr) { + size_t i = 0u; + dst[i++] = (0x0u << 7) | // Discontinuity indicator + (random_access_indicator << 6) | // Random access indicator + (0x0u << 5) | // Elementary stream priority indicator + ((!pcr.is_null()) << 4) | // PCR flag + (0x0u << 3) | // OPCR flag + (0x0u << 2) | // Splicing point flag + (0x0u << 1) | // Transport private data flag + (0x0u << 0); // Adaptation field extension flag + DCHECK_EQ(kAdaptationFieldFlagsSize, i); + return i; +} + +size_t FillInAdaptationField(uint8_t* dst, + bool random_access_indicator, + size_t min_size) { + // Reserve space for a length. + size_t i = kAdaptationFieldLengthSize; + + if (random_access_indicator || i < min_size) { + const base::TimeTicks pcr; + i += FillInAdaptationFieldFlags(&dst[i], random_access_indicator, pcr); + + if (i < min_size) { + std::memset(&dst[i], 0xFF, min_size - i); // Stuffing bytes. + i = min_size; + } + } + + // Fill in a length now that the size is known. + FillInAdaptationFieldLengthFromSize(dst, i); + + return i; +} + +size_t FillInPacketHeader(uint8_t* dst, + bool payload_unit_start_indicator, + unsigned packet_id, + bool adaptation_field_flag, + unsigned continuity_counter) { + size_t i = 0u; + dst[i++] = 0x47; // Sync byte ('G') + dst[i++] = + (0x0u << 7) | // Transport error indicator + (payload_unit_start_indicator << 6) | // Payload unit start indicator + (0x0 << 5) | // Transport priority + ((packet_id >> 8) & 0x1Fu); // Packet identifier (13 bits) + dst[i++] = packet_id & 0xFFu; // + dst[i++] = (0x0u << 6) | // Scrambling control (0b00 for not) + (adaptation_field_flag << 5) | // Adaptation field flag + (0x1u << 4) | // Payload flag + (continuity_counter & 0xFu); // Continuity counter + DCHECK_EQ(kPacketHeaderSize, i); + return i; +} + +} // namespace ts + +// Code and parameters related to the WiFi Display specification. +namespace widi { + +const size_t kUnitHeaderMaxSize = 4u; + +// Maximum interval between meta information which includes: +// * Program Association Table (PAT) +// * Program Map Table (PMT) +// * Program Clock Reference (PCR) +const int kMaxMillisecondsBetweenMetaInformation = 100u; + +const unsigned kProgramAssociationTablePacketId = 0x0000u; +const unsigned kProgramMapTablePacketId = 0x0100u; +const unsigned kProgramClockReferencePacketId = 0x1000u; +const unsigned kVideoStreamPacketId = 0x1011u; +const unsigned kFirstAudioStreamPacketId = 0x1100u; + +size_t FillInUnitHeader(uint8_t* dst, + const WiFiDisplayElementaryStreamInfo& stream_info) { + size_t i = 0u; + + if (stream_info.type() == WiFiDisplayElementaryStreamInfo::AUDIO_LPCM) { + // Convert an LPCM audio stream descriptor to an LPCM unit header. + if (const auto* lpcm_descriptor = + stream_info.FindDescriptor< + WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream>()) { + dst[i++] = 0xA0u; // Sub stream ID (0th sub stream) + dst[i++] = WiFiDisplayTransportStreamPacketizer::LPCM::kFramesPerUnit; + dst[i++] = ((0x00u << 1) | // Reserved (all 7 bits off) + (lpcm_descriptor->emphasis_flag() << 0)); + dst[i++] = ((lpcm_descriptor->bits_per_sample() << 6) | + (lpcm_descriptor->sampling_frequency() << 3) | + (lpcm_descriptor->number_of_channels() << 0)); + } + } + + DCHECK_LE(i, kUnitHeaderMaxSize); + return i; +} + +} // namespace widi + +} // namespace + +WiFiDisplayTransportStreamPacket::WiFiDisplayTransportStreamPacket( + const uint8_t* header_data, + size_t header_size) + : header_(header_data, header_size), + payload_(header_.end(), 0u), + filler_(kPacketSize - header_size) {} + +WiFiDisplayTransportStreamPacket::WiFiDisplayTransportStreamPacket( + const uint8_t* header_data, + size_t header_size, + const uint8_t* payload_data) + : header_(header_data, header_size), + payload_(payload_data, kPacketSize - header_size), + filler_(0u) {} + +struct WiFiDisplayTransportStreamPacketizer::ElementaryStreamState { + ElementaryStreamState(WiFiDisplayElementaryStreamInfo info, + uint16_t packet_id, + uint8_t stream_id) + : info(std::move(info)), + info_length( + psi::CalculateElementaryStreamInfoLength(this->info.descriptors())), + packet_id(packet_id), + stream_id(stream_id) {} + + WiFiDisplayElementaryStreamInfo info; + uint8_t info_length; + struct { + uint8_t continuity = 0u; + } counters; + uint16_t packet_id; + uint8_t stream_id; +}; + +WiFiDisplayTransportStreamPacketizer::WiFiDisplayTransportStreamPacketizer( + const base::TimeDelta& delay_for_unit_time_stamps, + std::vector stream_infos) + : delay_for_unit_time_stamps_(delay_for_unit_time_stamps) { + std::memset(&counters_, 0x00, sizeof(counters_)); + if (!stream_infos.empty()) + CHECK(SetElementaryStreams(std::move(stream_infos))); +} + +WiFiDisplayTransportStreamPacketizer::~WiFiDisplayTransportStreamPacketizer() {} + +bool WiFiDisplayTransportStreamPacketizer::EncodeElementaryStreamUnit( + unsigned stream_index, + const uint8_t* unit_data, + size_t unit_size, + bool random_access, + base::TimeTicks pts, + base::TimeTicks dts, + bool flush) { + DCHECK(CalledOnValidThread()); + DCHECK_LT(stream_index, stream_states_.size()); + ElementaryStreamState& stream_state = stream_states_[stream_index]; + + if (program_clock_reference_.is_null() || + base::TimeTicks::Now() - program_clock_reference_ > + base::TimeDelta::FromMilliseconds( + widi::kMaxMillisecondsBetweenMetaInformation) / + 2) { + if (!EncodeMetaInformation(false)) + return false; + } + + uint8_t unit_header_data[widi::kUnitHeaderMaxSize]; + const size_t unit_header_size = + widi::FillInUnitHeader(unit_header_data, stream_state.info); + + UpdateDelayForUnitTimeStamps(pts, dts); + NormalizeUnitTimeStamps(&pts, &dts); + + WiFiDisplayElementaryStreamPacketizer elementary_stream_packetizer; + WiFiDisplayElementaryStreamPacket elementary_stream_packet = + elementary_stream_packetizer.EncodeElementaryStreamUnit( + stream_state.stream_id, unit_header_data, unit_header_size, unit_data, + unit_size, pts, dts); + + size_t adaptation_field_min_size = 0u; + uint8_t header_data[WiFiDisplayTransportStreamPacket::kPacketSize]; + bool is_payload_unit_end; + bool is_payload_unit_start = true; + size_t remaining_unit_size = elementary_stream_packet.unit().size(); + do { + // Fill in headers and an adaptation field: + // * Transport stream packet header + // * Transport stream adaptation field + // (only for the first and/or the last packet): + // - for the first packet to hold flags + // - for the last packet to hold padding + // * PES packet header (only for the first packet): + // - PES packet header base + // - Optional PES header base + // - Optional PES header optional fields: + // - Presentation time stamp + // - Decoding time stamp + bool adaptation_field_flag = false; + size_t header_min_size; + if (is_payload_unit_start || is_payload_unit_end) { + header_min_size = ts::kPacketHeaderSize; + if (is_payload_unit_start) { + header_min_size += elementary_stream_packet.header().size() + + elementary_stream_packet.unit_header().size(); + } + adaptation_field_min_size = + std::max( + WiFiDisplayTransportStreamPacket::kPacketSize - header_min_size, + remaining_unit_size) - + remaining_unit_size; + adaptation_field_flag = adaptation_field_min_size > 0 || + (is_payload_unit_start && random_access); + } + size_t i = 0u; + i += ts::FillInPacketHeader(&header_data[i], is_payload_unit_start, + stream_state.packet_id, adaptation_field_flag, + stream_state.counters.continuity++); + if (is_payload_unit_start) { + size_t adaptation_field_size = adaptation_field_min_size; + if (adaptation_field_flag) { + adaptation_field_size = ts::FillInAdaptationField( + &header_data[i], random_access, adaptation_field_min_size); + i += adaptation_field_size; + DCHECK_GE(adaptation_field_size, adaptation_field_min_size); + } + std::memcpy(&header_data[i], elementary_stream_packet.header().data(), + elementary_stream_packet.header().size()); + i += elementary_stream_packet.header().size(); + std::memcpy(&header_data[i], + elementary_stream_packet.unit_header().data(), + elementary_stream_packet.unit_header().size()); + i += elementary_stream_packet.unit_header().size(); + DCHECK_EQ(header_min_size + adaptation_field_size, i); + } else if (is_payload_unit_end) { + if (adaptation_field_flag) { + // Fill in an adaptation field only for padding. + i += ts::FillInAdaptationField(&header_data[i], false, + adaptation_field_min_size); + } + DCHECK_EQ(header_min_size + adaptation_field_min_size, i); + } + + // Delegate the packet. + WiFiDisplayTransportStreamPacket packet( + header_data, i, + elementary_stream_packet.unit().end() - remaining_unit_size); + DCHECK_LE(packet.payload().size(), remaining_unit_size); + remaining_unit_size -= packet.payload().size(); + if (!OnPacketizedTransportStreamPacket( + packet, flush && remaining_unit_size == 0u)) { + return false; + } + + // Prepare for the next packet. + is_payload_unit_end = + remaining_unit_size <= + WiFiDisplayTransportStreamPacket::kPacketSize - ts::kPacketHeaderSize; + is_payload_unit_start = false; + } while (remaining_unit_size > 0u); + + DCHECK_EQ(0u, remaining_unit_size); + + return true; +} + +bool WiFiDisplayTransportStreamPacketizer::EncodeMetaInformation(bool flush) { + DCHECK(CalledOnValidThread()); + + return (EncodeProgramAssociationTable(false) && + EncodeProgramMapTables(false) && EncodeProgramClockReference(flush)); +} + +bool WiFiDisplayTransportStreamPacketizer::EncodeProgramAssociationTable( + bool flush) { + DCHECK(CalledOnValidThread()); + + const uint16_t transport_stream_id = 0x0001u; + + uint8_t header_data[WiFiDisplayTransportStreamPacket::kPacketSize]; + size_t i = 0u; + + // Fill in a packet header. + i += ts::FillInPacketHeader(&header_data[i], true, + widi::kProgramAssociationTablePacketId, false, + counters_.program_association_table_continuity++); + + // Fill in a minimal table pointer. + i += psi::FillInTablePointer(&header_data[i], 0u); + + // Reserve space for a table header. + const size_t table_header_index = i; + i += psi::kTableHeaderSize; + + // Fill in a table syntax. + const uint8_t version_number = 0u; + i += psi::FillInTableSyntax(&header_data[i], transport_stream_id, + version_number); + + // Fill in program association table data. + i += psi::FillInProgramAssociationTableEntry(&header_data[i], + psi::kFirstProgramNumber, + widi::kProgramMapTablePacketId); + + // Fill in a table header and a CRC now that the table size is known. + i += psi::FillInTableHeaderAndCrc(&header_data[table_header_index], + &header_data[i], + psi::kProgramAssociationTableId); + + // Delegate the packet. + return OnPacketizedTransportStreamPacket( + WiFiDisplayTransportStreamPacket(header_data, i), flush); +} + +bool WiFiDisplayTransportStreamPacketizer::EncodeProgramClockReference( + bool flush) { + DCHECK(CalledOnValidThread()); + + program_clock_reference_ = base::TimeTicks::Now(); + + uint8_t header_data[ts::kPacketHeaderSize + ts::kAdaptationFieldLengthSize + + ts::kAdaptationFieldFlagsSize + + ts::kProgramClockReferenceSize]; + size_t i = 0u; + + // Fill in a packet header. + i += ts::FillInPacketHeader(&header_data[i], true, + widi::kProgramClockReferencePacketId, true, + counters_.program_clock_reference_continuity++); + + // Fill in an adaptation field. + i += ts::FillInAdaptationFieldLengthFromSize( + &header_data[i], WiFiDisplayTransportStreamPacket::kPacketSize - i); + i += ts::FillInAdaptationFieldFlags(&header_data[i], false, + program_clock_reference_); + i += ts::FillInProgramClockReference(&header_data[i], + program_clock_reference_); + + DCHECK_EQ(std::end(header_data), header_data + i); + + // Delegate the packet. + return OnPacketizedTransportStreamPacket( + WiFiDisplayTransportStreamPacket(header_data, i), flush); +} + +bool WiFiDisplayTransportStreamPacketizer::EncodeProgramMapTables(bool flush) { + DCHECK(CalledOnValidThread()); + DCHECK(!stream_states_.empty()); + + const uint16_t program_number = psi::kFirstProgramNumber; + + uint8_t header_data[WiFiDisplayTransportStreamPacket::kPacketSize]; + size_t i = 0u; + + // Fill in a packet header. + i += ts::FillInPacketHeader(&header_data[i], true, + widi::kProgramMapTablePacketId, false, + counters_.program_map_table_continuity++); + + // Fill in a minimal table pointer. + i += psi::FillInTablePointer(&header_data[i], 0u); + + // Reserve space for a table header. + const size_t table_header_index = i; + i += psi::kTableHeaderSize; + + // Fill in a table syntax. + i += psi::FillInTableSyntax(&header_data[i], program_number, + counters_.program_map_table_version); + + // Fill in program map table data. + i += psi::FillInProgramMapTableData(&header_data[i], + widi::kProgramClockReferencePacketId); + for (const auto& stream_state : stream_states_) { + DCHECK_LE(i + psi::kProgramMapTableElementaryStreamEntryBaseSize + + stream_state.info_length + psi::kCrcSize, + WiFiDisplayTransportStreamPacket::kPacketSize); + i += psi::FillInProgramMapTableElementaryStreamEntry( + &header_data[i], stream_state.info.type(), stream_state.packet_id, + stream_state.info_length, stream_state.info.descriptors()); + } + + // Fill in a table header and a CRC now that the table size is known. + i += psi::FillInTableHeaderAndCrc(&header_data[table_header_index], + &header_data[i], psi::kProgramMapTableId); + + // Delegate the packet. + return OnPacketizedTransportStreamPacket( + WiFiDisplayTransportStreamPacket(header_data, i), flush); +} + +void WiFiDisplayTransportStreamPacketizer::NormalizeUnitTimeStamps( + base::TimeTicks* pts, + base::TimeTicks* dts) const { + DCHECK(CalledOnValidThread()); + + // Normalize a presentation time stamp. + if (!pts || pts->is_null()) + return; + *pts += delay_for_unit_time_stamps_; + DCHECK_LE(program_clock_reference_, *pts); + + // Normalize a decoding time stamp. + if (!dts || dts->is_null()) + return; + *dts += delay_for_unit_time_stamps_; + DCHECK_LE(program_clock_reference_, *dts); + DCHECK_LE(*dts, *pts); +} + +bool WiFiDisplayTransportStreamPacketizer::SetElementaryStreams( + std::vector stream_infos) { + DCHECK(CalledOnValidThread()); + + std::vector new_stream_states; + new_stream_states.reserve(stream_infos.size()); + + uint8_t audio_stream_id = + WiFiDisplayElementaryStreamPacketizer::kFirstAudioStreamId; + uint16_t audio_stream_packet_id = widi::kFirstAudioStreamPacketId; + uint8_t private_stream_1_id = + WiFiDisplayElementaryStreamPacketizer::kPrivateStream1Id; + uint16_t video_stream_packet_id = widi::kVideoStreamPacketId; + + for (auto& stream_info : stream_infos) { + uint16_t packet_id; + uint8_t stream_id; + + switch (stream_info.type()) { + case AUDIO_AAC: + packet_id = audio_stream_packet_id++; + stream_id = audio_stream_id++; + break; + case AUDIO_AC3: + case AUDIO_LPCM: + if (private_stream_1_id != + WiFiDisplayElementaryStreamPacketizer::kPrivateStream1Id) { + return false; + } + packet_id = audio_stream_packet_id++; + stream_id = private_stream_1_id++; + break; + case VIDEO_H264: + if (video_stream_packet_id != widi::kVideoStreamPacketId) + return false; + packet_id = video_stream_packet_id++; + stream_id = WiFiDisplayElementaryStreamPacketizer::kFirstVideoStreamId; + break; + } + + new_stream_states.emplace_back(std::move(stream_info), packet_id, + stream_id); + } + + // If there are no previous states, there is no previous program map table + // to change, either. This ensures that the first encoded program map table + // has version 0. + if (!stream_states_.empty()) + ++counters_.program_map_table_version; + + stream_states_.swap(new_stream_states); + + return true; +} + +void WiFiDisplayTransportStreamPacketizer::UpdateDelayForUnitTimeStamps( + const base::TimeTicks& pts, + const base::TimeTicks& dts) { + DCHECK(CalledOnValidThread()); + + if (pts.is_null()) + return; + + const base::TimeTicks now = base::TimeTicks::Now(); + DCHECK_LE(program_clock_reference_, now); + + // Ensure that delayed time stamps are greater than or equal to now. + const base::TimeTicks ts_min = + (dts.is_null() ? pts : dts) + delay_for_unit_time_stamps_; + if (now > ts_min) { + const base::TimeDelta error = now - ts_min; + delay_for_unit_time_stamps_ += 2 * error; + } +} + +} // namespace extensions diff --git a/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h b/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h new file mode 100644 index 0000000..0cdeb6a --- /dev/null +++ b/extensions/renderer/api/display_source/wifi_display/wifi_display_transport_stream_packetizer.h @@ -0,0 +1,176 @@ +// 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_TRANSPORT_STREAM_PACKETIZER_H_ +#define EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_TRANSPORT_STREAM_PACKETIZER_H_ + +#include + +#include "base/threading/non_thread_safe.h" +#include "base/time/time.h" +#include "extensions/renderer/api/display_source/wifi_display/wifi_display_stream_packet_part.h" + +namespace extensions { + +class WiFiDisplayElementaryStreamInfo; +class WiFiDisplayElementaryStreamPacket; + +// This class represents an MPEG Transport Stream (MPEG-TS) packet containing +// WiFi Display elementary stream unit data or related meta information. +class WiFiDisplayTransportStreamPacket { + public: + enum { kPacketSize = 188u }; + + using Part = WiFiDisplayStreamPacketPart; + + // This class represents a possibly empty padding part in the end of + // a transport stream packet. The padding part consists of repeated bytes + // having the same value. + class PaddingPart { + public: + explicit PaddingPart(unsigned size) : size_(size) {} + + unsigned size() const { return size_; } + uint8_t value() const { return 0xFFu; } + + private: + const unsigned size_; + + DISALLOW_COPY_AND_ASSIGN(PaddingPart); + }; + + WiFiDisplayTransportStreamPacket(const uint8_t* header_data, + size_t header_size); + WiFiDisplayTransportStreamPacket(const uint8_t* header_data, + size_t header_size, + const uint8_t* payload_data); + + const Part& header() const { return header_; } + const Part& payload() const { return payload_; } + const PaddingPart& filler() const { return filler_; } + + private: + const Part header_; + const Part payload_; + const PaddingPart filler_; + + DISALLOW_COPY_AND_ASSIGN(WiFiDisplayTransportStreamPacket); +}; + +// The WiFi Display transport stream packetizer packetizes unit buffers to +// MPEG Transport Stream (MPEG-TS) packets containing either meta information +// or Packetized Elementary Stream (PES) packets containing unit data. +// +// Whenever a Transport Stream (TS) packet is fully created and thus ready for +// further processing, a pure virtual member function +// |OnPacketizedTransportStreamPacket| is called. +class WiFiDisplayTransportStreamPacketizer : public base::NonThreadSafe { + public: + enum ElementaryStreamType : uint8_t { + AUDIO_AAC = 0x0Fu, + AUDIO_AC3 = 0x81u, + AUDIO_LPCM = 0x83u, + VIDEO_H264 = 0x1Bu, + }; + + // Fixed coding parameters for Linear Pulse-Code Modulation (LPCM) audio + // streams. See |WiFiDisplayElementaryStreamDescriptor::LPCMAudioStream| for + // variable ones. + struct LPCM { + enum { + kFramesPerUnit = 6u, + kChannelSamplesPerFrame = 80u, + kChannelSamplesPerUnit = kChannelSamplesPerFrame * kFramesPerUnit + }; + }; + + WiFiDisplayTransportStreamPacketizer( + const base::TimeDelta& delay_for_unit_time_stamps, + std::vector stream_infos); + virtual ~WiFiDisplayTransportStreamPacketizer(); + + // Encodes one elementary stream unit buffer (such as one video frame or + // 2 * |LPCM::kChannelSamplesPerUnit| two-channel LPCM audio samples) into + // packets: + // 1) Encodes meta information into meta information packets (by calling + // |EncodeMetaInformation|) if needed. + // 2) Normalizes unit time stamps (|pts| and |dts|) so that they are never + // smaller than a program clock reference. + // 3) Encodes the elementary stream unit buffer to unit data packets. + // Returns false in the case of an error in which case the caller should stop + // encoding. + // + // In order to minimize encoding delays, |flush| should be true unless + // the caller is about to continue encoding immediately. + // + // Precondition: Elementary streams are configured either using a constructor + // or using the |SetElementaryStreams| member function. + bool EncodeElementaryStreamUnit(unsigned stream_index, + const uint8_t* unit_data, + size_t unit_size, + bool random_access, + base::TimeTicks pts, + base::TimeTicks dts, + bool flush); + + // Encodes meta information (program association table, program map table and + // program clock reference). Returns false in the case of an error in which + // case the caller should stop encoding. + // + // The |EncodeElementaryStreamUnit| member function calls this member function + // when needed, thus the caller is responsible for calling this member + // function explicitly only if the caller does silence suppression and does + // thus not encode all elementary stream units by calling + // the |EncodeElementaryStreamUnit| member function. + // + // In order to minimize encoding delays, |flush| should be true unless + // the caller is about to continue encoding immediately. + // + // Precondition: Elementary streams are configured either using a constructor + // or using the |SetElementaryStreams| member function. + bool EncodeMetaInformation(bool flush); + + bool SetElementaryStreams( + std::vector stream_infos); + + void DetachFromThread() { base::NonThreadSafe::DetachFromThread(); } + + protected: + bool EncodeProgramAssociationTable(bool flush); + bool EncodeProgramClockReference(bool flush); + bool EncodeProgramMapTables(bool flush); + + // Normalizes unit time stamps by delaying them in order to ensure that unit + // time stamps are never smaller than a program clock reference. + // Precondition: The |UpdateDelayForUnitTimeStamps| member function is called. + void NormalizeUnitTimeStamps(base::TimeTicks* pts, + base::TimeTicks* dts) const; + // Update unit time stamp delay in order to ensure that normalized unit time + // stamps are never smaller than a program clock reference. + void UpdateDelayForUnitTimeStamps(const base::TimeTicks& pts, + const base::TimeTicks& dts); + + // Called whenever a Transport Stream (TS) packet is fully created and thus + // ready for further processing. + virtual bool OnPacketizedTransportStreamPacket( + const WiFiDisplayTransportStreamPacket& transport_stream_packet, + bool flush) = 0; + + private: + struct ElementaryStreamState; + + struct { + uint8_t program_association_table_continuity; + uint8_t program_map_table_continuity; + uint8_t program_map_table_version; + uint8_t program_clock_reference_continuity; + } counters_; + base::TimeDelta delay_for_unit_time_stamps_; + base::TimeTicks program_clock_reference_; + std::vector stream_states_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_API_DISPLAY_SOURCE_WIFI_DISPLAY_WIFI_DISPLAY_TRANSPORT_STREAM_PACKETIZER_H_ -- cgit v1.1