diff --git a/packager/media/formats/mp2t/mp2t.gyp b/packager/media/formats/mp2t/mp2t.gyp index 0935d53347..137e2e122a 100644 --- a/packager/media/formats/mp2t/mp2t.gyp +++ b/packager/media/formats/mp2t/mp2t.gyp @@ -36,6 +36,8 @@ 'ts_section_pmt.h', 'ts_section_psi.cc', 'ts_section_psi.h', + 'ts_writer.cc', + 'ts_writer.h', ], 'dependencies': [ '../../base/media_base.gyp:media_base', @@ -52,6 +54,7 @@ 'es_parser_h264_unittest.cc', 'mp2t_media_parser_unittest.cc', 'pes_packet_generator_unittest.cc', + 'ts_writer_unittest.cc', ], 'dependencies': [ '../../../testing/gtest.gyp:gtest', diff --git a/packager/media/formats/mp2t/pes_packet.h b/packager/media/formats/mp2t/pes_packet.h index 05ed7fd95b..f21ecfccc1 100644 --- a/packager/media/formats/mp2t/pes_packet.h +++ b/packager/media/formats/mp2t/pes_packet.h @@ -28,9 +28,9 @@ class PesPacket { void set_stream_id(uint8_t stream_id) { stream_id_ = stream_id; } /// @return true if dts has been set. - bool has_dts() const { return dts_ < 0; } + bool has_dts() const { return dts_ >= 0; } /// @return true if pts has been set. - bool has_pts() const { return pts_ < 0; } + bool has_pts() const { return pts_ >= 0; } /// @return dts. int64_t dts() const { return dts_; } diff --git a/packager/media/formats/mp2t/ts_writer.cc b/packager/media/formats/mp2t/ts_writer.cc new file mode 100644 index 0000000000..4b32bb8391 --- /dev/null +++ b/packager/media/formats/mp2t/ts_writer.cc @@ -0,0 +1,484 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/media/formats/mp2t/ts_writer.h" + +#include + +#include "packager/base/logging.h" +#include "packager/media/base/audio_stream_info.h" +#include "packager/media/base/buffer_writer.h" +#include "packager/media/base/stream_info.h" +#include "packager/media/base/video_stream_info.h" + +namespace edash_packager { +namespace media { +namespace mp2t { + +namespace { + +enum Pid : uint8_t { + // The pid can be 13 bits long but 8 bits is sufficient for this library. + // This is the minimum PID that can be used for PMT. + kPmtPid = 0x20, + // This is arbitrary number that is not reserved by the spec. + kElementaryPid = 0x50, +}; + +// Program number is 16 bits but 8 bits is sufficient. +const uint8_t kProgramNumber = 0x01; + +const uint8_t kStreamTypeH264 = 0x1B; +const uint8_t kStreamTypeAdtsAac = 0x0F; + +// For all the pointer fields in the following PAT and PMTs, they are not really +// part of PAT or PMT but it's there so that TsPacket can point to a memory +// location that starts from pointer field. + +const uint8_t kProgramAssociationTableId = 0x00; +const uint8_t kProgramMapTableId = 0x02; + +// TODO(rkuroiwa): +// Once encryption is added, another PAT must be used for the encrypted portion +// e.g. version number set to 1. +// But this works for clear lead and for clear segments. +// Write PSI generator. +const uint8_t kPat[] = { + 0x00, // pointer field + kProgramAssociationTableId, + 0xB0, // The last 2 '00' assumes that this PAT is not very long. + 0x0D, // Length of the rest of this array. + 0x00, 0x00, // Transport stream ID is 0. + 0xC1, // version number 0, current next indicator 1. + 0x00, // section number + 0x00, // last section number + // program number -> PMT PID mapping. + 0x00, 0x01, // program number is 1. + 0xE0, // first 3 bits is reserved. + kPmtPid, + // CRC32. + 0xAB, 0xB9, 0x9E, 0x9D, +}; + +// Like PAT, with encryption different PMTs are required. +// It might make sense to add a PmtGenerator class. +const uint8_t kPmtH264[] = { + 0x00, // pointer field + kProgramMapTableId, + 0xB0, // assumes length is <= 256 bytes. + 0x12, // length of the rest of this array. + 0x00, kProgramNumber, + 0xC1, // version 0, current next indicator 1. + 0x00, // section number + 0x00, // last section number. + 0xE0, // first 3 bits reserved. + kElementaryPid, // PCR PID is the elementary streams PID. + 0xF0, // first 4 bits reserved. + 0x00, // No descriptor at this level. + kStreamTypeH264, 0xE0, kElementaryPid, // stream_type -> PID. + 0xF0, 0x00, // Es_info_length is 0. + // CRC32. + 0x56, 0x90, 0xF4, 0xEB, +}; + +const uint8_t kPmtAac[] = { + 0x00, // pointer field + 0x02, // table id must be 0x02. + 0xB0, // assumes length is <= 256 bytes. + 0x12, // length of the rest of this array. + 0x00, kProgramNumber, + 0xC1, // version 0, current next indicator 1. + 0x00, // section number + 0x00, // last section number. + 0xE0, // first 3 bits reserved. + kElementaryPid, // PCR PID is the elementary streams PID. + 0xF0, // first 4 bits reserved. + 0x00, // No descriptor at this level. + kStreamTypeAdtsAac, 0xE0, kElementaryPid, // stream_type -> PID. + 0xF0, 0x00, // Es_info_length is 0. + // CRC32. + 0xC3, 0xF0, 0xC5, 0xA9, +}; + +const bool kHasPcr = true; +const bool kPayloadUnitStartIndicator = true; + +const uint8_t kSyncByte = 0x47; +const int kPcrFieldsSize = 6; + +// This is the size of the first few fields in a TS packet, i.e. TS packet size +// without adaptation field or the payload. +const int kTsPacketHeaderSize = 4; +const int kTsPacketSize = 188; +const int kTsPacketMaximumPayloadSize = + kTsPacketSize - kTsPacketHeaderSize; + +const size_t kMaxPesPacketLengthValue = 0xFFFF; + +// Used for adaptation field padding bytes. +const uint8_t kPaddingBytes[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +}; +static_assert(arraysize(kPaddingBytes) >= kTsPacketMaximumPayloadSize, + "Padding array is not big enough."); + +// |remaining_data_size| is the amount of data that has to be written. This may +// be bigger than a TS packet size. +// |remaining_data_size| matters if it is short and requires padding. +void WriteAdaptationField(bool has_pcr, + uint64_t pcr_base, + size_t remaining_data_size, + BufferWriter* writer) { + // Special case where a TS packet requires 1 byte padding. + if (!has_pcr && remaining_data_size == kTsPacketMaximumPayloadSize - 1) { + writer->AppendInt(static_cast(0)); + return; + } + + // The size of the field itself. + const int kAdaptationFieldLengthSize = 1; + + // The size of all leading flags (not including the adaptation_field_length). + const int kAdaptationFieldHeaderSize = 1; + int adaptation_field_length = + kAdaptationFieldHeaderSize + (has_pcr ? kPcrFieldsSize : 0); + if (remaining_data_size < kTsPacketMaximumPayloadSize) { + const int current_ts_size = kTsPacketHeaderSize + remaining_data_size + + adaptation_field_length + + kAdaptationFieldLengthSize; + if (current_ts_size < kTsPacketSize) { + adaptation_field_length += kTsPacketSize - current_ts_size; + } + } + + writer->AppendInt(static_cast(adaptation_field_length)); + int remaining_bytes = adaptation_field_length; + writer->AppendInt(static_cast( + // All flags except PCR_flag are 0. + static_cast(has_pcr) << 4)); + remaining_bytes -= 1; + + if (has_pcr) { + // program_clock_reference_extension = 0. + const uint32_t most_significant_32bits_pcr = + static_cast(pcr_base >> 1); + const uint16_t pcr_last_bit_reserved_and_pcr_extension = + ((pcr_base & 1) << 15); + writer->AppendInt(most_significant_32bits_pcr); + writer->AppendInt(pcr_last_bit_reserved_and_pcr_extension); + remaining_bytes -= kPcrFieldsSize; + } + DCHECK_GE(remaining_bytes, 0); + if (remaining_bytes == 0) + return; + + DCHECK_GE(static_cast(arraysize(kPaddingBytes)), remaining_bytes); + writer->AppendArray(kPaddingBytes, remaining_bytes); +} + +// |payload| can be any payload. Most likely raw PSI tables or PES packet +// payload. +void WritePayloadToBufferWriter(const uint8_t* payload, + size_t payload_size, + bool payload_unit_start_indicator, + int pid, + bool has_pcr, + uint64_t pcr_base, + ContinuityCounter* continuity_counter, + BufferWriter* writer) { + size_t payload_bytes_written = 0; + + do { + const bool must_write_adaptation_header = has_pcr; + const size_t bytes_left = payload_size - payload_bytes_written; + const bool has_adaptation_field = must_write_adaptation_header || + bytes_left < kTsPacketMaximumPayloadSize; + + writer->AppendInt(kSyncByte); + writer->AppendInt(static_cast( + // transport_error_indicator and transport_priority are both '0'. + static_cast(payload_unit_start_indicator) << 14 | pid)); + + const uint8_t adaptation_field_control = + ((has_adaptation_field ? 1 : 0) << 1) | ((bytes_left != 0) ? 1 : 0); + // transport_scrambling_control is '00'. + writer->AppendInt(static_cast(adaptation_field_control << 4 | + continuity_counter->GetNext())); + + if (has_adaptation_field) { + const size_t before = writer->Size(); + WriteAdaptationField(has_pcr, pcr_base, bytes_left, writer); + const size_t bytes_for_adaptation_field = writer->Size() - before; + + const int write_bytes = + kTsPacketMaximumPayloadSize - bytes_for_adaptation_field; + writer->AppendArray(payload + payload_bytes_written, write_bytes); + payload_bytes_written += write_bytes; + } else { + writer->AppendArray(payload + payload_bytes_written, + kTsPacketMaximumPayloadSize); + payload_bytes_written += kTsPacketMaximumPayloadSize; + } + + // Once written, not needed for this payload. + has_pcr = false; + payload_unit_start_indicator = false; + } while (payload_bytes_written < payload_size); +} + +void WritePatPmtToBuffer(const uint8_t* data, + int data_size, + int pid, + ContinuityCounter* continuity_counter, + BufferWriter* writer) { + WritePayloadToBufferWriter(data, data_size, kPayloadUnitStartIndicator, pid, + !kHasPcr, 0, continuity_counter, writer); +} + +void WritePatToBuffer(const uint8_t* pat, + int pat_size, + ContinuityCounter* continuity_counter, + BufferWriter* writer) { + const int kPatPid = 0; + WritePatPmtToBuffer(pat, pat_size, kPatPid, continuity_counter, writer); +} + +void WritePmtToBuffer(const uint8_t* pmt, + int pmt_size, + ContinuityCounter* continuity_counter, + BufferWriter* writer) { + WritePatPmtToBuffer(pmt, pmt_size, kPmtPid, continuity_counter, writer); +} + +// The only difference between writing PTS or DTS is the leading bits. +void WritePtsOrDts(uint8_t leading_bits, + uint64_t pts_or_dts, + BufferWriter* writer) { + // First byte has 3 MSB of PTS. + uint8_t first_byte = + leading_bits << 4 | (((pts_or_dts >> 30) & 0x07) << 1) | 1; + // Second byte has the next 8 bits of pts. + uint8_t second_byte = (pts_or_dts >> 22) & 0xFF; + // Third byte has the next 7 bits of pts followed by a marker bit. + uint8_t third_byte = (((pts_or_dts >> 15) & 0x7F) << 1) | 1; + // Fourth byte has the next 8 bits of pts. + uint8_t fourth_byte = ((pts_or_dts >> 7) & 0xFF); + // Fifth byte has the last 7 bits of pts followed by a marker bit. + uint8_t fifth_byte = ((pts_or_dts & 0x7F) << 1) | 1; + writer->AppendInt(first_byte); + writer->AppendInt(second_byte); + writer->AppendInt(third_byte); + writer->AppendInt(fourth_byte); + writer->AppendInt(fifth_byte); +} + +bool WritePesToFile(const PesPacket& pes, + ContinuityCounter* continuity_counter, + File* file) { + // The size of the length field. + const int kAdaptationFieldLengthSize = 1; + // The size of the flags field. + const int kAdaptationFieldHeaderSize = 1; + const int kPcrFieldSize = 6; + const int kTsPacketMaxPayloadWithPcr = + kTsPacketMaximumPayloadSize - kAdaptationFieldLengthSize - + kAdaptationFieldHeaderSize - kPcrFieldSize; + const uint64_t pcr_base = pes.has_dts() ? pes.dts() : pes.pts(); + const int pid = kElementaryPid; + + // This writer will hold part of PES packet after PES_packet_length field. + BufferWriter pes_header_writer; + // The first bit must be '10' for PES with video or audio stream id. The other + // flags (bits) don't matter so they are 0. + pes_header_writer.AppendInt(static_cast(0x80)); + pes_header_writer.AppendInt( + static_cast(static_cast(pes.has_pts()) << 7 | + static_cast(pes.has_dts()) << 6 + // Other fields are all 0. + )); + uint8_t pes_header_data_length = 0; + if (pes.has_pts()) + pes_header_data_length += 5; + if (pes.has_dts()) + pes_header_data_length += 5; + pes_header_writer.AppendInt(pes_header_data_length); + + if (pes.has_pts() && pes.has_dts()) { + WritePtsOrDts(0x03, pes.pts(), &pes_header_writer); + WritePtsOrDts(0x01, pes.dts(), &pes_header_writer); + } else if (pes.has_pts()) { + WritePtsOrDts(0x02, pes.pts(), &pes_header_writer); + } + + // Put the first TS packet's payload into a buffer. This contains the PES + // packet's header. + BufferWriter first_ts_packet_buffer(kTsPacketSize); + first_ts_packet_buffer.AppendNBytes(static_cast(0x000001), 3); + first_ts_packet_buffer.AppendInt(pes.stream_id()); + const size_t pes_packet_length = pes.data().size() + pes_header_writer.Size(); + first_ts_packet_buffer.AppendInt(static_cast( + pes_packet_length > kMaxPesPacketLengthValue ? 0 : pes_packet_length)); + first_ts_packet_buffer.AppendBuffer(pes_header_writer); + + const int available_payload = + kTsPacketMaxPayloadWithPcr - first_ts_packet_buffer.Size(); + const int bytes_consumed = + std::min(static_cast(pes.data().size()), available_payload); + first_ts_packet_buffer.AppendArray(pes.data().data(), bytes_consumed); + + BufferWriter output_writer; + WritePayloadToBufferWriter(first_ts_packet_buffer.Buffer(), + first_ts_packet_buffer.Size(), + kPayloadUnitStartIndicator, pid, kHasPcr, pcr_base, + continuity_counter, &output_writer); + + const size_t remaining_pes_data_size = pes.data().size() - bytes_consumed; + if (remaining_pes_data_size > 0) { + WritePayloadToBufferWriter(pes.data().data() + bytes_consumed, + remaining_pes_data_size, + !kPayloadUnitStartIndicator, pid, !kHasPcr, 0, + continuity_counter, &output_writer); + } + return output_writer.WriteToFile(file).ok(); +} + +} // namespace + +ContinuityCounter::ContinuityCounter() {} +ContinuityCounter::~ContinuityCounter() {} + +int ContinuityCounter::GetNext() { + int ret = counter_; + ++counter_; + counter_ %= 16; + return ret; +} + +TsWriter::TsWriter() {} +TsWriter::~TsWriter() {} + +bool TsWriter::Initialize(const StreamInfo& stream_info) { + // This buffer will hold PMT data after section_length field so that this + // can be used to get the section_length. + time_scale_ = stream_info.time_scale(); + if (time_scale_ == 0) { + LOG(ERROR) << "Timescale is 0."; + return false; + } + const StreamType stream_type = stream_info.stream_type(); + if (stream_type != StreamType::kStreamVideo && + stream_type != StreamType::kStreamAudio) { + LOG(ERROR) << "TsWriter cannot handle stream type " << stream_type + << " yet."; + return false; + } + + const uint8_t* pmt = nullptr; + size_t pmt_size = 0u; + if (stream_info.stream_type() == StreamType::kStreamVideo) { + const VideoStreamInfo& video_stream_info = + static_cast(stream_info); + if (video_stream_info.codec() != VideoCodec::kCodecH264) { + LOG(ERROR) << "TsWriter cannot handle video codec " + << video_stream_info.codec() << " yet."; + return false; + } + pmt = kPmtH264; + pmt_size = arraysize(kPmtH264); + } else { + DCHECK_EQ(stream_type, StreamType::kStreamAudio); + const AudioStreamInfo& audio_stream_info = + static_cast(stream_info); + if (audio_stream_info.codec() != AudioCodec::kCodecAAC) { + LOG(ERROR) << "TsWriter cannot handle audio codec " + << audio_stream_info.codec() << " yet."; + return false; + } + pmt = kPmtAac; + pmt_size = arraysize(kPmtAac); + } + DCHECK(pmt); + DCHECK_GT(pmt_size, 0u); + + // Most likely going to fit in 2 TS packets. + BufferWriter psi_writer(kTsPacketSize * 2); + WritePatToBuffer(kPat, arraysize(kPat), &pat_continuity_counter_, + &psi_writer); + WritePmtToBuffer(pmt, pmt_size, &pmt_continuity_counter_, &psi_writer); + + psi_writer.SwapBuffer(&psi_ts_packets_); + return true; +} + +bool TsWriter::NewSegment(const std::string& file_name) { + DCHECK(!psi_ts_packets_.empty()); + if (current_file_) { + LOG(ERROR) << "File " << current_file_->file_name() << " still open."; + return false; + } + current_file_.reset(File::Open(file_name.c_str(), "w")); + if (!current_file_) { + LOG(ERROR) << "Failed to open file " << file_name; + return false; + } + + // TODO(kqyang): Add WriteArrayToFile(). + BufferWriter psi_writer(psi_ts_packets_.size()); + psi_writer.AppendVector(psi_ts_packets_); + if (!psi_writer.WriteToFile(current_file_.get()).ok()) { + LOG(ERROR) << "Failed to write PSI to file."; + return false; + } + + return true; +} + +bool TsWriter::FinalizeSegment() { + return current_file_.release()->Close(); +} + +bool TsWriter::AddPesPacket(scoped_ptr pes_packet) { + if (time_scale_ == 0) { + LOG(ERROR) << "Timescale is 0."; + return false; + } + DCHECK(current_file_); + if (!WritePesToFile(*pes_packet, &elementary_stream_continuity_counter_, + current_file_.get())) { + LOG(ERROR) << "Failed to write pes to file."; + return false; + } + + // No need to keep pes_packet around so not passing it anywhere. + return true; +} + +} // namespace mp2t +} // namespace media +} // namespace edash_packager diff --git a/packager/media/formats/mp2t/ts_writer.h b/packager/media/formats/mp2t/ts_writer.h new file mode 100644 index 0000000000..f3c8f6cfb8 --- /dev/null +++ b/packager/media/formats/mp2t/ts_writer.h @@ -0,0 +1,84 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef PACKAGER_MEDIA_FORMATS_MP2T_TS_WRITER_H_ +#define PACKAGER_MEDIA_FORMATS_MP2T_TS_WRITER_H_ + +#include +#include +#include + +#include "packager/base/memory/scoped_ptr.h" +#include "packager/media/base/media_stream.h" +#include "packager/media/file/file.h" +#include "packager/media/file/file_closer.h" +#include "packager/media/formats/mp2t/pes_packet.h" + +namespace edash_packager { +namespace media { +namespace mp2t { + +class ContinuityCounter { + public: + ContinuityCounter(); + ~ContinuityCounter(); + + /// As specified by the spec, this starts from 0 and is incremented by 1 until + /// it wraps back to 0 when it reaches 16. + /// @return counter value. + int GetNext(); + + private: + int counter_ = 0; + DISALLOW_COPY_AND_ASSIGN(ContinuityCounter); +}; + +/// This class takes PesPackets, encapsulates them into TS packets, and write +/// the data to file. This also creates PSI from StreamInfo. +class TsWriter { + public: + TsWriter(); + ~TsWriter(); + + /// This must be called before calling other methods. + /// @return true on success, false otherwise. + bool Initialize(const StreamInfo& stream_info); + + /// This will fail if the current segment is not finalized. + /// @param file_name is the output file name. + /// @return true on success, false otherwise. + bool NewSegment(const std::string& file_name); + + /// Flush all the pending PesPackets that have not been written to file and + /// close the file. + /// @return true on success, false otherwise. + bool FinalizeSegment(); + + /// Add PesPacket to the instance. PesPacket might not get written to file + /// immediately. + /// @param pes_packet gets added to the writer. + /// @return true on success, false otherwise. + bool AddPesPacket(scoped_ptr pes_packet); + + private: + std::vector psi_ts_packets_; + + uint32_t time_scale_ = 0u; + + ContinuityCounter pmt_continuity_counter_; + ContinuityCounter pat_continuity_counter_; + ContinuityCounter elementary_stream_continuity_counter_; + + scoped_ptr current_file_; + + DISALLOW_COPY_AND_ASSIGN(TsWriter); +}; + +} // namespace mp2t +} // namespace media +} // namespace edash_packager + +#endif // PACKAGER_MEDIA_FORMATS_MP2T_TS_WRITER_H_ diff --git a/packager/media/formats/mp2t/ts_writer_unittest.cc b/packager/media/formats/mp2t/ts_writer_unittest.cc new file mode 100644 index 0000000000..7657e87cdd --- /dev/null +++ b/packager/media/formats/mp2t/ts_writer_unittest.cc @@ -0,0 +1,424 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include + +#include "packager/base/files/file_path.h" +#include "packager/base/files/file_util.h" +#include "packager/media/base/audio_stream_info.h" +#include "packager/media/base/video_stream_info.h" +#include "packager/media/formats/mp2t/pes_packet.h" +#include "packager/media/formats/mp2t/ts_writer.h" + +namespace edash_packager { +namespace media { +namespace mp2t { + +namespace { + +const int kTsPacketSize = 188; + +// Only {Audio,Video}Codec matter for this test. Other values are bogus. +const VideoCodec kH264VideoCodec = VideoCodec::kCodecH264; +const AudioCodec kAacAudioCodec = AudioCodec::kCodecAAC; +const int kTrackId = 0; +const uint32_t kTimeScale = 90000; +const uint64_t kDuration = 180000; +const char kCodecString[] = "avc1"; +const char kLanguage[] = "eng"; +const uint32_t kWidth = 1280; +const uint32_t kHeight = 720; +const uint32_t kPixelWidth = 1; +const uint32_t kPixelHeight = 1; +const uint16_t kTrickPlayRate = 1; +const uint8_t kNaluLengthSize = 1; +const bool kIsEncrypted = false; + +const uint8_t kSampleBits = 16; +const uint8_t kNumChannels = 2; +const uint32_t kSamplingFrequency = 44100; +const uint32_t kMaxBitrate = 320000; +const uint32_t kAverageBitrate = 256000; + +const uint8_t kExtraData[] = { + 0x01, 0x02, +}; + +} // namespace + +class TsWriterTest : public ::testing::Test { + protected: + // Using different file names for each test so that the tests can be run in + // parallel. + void SetUp() override { + base::CreateTemporaryFile(&test_file_path_); + // TODO(rkuroiwa): Use memory file prefix once its exposed. + test_file_name_ = kLocalFilePrefix + test_file_path_.value(); + } + + void TearDown() override { + const bool kRecursive = true; + base::DeleteFile(test_file_path_, !kRecursive); + } + + bool ReadFileToVector(const base::FilePath& path, std::vector* out) { + std::string content; + if (!base::ReadFileToString(path, &content)) + return false; + out->assign(content.begin(), content.end()); + return true; + } + + // Checks whether |actual|'s prefix matches with |prefix| and the suffix + // matches with |suffix|. If there is padding, then padding_length specifies + // how long the padding is between prefix and suffix. + // |actual| must be at least 188 bytes long. + void ExpectTsPacketEqual(const uint8_t* prefix, size_t prefix_size, + int padding_length, + const uint8_t* suffix, size_t suffix_size, + const uint8_t* actual) { + std::vector actual_prefix(actual, actual + prefix_size); + EXPECT_EQ(std::vector(prefix, prefix + prefix_size), + actual_prefix); + + // Padding until the payload. + for (size_t i = prefix_size; i < kTsPacketSize - suffix_size; ++i) { + EXPECT_EQ(0xFF, actual[i]) << "at index " << i; + } + + std::vector actual_suffix(actual + prefix_size + padding_length, + actual + kTsPacketSize); + EXPECT_EQ(std::vector(suffix, suffix + suffix_size), + actual_suffix); + } + + std::string test_file_name_; + TsWriter ts_writer_; + + base::FilePath test_file_path_; +}; + +TEST_F(TsWriterTest, InitializeVideoH264) { + scoped_refptr stream_info(new VideoStreamInfo( + kTrackId, kTimeScale, kDuration, kH264VideoCodec, kCodecString, kLanguage, + kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, + kNaluLengthSize, kExtraData, arraysize(kExtraData), kIsEncrypted)); + EXPECT_TRUE(ts_writer_.Initialize(*stream_info)); +} + +TEST_F(TsWriterTest, InitializeVideoNonH264) { + scoped_refptr stream_info(new VideoStreamInfo( + kTrackId, kTimeScale, kDuration, VideoCodec::kCodecVP9, kCodecString, + kLanguage, kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, + kNaluLengthSize, kExtraData, arraysize(kExtraData), kIsEncrypted)); + EXPECT_FALSE(ts_writer_.Initialize(*stream_info)); +} + +TEST_F(TsWriterTest, InitializeAudioAac) { + scoped_refptr stream_info(new AudioStreamInfo( + kTrackId, kTimeScale, kDuration, kAacAudioCodec, kCodecString, kLanguage, + kSampleBits, kNumChannels, kSamplingFrequency, kMaxBitrate, + kAverageBitrate, kExtraData, arraysize(kExtraData), kIsEncrypted)); + EXPECT_TRUE(ts_writer_.Initialize(*stream_info)); +} + +TEST_F(TsWriterTest, InitializeAudioNonAac) { + scoped_refptr stream_info(new AudioStreamInfo( + kTrackId, kTimeScale, kDuration, AudioCodec::kCodecOpus, kCodecString, + kLanguage, kSampleBits, kNumChannels, kSamplingFrequency, kMaxBitrate, + kAverageBitrate, kExtraData, arraysize(kExtraData), kIsEncrypted)); + EXPECT_FALSE(ts_writer_.Initialize(*stream_info)); +} + +TEST_F(TsWriterTest, NewSegment) { + scoped_refptr stream_info(new VideoStreamInfo( + kTrackId, kTimeScale, kDuration, kH264VideoCodec, kCodecString, kLanguage, + kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, + kNaluLengthSize, kExtraData, arraysize(kExtraData), kIsEncrypted)); + EXPECT_TRUE(ts_writer_.Initialize(*stream_info)); + EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_)); + ASSERT_TRUE(ts_writer_.FinalizeSegment()); + + std::vector content; + ASSERT_TRUE(ReadFileToVector(test_file_path_, &content)); + // 2 TS Packets. PAT, PMT. + ASSERT_EQ(376u, content.size()); + + const uint8_t kExpectedPatPrefix[] = { + 0x47, // Sync byte. + 0x40, // payload_unit_start_indicator set. + 0x00, // pid. + 0x30, // Adaptation field and payload are both present. counter = 0. + 0xA6, // Adaptation Field length. + 0x00, // All adaptation field flags 0. + }; + const int kExpectedPatPrefixSize = arraysize(kExpectedPatPrefix); + const uint8_t kExpectedPatPayload[] = { + 0x00, // pointer field + 0x00, + 0xB0, // The last 2 '00' assumes that this PAT is not very long. + 0x0D, // Length of the rest of this array. + 0x00, 0x00, // Transport stream ID is 0. + 0xC1, // version number 0, current next indicator 1. + 0x00, // section number + 0x00, // last section number + // program number -> PMT PID mapping. + 0x00, 0x01, // program number is 1. + 0xE0, // first 3 bits is reserved. + 0x20, // PMT PID. + // CRC32. + 0xAB, 0xB9, 0x9E, 0x9D, + }; + + EXPECT_NO_FATAL_FAILURE(ExpectTsPacketEqual( + kExpectedPatPrefix, kExpectedPatPrefixSize, 165, kExpectedPatPayload, + arraysize(kExpectedPatPayload), content.data())); + + const uint8_t kExpectedPmtPrefix[] = { + 0x47, // Sync byte. + 0x40, // payload_unit_start_indicator set. + 0x20, // pid. + 0x30, // Adaptation field and payload are both present. counter = 0. + 0xA1, // Adaptation Field length. + 0x00, // All adaptation field flags 0. + }; + const int kExpectedPmtPrefixSize = arraysize(kExpectedPmtPrefix); + const uint8_t kPmtH264[] = { + 0x00, // pointer field + 0x02, + 0xB0, // assumes length is <= 256 bytes. + 0x12, // length of the rest of this array. + 0x00, 0x01, + 0xC1, // version 0, current next indicator 1. + 0x00, // section number + 0x00, // last section number. + 0xE0, // first 3 bits reserved. + 0x50, // PCR PID is the elementary streams PID. + 0xF0, // first 4 bits reserved. + 0x00, // No descriptor at this level. + 0x1B, 0xE0, 0x50, // stream_type -> PID. + 0xF0, 0x00, // Es_info_length is 0. + // CRC32. + 0x56, 0x90, 0xF4, 0xEB, + }; + + EXPECT_NO_FATAL_FAILURE(ExpectTsPacketEqual( + kExpectedPmtPrefix, kExpectedPmtPrefixSize, 160, kPmtH264, + arraysize(kPmtH264), content.data() + kTsPacketSize)); +} + +TEST_F(TsWriterTest, AddPesPacket) { + scoped_refptr stream_info(new VideoStreamInfo( + kTrackId, kTimeScale, kDuration, kH264VideoCodec, kCodecString, kLanguage, + kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, + kNaluLengthSize, kExtraData, arraysize(kExtraData), kIsEncrypted)); + EXPECT_TRUE(ts_writer_.Initialize(*stream_info)); + EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_)); + + scoped_ptr pes(new PesPacket()); + pes->set_stream_id(0xE0); + pes->set_duration(99000); + pes->set_pts(0x900); + pes->set_dts(0x900); + const uint8_t kAnyData[] = { + 0x12, 0x88, 0x4f, 0x4a, + }; + pes->mutable_data()->assign(kAnyData, kAnyData + arraysize(kAnyData)); + + EXPECT_TRUE(ts_writer_.AddPesPacket(pes.Pass())); + ASSERT_TRUE(ts_writer_.FinalizeSegment()); + + std::vector content; + ASSERT_TRUE(ReadFileToVector(test_file_path_, &content)); + // 3 TS Packets. PAT, PMT, and PES. + ASSERT_EQ(564u, content.size()); + + const int kPesStartPosition = 376; + + // Prefix of the expected output. Rest of the packet should be filled with + // padding. + const uint8_t kExpectedOutputPrefix[] = { + 0x47, // Sync byte. + 0x40, // payload_unit_start_indicator set. + 0x50, // pid. + 0x30, // Adaptation field and payload are both present. counter = 0. + 0xA0, // Adaptation Field length. + 0x10, // pcr flag. + 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, // PCR. + }; + + const uint8_t kExpectedPayload[] = { + 0x00, 0x00, 0x01, // Start code. + 0xE0, // stream id. + 0x00, 0x11, // PES_packet_length. + 0x80, // Flags. + 0xC0, // PTS and DTS both present. + 0x0A, // PES_header_data_length. + 0x31, // Since PTS is 0 this is '0011' (fixed) and marker bit at LSB. + 0x00, // PTS leading bits 0. + 0x01, // PTS 0 followed by marker bit. + 0x12, // PTS 0x900 shifted. + 0x01, // PTS 0 followed by marker bit. + 0x11, // Fixed '0001' followed by marker bit at LSB. + 0x00, // DTS leading bits 0. + 0x01, // DTS 0 followed by marker bit. + 0x12, // DTS 0x900 shifted. + 0x01, // DTS 0 followed by marker bit. + 0x12, 0x88, 0x4f, 0x4a, // Payload. + }; + EXPECT_NO_FATAL_FAILURE(ExpectTsPacketEqual( + kExpectedOutputPrefix, arraysize(kExpectedOutputPrefix), 153, + kExpectedPayload, arraysize(kExpectedPayload), + content.data() + kPesStartPosition)); +} + +// Verify that PES packet > 64KiB can be handled. +TEST_F(TsWriterTest, BigPesPacket) { + scoped_refptr stream_info(new VideoStreamInfo( + kTrackId, kTimeScale, kDuration, kH264VideoCodec, kCodecString, kLanguage, + kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, + kNaluLengthSize, kExtraData, arraysize(kExtraData), kIsEncrypted)); + EXPECT_TRUE(ts_writer_.Initialize(*stream_info)); + EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_)); + + scoped_ptr pes(new PesPacket()); + pes->set_duration(99000); + pes->set_pts(0); + pes->set_dts(0); + // A little over 2 TS Packets (3 TS Packets). + const std::vector big_data(400, 0x23); + *pes->mutable_data() = big_data; + + EXPECT_TRUE(ts_writer_.AddPesPacket(pes.Pass())); + ASSERT_TRUE(ts_writer_.FinalizeSegment()); + + std::vector content; + ASSERT_TRUE(ReadFileToVector(test_file_path_, &content)); + // The first TsPacket can only carry + // 177 (TS packet size - header - adaptation_field) - 19 (PES header data) = + // 158 bytes of the PES packet payload. + // So this should create + // 2 + 1 + ceil((400 - 158) / 184) = 5 TsPackets. + // Where 184 is the maxium payload of a TS packet. + EXPECT_EQ(5u * 188, content.size()); + + // Check continuity counter. + EXPECT_EQ(0, (content[2 * 188 + 3] & 0xF)); + EXPECT_EQ(1, (content[3 * 188 + 3] & 0xF)); + EXPECT_EQ(2, (content[4 * 188 + 3] & 0xF)); +} + +// Bug found in code review. It should check whether PTS is present not whether +// PTS (implicilty) cast to bool is true. +TEST_F(TsWriterTest, PesPtsZeroNoDts) { + scoped_refptr stream_info(new VideoStreamInfo( + kTrackId, kTimeScale, kDuration, kH264VideoCodec, kCodecString, kLanguage, + kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, + kNaluLengthSize, kExtraData, arraysize(kExtraData), kIsEncrypted)); + EXPECT_TRUE(ts_writer_.Initialize(*stream_info)); + EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_)); + + scoped_ptr pes(new PesPacket()); + pes->set_stream_id(0xE0); + pes->set_duration(99000); + pes->set_pts(0x0); + const uint8_t kAnyData[] = { + 0x12, 0x88, 0x4F, 0x4A, + }; + pes->mutable_data()->assign(kAnyData, kAnyData + arraysize(kAnyData)); + + EXPECT_TRUE(ts_writer_.AddPesPacket(pes.Pass())); + ASSERT_TRUE(ts_writer_.FinalizeSegment()); + + std::vector content; + ASSERT_TRUE(ReadFileToVector(test_file_path_, &content)); + // 3 TS Packets. PAT, PMT, and PES. + ASSERT_EQ(564u, content.size()); + + const int kPesStartPosition = 376; + + // Prefix of the expected output. Rest of the packet should be filled with + // padding. + const uint8_t kExpectedOutputPrefix[] = { + 0x47, // Sync byte. + 0x40, // payload_unit_start_indicator set. + 0x50, // pid. + 0x30, // Adaptation field and payload are both present. counter = 0. + 0xA5, // Adaptation Field length. + 0x10, // pcr flag. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // PCR. + }; + + const uint8_t kExpectedPayload[] = { + 0x00, 0x00, 0x01, // Start code. + 0xE0, // stream id. + 0x00, 0x0C, // PES_packet_length. + 0x80, // Flags. + 0x80, // Only PTS present. + 0x05, // PES_header_data_length. + 0x21, // Since PTS is 0 this is '0010' (fixed) and marker bit at LSB. + 0x00, // PTS 0. + 0x01, // PTS 0 followed by marker bit. + 0x00, // PTS 0. + 0x01, // PTS 0 followed by marker bit. + 0x12, 0x88, 0x4F, 0x4A, // Payload. + }; + EXPECT_NO_FATAL_FAILURE(ExpectTsPacketEqual( + kExpectedOutputPrefix, arraysize(kExpectedOutputPrefix), 158, + kExpectedPayload, arraysize(kExpectedPayload), + content.data() + kPesStartPosition)); +} + +// Verify that TS packet with payload 183 is handled correctly, e.g. +// adaptation_field_length should be 0. +TEST_F(TsWriterTest, TsPacketPayload183Bytes) { + scoped_refptr stream_info(new VideoStreamInfo( + kTrackId, kTimeScale, kDuration, kH264VideoCodec, kCodecString, kLanguage, + kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, + kNaluLengthSize, kExtraData, arraysize(kExtraData), kIsEncrypted)); + EXPECT_TRUE(ts_writer_.Initialize(*stream_info)); + EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_)); + + scoped_ptr pes(new PesPacket()); + pes->set_stream_id(0xE0); + pes->set_pts(0x00); + pes->set_dts(0x00); + + // Note that first TS packet will have adaptation fields with PCR, so make + // payload big enough so that second PES packet's payload is 183. + // First TS packet can carry 157 bytes of PES payload. The next one should + // carry 183 bytes. + std::vector pes_payload(157 + 183, 0xAF); + *pes->mutable_data() = pes_payload; + + EXPECT_TRUE(ts_writer_.AddPesPacket(pes.Pass())); + ASSERT_TRUE(ts_writer_.FinalizeSegment()); + + const uint8_t kExpectedOutputPrefix[] = { + 0x47, // Sync byte. + 0x00, // payload_unit_start_indicator set. + 0x50, // pid. + 0x31, // Adaptation field and payload are both present. counter = 0. + 0x00, // Adaptation Field length, 1 byte padding. + }; + + std::vector content; + ASSERT_TRUE(ReadFileToVector(test_file_path_, &content)); + // 4 TsPackets. PAT, PMT, TsPacket with PES header, TsPacket rest of PES + // payload. + ASSERT_EQ(752u, content.size()); + + const int kPesStartPosition = 564; + std::vector actual_prefix(content.data() + kPesStartPosition, + content.data() + kPesStartPosition + 5); + EXPECT_EQ( + std::vector(kExpectedOutputPrefix, kExpectedOutputPrefix + 5), + actual_prefix); +} + +} // namespace mp2t +} // namespace media +} // namespace edash_packager