diff --git a/packager/app/test/testdata/bear-640x360-a-golden-2.ts b/packager/app/test/testdata/bear-640x360-a-golden-2.ts index af2d499949..6e8503c696 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-golden-2.ts and b/packager/app/test/testdata/bear-640x360-a-golden-2.ts differ diff --git a/packager/app/test/testdata/bear-640x360-a-golden-3.ts b/packager/app/test/testdata/bear-640x360-a-golden-3.ts index 5e060e766c..b877ffdbf7 100644 Binary files a/packager/app/test/testdata/bear-640x360-a-golden-3.ts and b/packager/app/test/testdata/bear-640x360-a-golden-3.ts differ diff --git a/packager/app/test/testdata/bear-640x360-v-golden-2.ts b/packager/app/test/testdata/bear-640x360-v-golden-2.ts index 2b01f5cffc..7dadd15355 100644 Binary files a/packager/app/test/testdata/bear-640x360-v-golden-2.ts and b/packager/app/test/testdata/bear-640x360-v-golden-2.ts differ diff --git a/packager/app/test/testdata/bear-640x360-v-golden-3.ts b/packager/app/test/testdata/bear-640x360-v-golden-3.ts index 713dcc5f77..ab0c79483c 100644 Binary files a/packager/app/test/testdata/bear-640x360-v-golden-3.ts and b/packager/app/test/testdata/bear-640x360-v-golden-3.ts differ diff --git a/packager/media/base/fourccs.h b/packager/media/base/fourccs.h index fd0e6f9c55..2ffe4c557c 100644 --- a/packager/media/base/fourccs.h +++ b/packager/media/base/fourccs.h @@ -10,13 +10,15 @@ namespace edash_packager { namespace media { -enum FourCC { +enum FourCC : uint32_t { FOURCC_NULL = 0, FOURCC_ID32 = 0x49443332, FOURCC_PRIV = 0x50524956, + FOURCC_aacd = 0x61616364, FOURCC_ac_3 = 0x61632d33, // "ac-3" + FOURCC_apad = 0x61706164, FOURCC_avc1 = 0x61766331, FOURCC_avcC = 0x61766343, FOURCC_bloc = 0x626C6F63, @@ -131,6 +133,9 @@ enum FourCC { FOURCC_vtte = 0x76747465, FOURCC_wide = 0x77696465, FOURCC_wvtt = 0x77767474, + FOURCC_zaac = 0x7A616163, + FOURCC_zach = 0x7A616368, + FOURCC_zacp = 0x7A616370, }; const inline std::string FourCCToString(FourCC fourcc) { diff --git a/packager/media/formats/mp2t/continuity_counter.cc b/packager/media/formats/mp2t/continuity_counter.cc new file mode 100644 index 0000000000..ed61c29e19 --- /dev/null +++ b/packager/media/formats/mp2t/continuity_counter.cc @@ -0,0 +1,25 @@ +// 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/continuity_counter.h" + +namespace edash_packager { +namespace media { +namespace mp2t { + +ContinuityCounter::ContinuityCounter() {} +ContinuityCounter::~ContinuityCounter() {} + +int ContinuityCounter::GetNext() { + int ret = counter_; + ++counter_; + counter_ %= 16; + return ret; +} + +} // namespace mp2t +} // namespace media +} // namespace edash_packager diff --git a/packager/media/formats/mp2t/continuity_counter.h b/packager/media/formats/mp2t/continuity_counter.h new file mode 100644 index 0000000000..0a62386c3d --- /dev/null +++ b/packager/media/formats/mp2t/continuity_counter.h @@ -0,0 +1,35 @@ +// 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_CONTINUITY_COUNTER_H_ +#define PACKAGER_MEDIA_FORMATS_MP2T_CONTINUITY_COUNTER_H_ + +#include "packager/base/macros.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); +}; + +} // namespace mp2t +} // namespace media +} // namespace edash_packager + +#endif // PACKAGER_MEDIA_FORMATS_MP2T_CONTINUITY_COUNTER_H_ diff --git a/packager/media/formats/mp2t/mp2t.gyp b/packager/media/formats/mp2t/mp2t.gyp index d5e6debb47..8b90293a54 100644 --- a/packager/media/formats/mp2t/mp2t.gyp +++ b/packager/media/formats/mp2t/mp2t.gyp @@ -15,6 +15,8 @@ 'sources': [ 'adts_header.cc', 'adts_header.h', + 'continuity_counter.cc', + 'continuity_counter.h', 'es_parser_adts.cc', 'es_parser_adts.h', 'es_parser_h264.cc', @@ -30,10 +32,14 @@ 'pes_packet.h', 'pes_packet_generator.cc', 'pes_packet_generator.h', + 'program_map_table_writer.cc', + 'program_map_table_writer.h', 'ts_muxer.cc', 'ts_muxer.h', 'ts_packet.cc', 'ts_packet.h', + 'ts_packet_writer_util.cc', + 'ts_packet_writer_util.h', 'ts_section_pat.cc', 'ts_section_pat.h', 'ts_section_pes.cc', @@ -64,6 +70,7 @@ 'es_parser_h26x_unittest.cc', 'mp2t_media_parser_unittest.cc', 'pes_packet_generator_unittest.cc', + 'program_map_table_writer_unittest.cc', 'ts_segmenter_unittest.cc', 'ts_writer_unittest.cc', ], diff --git a/packager/media/formats/mp2t/program_map_table_writer.cc b/packager/media/formats/mp2t/program_map_table_writer.cc new file mode 100644 index 0000000000..6f40874c34 --- /dev/null +++ b/packager/media/formats/mp2t/program_map_table_writer.cc @@ -0,0 +1,412 @@ +// 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/program_map_table_writer.h" + +#include + +#include "packager/base/logging.h" +#include "packager/media/base/buffer_writer.h" +#include "packager/media/base/fourccs.h" +#include "packager/media/formats/mp2t/continuity_counter.h" +#include "packager/media/formats/mp2t/ts_packet_writer_util.h" +#include "packager/media/formats/mp4/aac_audio_specific_config.h" + +namespace edash_packager { +namespace media { +namespace mp2t { + +namespace { + +// Values for version. Only 0 and 1 are necessary for the implementation. +const int kVersion1 = 1; + +// Values for current_next_indicator. +const int kCurrent = 1; +const int kNext= 0; + +// Program number is 16 bits but 8 bits is sufficient. +const uint8_t kProgramNumber = 0x01; +const uint8_t kProgramMapTableId = 0x02; + +// Stream types. +// Clear. +const uint8_t kStreamTypeH264 = 0x1B; +const uint8_t kStreamTypeAdtsAac = 0x0F; +// Encrypted. +const uint8_t kStreamTypeEncryptedH264 = 0xDB; +const uint8_t kStreamTypeEncryptedAdtsAac = 0xCF; + +// Table for CRC32/MPEG2. +const uint32_t kCrcTable[] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, + 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, + 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, + 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, + 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, + 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, + 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, + 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, + 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, + 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, + 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, + 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, + 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, + 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, + 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, + 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, + 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, + 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, + 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, + 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, + 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, + 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, + 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, + 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, + 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, + 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, + 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, + 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, + 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, + 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, + 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, + 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, + 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, + 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, + 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4, +}; + +// Note there are dozens of CRCs. This is one of them. +// http://reveng.sourceforge.net/crc-catalogue/all.htm +uint32_t Crc32Mpeg2(const uint8_t* data, size_t data_size) { + uint32_t crc = 0xFFFFFFFF; + for (size_t i = 0; i < data_size; ++i) { + crc = kCrcTable[((crc >> 24) ^ data[i]) & 0xFF] ^ (crc << 8); + } + return crc; +} + +// For all the pointer fields in the PMTs, they are not really part of the PMT +// but it's there so that an extra buffer isn't required to prepend the 0x00 +// byte. + +// PMT for H264 clear segments. +// Note that this is version 0, so it only works for clear lead or clear stream. +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. + // PCR PID is the elementary streams PID. + ProgramMapTableWriter::kElementaryPid, + 0xF0, // first 4 bits reserved. + 0x00, // No descriptor at this level. + // stream_type -> PID. + kStreamTypeH264, 0xE0, ProgramMapTableWriter::kElementaryPid, + 0xF0, 0x00, // Es_info_length is 0. + // CRC32. + 0x43, 0x49, 0x97, 0xBE, +}; + +// PMT for AAC clear segments. +// Note that this is version 0, so it only works for clear lead or clear stream. +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. + // PCR PID is the elementary streams PID. + ProgramMapTableWriter::kElementaryPid, + 0xF0, // first 4 bits reserved. + 0x00, // No descriptor at this level. + // stream_type -> PID. + kStreamTypeAdtsAac, 0xE0, ProgramMapTableWriter::kElementaryPid, + 0xF0, 0x00, // Es_info_length is 0. + // CRC32. + 0xE0, 0x6F, 0x1A, 0x31, +}; + +// private_data_indicator for SAMPLE-AES H264. This is the same for all H264 +// streams. +const uint8_t kPrivateDataIndicatorDescriptorEncryptedH264[] = { + 0x0F, // descriptor_tag. + 0x04, // Length of the rest of this descriptor. + // 'zavc'. + 0x7A, 0x61, 0x76, 0x63, +}; + +void WritePmtToBuffer(const uint8_t* pmt, + int pmt_size, + ContinuityCounter* continuity_counter, + BufferWriter* writer) { + const bool kPayloadUnitStartIndicator = true; + const bool kHasPcr = true; + const uint64_t kAnyPcrBase = 0; + WritePayloadToBufferWriter(pmt, pmt_size, kPayloadUnitStartIndicator, + ProgramMapTableWriter::kPmtPid, !kHasPcr, + kAnyPcrBase, continuity_counter, writer); +} + +void WritePrivateDataIndicatorDescriptor(FourCC fourcc, BufferWriter* output) { + const uint8_t kPrivateDataIndicatorDescriptor = 15; + output->AppendInt(kPrivateDataIndicatorDescriptor); + output->AppendInt(static_cast(sizeof(FOURCC_aacd))); + output->AppendInt(FOURCC_aacd); +} + +bool WriteAacAudioSetupInformation(const uint8_t* aac_audio_specific_config, + size_t aac_audio_specific_config_size, + BufferWriter* audio_setup_information) { + mp4::AACAudioSpecificConfig config; + const bool result = config.Parse(std::vector( + aac_audio_specific_config, + aac_audio_specific_config + aac_audio_specific_config_size)); + if (!result) { + LOG(WARNING) << "Failed to parse config. Assuming AAC-LC."; + return false; + } + + const uint8_t kAacLc = 2; + const uint8_t kAacHeV1 = 5; + const uint8_t kAacHeV2 = 29; + uint8_t audio_object_type = 2; + audio_object_type = config.audio_object_type(); + + switch (audio_object_type) { + case kAacLc: + audio_setup_information->AppendInt(FOURCC_zaac); + break; + case kAacHeV1: + audio_setup_information->AppendInt(FOURCC_zach); + break; + case kAacHeV2: + audio_setup_information->AppendInt(FOURCC_zacp); + break; + default: + LOG(ERROR) << "Unknown object type for aac " << audio_object_type; + return false; + } + + // Priming. Since no info from encoder, set it to 0x0000. + audio_setup_information->AppendInt(static_cast(0x0000)); + // Version is always 0x01. + audio_setup_information->AppendInt(static_cast(0x01)); + audio_setup_information->AppendInt( + static_cast(aac_audio_specific_config_size)); + audio_setup_information->AppendArray(aac_audio_specific_config, + aac_audio_specific_config_size); + return true; +} + +bool WriteRegistrationDescriptorForEncryptedAudio(const uint8_t* setup_data, + size_t setup_data_size, + BufferWriter* output) { + const uint8_t kRegistrationDescriptor = 5; + BufferWriter audio_setup_information; + if (!WriteAacAudioSetupInformation(setup_data, setup_data_size, + &audio_setup_information)) { + return false; + } + + output->AppendInt(kRegistrationDescriptor); + // Length of the rest of this descriptor is size of audio_setup_information + + // 4 bytes (for 'apad'). + output->AppendInt(static_cast(audio_setup_information.Size() + + sizeof(FOURCC_apad))); + output->AppendInt(FOURCC_apad); + output->AppendBuffer(audio_setup_information); + return true; +} + +void WritePmtWithParameters(uint8_t stream_type, + int version, + int current_next_indicator, + const uint8_t* descriptors, + size_t descriptors_size, + ContinuityCounter* continuity_counter, + BufferWriter* output) { + DCHECK(current_next_indicator == kCurrent || current_next_indicator == kNext); + // Body starting from program number. + BufferWriter pmt_body; + pmt_body.AppendInt(static_cast(0x00)); + pmt_body.AppendInt(kProgramNumber); + // resevered bits then version and current_next_indicator. + pmt_body.AppendInt( + static_cast(0xC0 | + static_cast(version) << 1 | + static_cast(current_next_indicator))); + // section number. + pmt_body.AppendInt(static_cast(0x00)); + // last section number. + pmt_body.AppendInt(static_cast(0x00)); + // first 3 bits reserved. Rest is unused bits for PCR PID. + pmt_body.AppendInt(static_cast(0xE0)); + pmt_body.AppendInt(ProgramMapTableWriter::kElementaryPid); + // First 4 bits are reserved. Next 12 bits is program_info_length which is 0. + pmt_body.AppendInt(static_cast(0xF0)); + pmt_body.AppendInt(static_cast(0x00)); + + pmt_body.AppendInt(stream_type); + // 3 reserved bits followed by 13 bit elementary_PID. + pmt_body.AppendInt(static_cast(0xE0)); + pmt_body.AppendInt(ProgramMapTableWriter::kElementaryPid); + + // 4 reserved bits followed by ES_info_length. + pmt_body.AppendInt(static_cast(0xF000 | descriptors_size)); + pmt_body.AppendArray(descriptors, descriptors_size); + + // The whole PMT has 3 bytes before the body and 4 more bytes for CRC. This + // also includes pointer field (1 byte) so + 8 in total. + BufferWriter pmt(pmt_body.Size() + 8); + // Pointer field. + pmt.AppendInt(static_cast(0x00)); + // PMT table ID is always 2. + pmt.AppendInt(static_cast(0x02)); + // First four bits must be '1011'. +4 for CRC. + pmt.AppendInt(static_cast(0xB000 | (pmt_body.Size() + 4))); + pmt.AppendBuffer(pmt_body); + + // Don't include the pointer field. + const uint32_t crc = Crc32Mpeg2(pmt.Buffer() + 1, pmt.Size() - 1); + pmt.AppendInt(crc); + WritePmtToBuffer(pmt.Buffer(), pmt.Size(), continuity_counter, output); +} + +} // namespace + +ProgramMapTableWriter::ProgramMapTableWriter() {} +ProgramMapTableWriter::~ProgramMapTableWriter() {} + +H264ProgramMapTableWriter::H264ProgramMapTableWriter( + ContinuityCounter* continuity_counter) + : continuity_counter_(continuity_counter) { + DCHECK(continuity_counter); +} + +H264ProgramMapTableWriter::~H264ProgramMapTableWriter() {} + +bool H264ProgramMapTableWriter::ClearLeadSegmentPmt(BufferWriter* writer) { + WritePmtToBuffer(kPmtH264, arraysize(kPmtH264), continuity_counter_, writer); + WritePmtWithParameters( + kStreamTypeEncryptedH264, kVersion1, kNext, + kPrivateDataIndicatorDescriptorEncryptedH264, + arraysize(kPrivateDataIndicatorDescriptorEncryptedH264), + continuity_counter_, writer); + return true; +} + +bool H264ProgramMapTableWriter::EncryptedSegmentPmt(BufferWriter* writer) { + WritePmtWithParameters( + kStreamTypeEncryptedH264, kVersion1, kCurrent, + kPrivateDataIndicatorDescriptorEncryptedH264, + arraysize(kPrivateDataIndicatorDescriptorEncryptedH264), + continuity_counter_, writer); + return true; +} + +bool H264ProgramMapTableWriter::ClearSegmentPmt(BufferWriter* writer) { + WritePmtToBuffer(kPmtH264, arraysize(kPmtH264), continuity_counter_, writer); + return true; +} + +AacProgramMapTableWriter::AacProgramMapTableWriter( + const std::vector& aac_audio_specific_config, + ContinuityCounter* continuity_counter) + : aac_audio_specific_config_(aac_audio_specific_config), + continuity_counter_(continuity_counter) { + DCHECK(!aac_audio_specific_config.empty()); + DCHECK(continuity_counter_); +} + +AacProgramMapTableWriter::~AacProgramMapTableWriter() {} + +// TODO(rkuroiwa): Cache the PMT for encrypted segments, it doesn't need to +// be recalculated. +bool AacProgramMapTableWriter::ClearLeadSegmentPmt(BufferWriter* writer) { + WritePmtToBuffer(kPmtAac, arraysize(kPmtAac), continuity_counter_, writer); + // Version 1 and next. + return EncryptedSegmentPmtWithParameters(kVersion1, kNext, writer); +} + +bool AacProgramMapTableWriter::EncryptedSegmentPmt(BufferWriter* writer) { + // Version 1 and current. + return EncryptedSegmentPmtWithParameters(kVersion1, kCurrent, writer); +} + +bool AacProgramMapTableWriter::ClearSegmentPmt(BufferWriter* writer) { + WritePmtToBuffer(kPmtAac, arraysize(kPmtAac), continuity_counter_, writer); + return true; +} + +bool AacProgramMapTableWriter::EncryptedSegmentPmtWithParameters( + int version, + int current_next_indicator, + BufferWriter* writer) { + // -12 because there are 12 bytes between 'descriptor_length' in + // registartion_descriptor and 'setup_data_length' in audio_setup_information. + if (aac_audio_specific_config_.size() > + std::numeric_limits::max() - 12) { + LOG(ERROR) << "AACAudioSpecificConfig of size: " + << aac_audio_specific_config_.size() + << " will not fit in the descriptor."; + return false; + } + BufferWriter descriptors; + WritePrivateDataIndicatorDescriptor(FOURCC_aacd, &descriptors); + if (!WriteRegistrationDescriptorForEncryptedAudio( + aac_audio_specific_config_.data(), aac_audio_specific_config_.size(), + &descriptors)) { + return false; + } + + WritePmtWithParameters( + kStreamTypeEncryptedAdtsAac, version, current_next_indicator, + descriptors.Buffer(), descriptors.Size(), continuity_counter_, writer); + return true; +} + +} // namespace mp2t +} // namespace media +} // namespace edash_packager diff --git a/packager/media/formats/mp2t/program_map_table_writer.h b/packager/media/formats/mp2t/program_map_table_writer.h new file mode 100644 index 0000000000..6a162f3632 --- /dev/null +++ b/packager/media/formats/mp2t/program_map_table_writer.h @@ -0,0 +1,100 @@ +// 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_PROGRAM_MAP_TABLE_WRITER_H_ +#define PACKAGER_MEDIA_FORMATS_MP2T_PROGRAM_MAP_TABLE_WRITER_H_ + +#include + +#include + +#include "packager/base/macros.h" + +namespace edash_packager { +namespace media { + +class BufferWriter; + +namespace mp2t { + +class ContinuityCounter; + +/// Puts PMT into TS packets and writes them to buffer. +/// Note that this does not currently allow encryption without clear lead. +class ProgramMapTableWriter { + public: + ProgramMapTableWriter(); + virtual ~ProgramMapTableWriter(); + + /// Writes TS packets with PMT for clear lead followed by another PMT for + /// encrypted segments. + virtual bool ClearLeadSegmentPmt(BufferWriter* writer) = 0; + + /// Writes TS packets with PMT for encrypted segments, the version number will + /// be 1. + virtual bool EncryptedSegmentPmt(BufferWriter* writer) = 0; + + /// This is the same as ClearLeadSegmentPmt() but does not append the extra PMT + /// for encrypted segments that the clear segments. IOW use this if the entire + /// stream is in the clear. + virtual bool ClearSegmentPmt(BufferWriter* writer) = 0; + + // 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. + static const uint8_t kPmtPid = 0x20; + + // This is arbitrary number that is not reserved by the spec. + static const uint8_t kElementaryPid = 0x50; +}; + +/// This is not a general purpose PMT writer. This is intended to be used by +/// TsWriter. +class H264ProgramMapTableWriter : public ProgramMapTableWriter { + public: + explicit H264ProgramMapTableWriter(ContinuityCounter* continuity_counter); + ~H264ProgramMapTableWriter() override; + + bool ClearLeadSegmentPmt(BufferWriter* writer) override; + bool EncryptedSegmentPmt(BufferWriter* writer) override; + bool ClearSegmentPmt(BufferWriter* writer) override; + + private: + ContinuityCounter* const continuity_counter_; + + DISALLOW_COPY_AND_ASSIGN(H264ProgramMapTableWriter); +}; + +// TODO(rkuroiwa): For now just handle AAC, we would want AudioProgramMapTable +// later when we support other audio codecs. +/// This is not a general purpose PMT writer. This is intended to be used by +/// TsWriter. +class AacProgramMapTableWriter : public ProgramMapTableWriter { + public: + AacProgramMapTableWriter( + const std::vector& aac_audio_specific_config, + ContinuityCounter* continuity_counter); + ~AacProgramMapTableWriter() override; + + bool ClearLeadSegmentPmt(BufferWriter* writer) override; + bool EncryptedSegmentPmt(BufferWriter* writer) override; + bool ClearSegmentPmt(BufferWriter* writer) override; + + private: + bool EncryptedSegmentPmtWithParameters(int version, + int current_next_indicator, + BufferWriter* writer); + + const std::vector aac_audio_specific_config_; + ContinuityCounter* const continuity_counter_; + + DISALLOW_COPY_AND_ASSIGN(AacProgramMapTableWriter); +}; + +} // namespace mp2t +} // namespace media +} // namespace edash_packager + +#endif // PACKAGER_MEDIA_FORMATS_MP2T_PROGRAM_MAP_TABLE_WRITER_H_ diff --git a/packager/media/formats/mp2t/program_map_table_writer_unittest.cc b/packager/media/formats/mp2t/program_map_table_writer_unittest.cc new file mode 100644 index 0000000000..9eb0f78e87 --- /dev/null +++ b/packager/media/formats/mp2t/program_map_table_writer_unittest.cc @@ -0,0 +1,402 @@ +// 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 + +#include "packager/media/base/buffer_writer.h" +#include "packager/media/formats/mp2t/continuity_counter.h" +#include "packager/media/formats/mp2t/program_map_table_writer.h" + +namespace edash_packager { +namespace media { +namespace mp2t { + +namespace { +const size_t kTsPacketSize = 188; +const uint8_t kAacBasicProfileExtraData[] = {0x12, 0x10}; +} // namespace + +class ProgramMapTableWriterTest : public ::testing::Test { + protected: + 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); + } +}; + +TEST_F(ProgramMapTableWriterTest, ClearH264) { + ContinuityCounter counter; + H264ProgramMapTableWriter writer(&counter); + BufferWriter buffer; + writer.ClearSegmentPmt(&buffer); + + 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. + 0x43, 0x49, 0x97, 0xbe, + }; + + ASSERT_EQ(kTsPacketSize, buffer.Size()); + EXPECT_NO_FATAL_FAILURE( + ExpectTsPacketEqual(kExpectedPmtPrefix, kExpectedPmtPrefixSize, 160, + kPmtH264, arraysize(kPmtH264), buffer.Buffer())); +} + +TEST_F(ProgramMapTableWriterTest, ClearLeadH264) { + ContinuityCounter counter; + H264ProgramMapTableWriter writer(&counter); + BufferWriter buffer; + writer.ClearLeadSegmentPmt(&buffer); + + EXPECT_EQ(kTsPacketSize * 2, buffer.Size()); + + // First PMT is for the clear lead segments. + const uint8_t kFirstTsPrefix[] = { + 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 uint8_t kClearPmtH264[] = { + 0x00, // pointer field + 0x02, + 0xB0, // assumes length is <= 256 bytes. + 0x12, // length of the rest of this array. + 0x00, 0x01, // program number. + 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. + 0x43, 0x49, 0x97, 0xbe, + }; + EXPECT_NO_FATAL_FAILURE(ExpectTsPacketEqual( + kFirstTsPrefix, arraysize(kFirstTsPrefix), 160, kClearPmtH264, + arraysize(kClearPmtH264), buffer.Buffer())); + + // Second PMT is for the encrypted segments after clear lead. + const uint8_t kSecondTsPrefix[] = { + 0x47, // Sync byte. + 0x40, // payload_unit_start_indicator set. + 0x20, // pid. + 0x31, // Adaptation field and payload are both present. counter = 1. + 0x9B, // Adaptation Field length. + 0x00, // All adaptation field flags 0. + }; + const uint8_t kPmtForClearLeadEncryptedH264[] = { + 0x00, // pointer field + 0x02, // table id. + 0xB0, // The first 4 bits must be '1011'. + 0x18, // length of the rest of this array. + 0x00, 0x01, // Program number. + 0xC2, // version 1, current next indicator 0. + 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. + 0xDB, 0xE0, 0x50, // stream_type -> PID. + 0xF0, 0x06, // Es_info_length is 6 for private_data_indicator + 0x0F, // private_data_indicator descriptor_tag. + 0x04, // Length of the rest of this descriptor + 0x7A, 0x61, 0x76, 0x63, // 'zavc'. + // CRC32. + 0x2E, 0xAB, 0xF2, 0x54, + }; + + EXPECT_NO_FATAL_FAILURE(ExpectTsPacketEqual( + kSecondTsPrefix, arraysize(kSecondTsPrefix), 154, + kPmtForClearLeadEncryptedH264, arraysize(kPmtForClearLeadEncryptedH264), + buffer.Buffer() + kTsPacketSize)); +} + + +TEST_F(ProgramMapTableWriterTest, EncryptedSegmentsH264Pmt) { + ContinuityCounter counter; + H264ProgramMapTableWriter writer(&counter); + BufferWriter buffer; + writer.EncryptedSegmentPmt(&buffer); + + EXPECT_EQ(kTsPacketSize, buffer.Size()); + + const uint8_t kPmtEncryptedH264Prefix[] = { + 0x47, // Sync byte. + 0x40, // payload_unit_start_indicator set. + 0x20, // pid. + 0x30, // Adaptation field and payload are both present. counter = 0. + 0x9B, // Adaptation Field length. + 0x00, // All adaptation field flags 0. + }; + + const uint8_t kPmtEncryptedH264[] = { + 0x00, // pointer field + 0x02, // Table id. + 0xB0, // The first 4 bits must be '1011'. + 0x18, // length of the rest of this array. + 0x00, 0x01, // program number. + 0xC3, // version 1, 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. + 0xDB, 0xE0, 0x50, // stream_type -> PID. + 0xF0, 0x06, // Es_info_length is 6 for private_data_indicator + 0x0F, // descriptor_tag. + 0x04, // Length of the rest of this descriptor + 0x7A, 0x61, 0x76, 0x63, // 'zavc'. + // CRC32. + 0xAF, 0xCC, 0x24, 0x21, + }; + EXPECT_NO_FATAL_FAILURE(ExpectTsPacketEqual( + kPmtEncryptedH264Prefix, arraysize(kPmtEncryptedH264Prefix), 154, + kPmtEncryptedH264, arraysize(kPmtEncryptedH264), buffer.Buffer())); +} + +TEST_F(ProgramMapTableWriterTest, ClearAac) { + ContinuityCounter counter; + const std::vector aac_audio_specific_config( + kAacBasicProfileExtraData, + kAacBasicProfileExtraData + arraysize(kAacBasicProfileExtraData)); + AacProgramMapTableWriter writer(aac_audio_specific_config, &counter); + BufferWriter buffer; + writer.ClearSegmentPmt(&buffer); + + 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 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, 0x01, // program number. + 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. + 0x0F, 0xE0, 0x50, // stream_type -> PID. + 0xF0, 0x00, // Es_info_length is 0. + // CRC32. + 0xE0, 0x6F, 0x1A, 0x31, + }; + + EXPECT_NO_FATAL_FAILURE( + ExpectTsPacketEqual(kExpectedPmtPrefix, arraysize(kExpectedPmtPrefix), + 160, kPmtAac, arraysize(kPmtAac), buffer.Buffer())); +} + +TEST_F(ProgramMapTableWriterTest, ClearLeadAac) { + ContinuityCounter counter; + const std::vector aac_audio_specific_config( + kAacBasicProfileExtraData, + kAacBasicProfileExtraData + arraysize(kAacBasicProfileExtraData)); + AacProgramMapTableWriter writer(aac_audio_specific_config, &counter); + BufferWriter buffer; + writer.ClearLeadSegmentPmt(&buffer); + + EXPECT_EQ(kTsPacketSize * 2, buffer.Size()); + + // First PMT is for the clear lead segments. + const uint8_t kFirstTsPrefix[] = { + 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 uint8_t kClearPmtAac[] = { + 0x00, // pointer field + 0x02, // table id must be 0x02. + 0xB0, // assumes length is <= 256 bytes. + 0x12, // length of the rest of this array. + 0x00, 0x01, // program number. + 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. + 0x0F, 0xE0, 0x50, // stream_type -> PID. + 0xF0, 0x00, // Es_info_length is 0. + // CRC32. + 0xE0, 0x6F, 0x1A, 0x31, + }; + + EXPECT_NO_FATAL_FAILURE(ExpectTsPacketEqual( + kFirstTsPrefix, arraysize(kFirstTsPrefix), + 160, kClearPmtAac, arraysize(kClearPmtAac), + buffer.Buffer())); + + // Second PMT is for the encrypted segments after clear lead. + const uint8_t kSecondTsPrefix[] = { + 0x47, // Sync byte. + 0x40, // payload_unit_start_indicator set. + 0x20, // pid. + 0x31, // Adaptation field and payload are both present. counter = 1. + 0x8B, // Adaptation Field length. + 0x00, // All adaptation field flags 0. + }; + const uint8_t kPmtForClearLeadEncryptedAac[] = { + 0x00, // pointer field + 0x02, // table id. + 0xB0, // The first 4 bits must be '1011'. + 0x28, // length of the rest of this array. + 0x00, 0x01, // Program number. + 0xC2, // version 1, current next indicator 0. + 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. + 0xCF, 0xE0, 0x50, // stream_type -> PID. + 0xF0, 0x16, // Es_info_length is 5 for private_data_indicator + 0x0F, // private_data_indicator descriptor_tag. + 0x04, // Length of the rest of this descriptor + 0x61, 0x61, 0x63, 0x64, // 'aacd'. + 0x05, // registration_descriptor tag. + // space for 'zaac' + priming (0x0000) + version (0x01) + + // setup_data_length size + size of kAacBasicProfileExtraData + space for + // 'apad'. Which is 14. + 0x0E, + 0x61, 0x70, 0x61, 0x64, // 'apad'. + 0x7A, 0x61, 0x61, 0x63, // 'zaac'. + 0x00, 0x00, // priming. + 0x01, // version. + 0x02, // setup_data_length == extra data length + 0x12, 0x10, // setup_data == extra data. + // CRC32. + 0x5C, 0x60, 0xB2, 0x55, + }; + + EXPECT_NO_FATAL_FAILURE(ExpectTsPacketEqual( + kSecondTsPrefix, arraysize(kSecondTsPrefix), 138, + kPmtForClearLeadEncryptedAac, arraysize(kPmtForClearLeadEncryptedAac), + buffer.Buffer() + kTsPacketSize)); +} + +TEST_F(ProgramMapTableWriterTest, EncryptedSegmentsAacPmt) { + ContinuityCounter counter; + const std::vector aac_audio_specific_config( + kAacBasicProfileExtraData, + kAacBasicProfileExtraData + arraysize(kAacBasicProfileExtraData)); + AacProgramMapTableWriter writer(aac_audio_specific_config, &counter); + BufferWriter buffer; + writer.EncryptedSegmentPmt(&buffer); + + EXPECT_EQ(kTsPacketSize, buffer.Size()); + + // Second PMT is for the encrypted segments after clear lead. + const uint8_t kPmtEncryptedAacPrefix[] = { + 0x47, // Sync byte. + 0x40, // payload_unit_start_indicator set. + 0x20, // pid. + 0x30, // Adaptation field and payload are both present. counter = 0. + 0x8B, // Adaptation Field length. + 0x00, // All adaptation field flags 0. + }; + const uint8_t kPmtEncryptedAac[] = { + 0x00, // pointer field + 0x02, // table id. + 0xB0, // The first 4 bits must be '1011'. + 0x28, // length of the rest of this array. + 0x00, 0x01, // Program number. + 0xC3, // version 1, 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. + 0xCF, 0xE0, 0x50, // stream_type -> PID. + 0xF0, 0x16, // Es_info_length is 5 for private_data_indicator + 0x0F, // private_data_indicator descriptor_tag. + 0x04, // Length of the rest of this descriptor + 0x61, 0x61, 0x63, 0x64, // 'aacd'. + 0x05, // registration_descriptor tag. + // space for 'zaac' + priming (0x0000) + version (0x01) + + // setup_data_length size + size of kAacBasicProfileExtraData + space for + // 'apad'. Which is 14. + 0x0E, + 0x61, 0x70, 0x61, 0x64, // 'apad'. + 0x7A, 0x61, 0x61, 0x63, // 'zaac'. + 0x00, 0x00, // priming. + 0x01, // version. + 0x02, // setup_data_length == extra data length + 0x12, 0x10, // setup_data == extra data. + // CRC32. + 0xC6, 0xB3, 0x31, 0x3A, + }; + + EXPECT_NO_FATAL_FAILURE(ExpectTsPacketEqual( + kPmtEncryptedAacPrefix, arraysize(kPmtEncryptedAacPrefix), 138, + kPmtEncryptedAac, arraysize(kPmtEncryptedAac), + buffer.Buffer())); +} + +} // namespace mp2t +} // namespace media +} // namespace edash_packager diff --git a/packager/media/formats/mp2t/ts_packet_writer_util.cc b/packager/media/formats/mp2t/ts_packet_writer_util.cc new file mode 100644 index 0000000000..2be98bd6a5 --- /dev/null +++ b/packager/media/formats/mp2t/ts_packet_writer_util.cc @@ -0,0 +1,165 @@ +// 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_packet_writer_util.h" + +#include "packager/base/logging.h" +#include "packager/media/base/buffer_writer.h" +#include "packager/media/formats/mp2t/continuity_counter.h" + +namespace edash_packager { +namespace media { +namespace mp2t { + +namespace { + +const int kPcrFieldsSize = 6; +const uint8_t kSyncByte = 0x47; + +// 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; + +// 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); +} + +} // namespace + +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); +} + +} // namespace mp2t +} // namespace media +} // namespace edash_packager diff --git a/packager/media/formats/mp2t/ts_packet_writer_util.h b/packager/media/formats/mp2t/ts_packet_writer_util.h new file mode 100644 index 0000000000..ca92c76dc9 --- /dev/null +++ b/packager/media/formats/mp2t/ts_packet_writer_util.h @@ -0,0 +1,47 @@ +// 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 + +// This file contains utility functions that help write TS packets to buffer. + +#ifndef PACKAGER_MEDIA_FORMATS_MP2T_TS_WRITER_UTIL_H_ +#define PACKAGER_MEDIA_FORMATS_MP2T_TS_WRITER_UTIL_H_ + +#include +#include + +namespace edash_packager { +namespace media { + +class BufferWriter; + +namespace mp2t { + +class ContinuityCounter; + +/// General purpose TS packet writing function. The output goes to @a output. +/// @param payload can be any payload. Most likely raw PSI tables or PES packet +/// payload. +/// @param payload_size is the size of payload. +/// @param payload_unit_start_indicator is the same as the definition in spec. +/// @param pid is the same the same defition in the spec. +/// @param has_pcr is true if @a pcr_base should be used. +/// @param pcr_base is the PCR_base value in the spec. +/// @param continuity_counter is the continuity_counter for this TS packet. +/// @param output is where the TS packets get written. +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* output); + +} // namespace mp2t +} // namespace media +} // namespace edash_packager + +#endif // PACKAGER_MEDIA_FORMATS_MP2T_TS_WRITER_UTIL_H_ diff --git a/packager/media/formats/mp2t/ts_segmenter.cc b/packager/media/formats/mp2t/ts_segmenter.cc index 5910f8232d..099202b948 100644 --- a/packager/media/formats/mp2t/ts_segmenter.cc +++ b/packager/media/formats/mp2t/ts_segmenter.cc @@ -30,7 +30,7 @@ TsSegmenter::~TsSegmenter() {} Status TsSegmenter::Initialize(const StreamInfo& stream_info) { if (muxer_options_.segment_template.empty()) return Status(error::MUXER_FAILURE, "Segment template not specified."); - if (!ts_writer_->Initialize(stream_info)) + if (!ts_writer_->Initialize(stream_info, false)) return Status(error::MUXER_FAILURE, "Failed to initialize TsWriter."); if (!pes_packet_generator_->Initialize(stream_info)) { return Status(error::MUXER_FAILURE, diff --git a/packager/media/formats/mp2t/ts_segmenter_unittest.cc b/packager/media/formats/mp2t/ts_segmenter_unittest.cc index 07a29e3c42..2535007b58 100644 --- a/packager/media/formats/mp2t/ts_segmenter_unittest.cc +++ b/packager/media/formats/mp2t/ts_segmenter_unittest.cc @@ -63,7 +63,8 @@ class MockPesPacketGenerator : public PesPacketGenerator { class MockTsWriter : public TsWriter { public: - MOCK_METHOD1(Initialize, bool(const StreamInfo& stream_info)); + MOCK_METHOD2(Initialize, + bool(const StreamInfo& stream_info, bool will_be_encrypted)); MOCK_METHOD1(NewSegment, bool(const std::string& file_name)); MOCK_METHOD0(FinalizeSegment, bool()); @@ -97,7 +98,7 @@ TEST_F(TsSegmenterTest, Initialize) { options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); - EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true)); + EXPECT_CALL(*mock_ts_writer_, Initialize(_, _)).WillOnce(Return(true)); EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) .WillOnce(Return(true)); @@ -118,7 +119,7 @@ TEST_F(TsSegmenterTest, AddSample) { options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); - EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true)); + EXPECT_CALL(*mock_ts_writer_, Initialize(_, _)).WillOnce(Return(true)); EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) .WillOnce(Return(true)); @@ -180,7 +181,7 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) { const uint32_t kFirstPts = 1000; - EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true)); + EXPECT_CALL(*mock_ts_writer_, Initialize(_, _)).WillOnce(Return(true)); EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) .WillOnce(Return(true)); @@ -278,7 +279,7 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) { options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); - EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true)); + EXPECT_CALL(*mock_ts_writer_, Initialize(_, _)).WillOnce(Return(true)); EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) .WillOnce(Return(true)); @@ -307,7 +308,7 @@ TEST_F(TsSegmenterTest, Finalize) { options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); - EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true)); + EXPECT_CALL(*mock_ts_writer_, Initialize(_, _)).WillOnce(Return(true)); EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) .WillOnce(Return(true)); @@ -336,7 +337,7 @@ TEST_F(TsSegmenterTest, SegmentOnlyBeforeKeyFrame) { options.segment_template = "file$Number$.ts"; TsSegmenter segmenter(options, nullptr); - EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true)); + EXPECT_CALL(*mock_ts_writer_, Initialize(_, _)).WillOnce(Return(true)); EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_)) .WillOnce(Return(true)); diff --git a/packager/media/formats/mp2t/ts_writer.cc b/packager/media/formats/mp2t/ts_writer.cc index 830b56acdd..c126583d70 100644 --- a/packager/media/formats/mp2t/ts_writer.cc +++ b/packager/media/formats/mp2t/ts_writer.cc @@ -13,6 +13,7 @@ #include "packager/media/base/buffer_writer.h" #include "packager/media/base/stream_info.h" #include "packager/media/base/video_stream_info.h" +#include "packager/media/formats/mp2t/ts_packet_writer_util.h" namespace edash_packager { namespace media { @@ -20,32 +21,12 @@ 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. +// This PAT can be used for both encrypted and clear. const uint8_t kPat[] = { 0x00, // pointer field kProgramAssociationTableId, @@ -58,57 +39,14 @@ const uint8_t kPat[] = { // program number -> PMT PID mapping. 0x00, 0x01, // program number is 1. 0xE0, // first 3 bits is reserved. - kPmtPid, + ProgramMapTableWriter::kPmtPid, // CRC32. 0xF9, 0x62, 0xF5, 0x8B, }; -// 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. - 0x43, 0x49, 0x97, 0xBE, -}; - -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. - 0xE0, 0x6F, 0x1A, 0x31, -}; - 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; @@ -118,162 +56,13 @@ const int kTsPacketMaximumPayloadSize = 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); + WritePayloadToBufferWriter(pat, pat_size, kPayloadUnitStartIndicator, kPatPid, + !kHasPcr, 0, continuity_counter, writer); } // The only difference between writing PTS or DTS is the leading bits. @@ -310,7 +99,7 @@ bool WritePesToFile(const PesPacket& pes, kTsPacketMaximumPayloadSize - kAdaptationFieldLengthSize - kAdaptationFieldHeaderSize - kPcrFieldSize; const uint64_t pcr_base = pes.has_dts() ? pes.dts() : pes.pts(); - const int pid = kElementaryPid; + const int pid = ProgramMapTableWriter::kElementaryPid; // This writer will hold part of PES packet after PES_packet_length field. BufferWriter pes_header_writer; @@ -370,27 +159,11 @@ bool WritePesToFile(const PesPacket& pes, } // 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; - } +bool TsWriter::Initialize(const StreamInfo& stream_info, + bool will_be_encrypted) { const StreamType stream_type = stream_info.stream_type(); if (stream_type != StreamType::kStreamVideo && stream_type != StreamType::kStreamAudio) { @@ -399,8 +172,6 @@ bool TsWriter::Initialize(const StreamInfo& stream_info) { 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); @@ -409,8 +180,7 @@ bool TsWriter::Initialize(const StreamInfo& stream_info) { << video_stream_info.codec() << " yet."; return false; } - pmt = kPmtH264; - pmt_size = arraysize(kPmtH264); + pmt_writer_.reset(new H264ProgramMapTableWriter(&pmt_continuity_counter_)); } else { DCHECK_EQ(stream_type, StreamType::kStreamAudio); const AudioStreamInfo& audio_stream_info = @@ -420,24 +190,15 @@ bool TsWriter::Initialize(const StreamInfo& stream_info) { << audio_stream_info.codec() << " yet."; return false; } - pmt = kPmtAac; - pmt_size = arraysize(kPmtAac); + pmt_writer_.reset(new AacProgramMapTableWriter( + audio_stream_info.extra_data(), &pmt_continuity_counter_)); } - 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_); + will_be_encrypted_ = will_be_encrypted; 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; @@ -448,10 +209,23 @@ bool TsWriter::NewSegment(const std::string& 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()) { + BufferWriter psi; + WritePatToBuffer(kPat, arraysize(kPat), &pat_continuity_counter_, &psi); + if (will_be_encrypted_ && !encrypted_) { + if (!pmt_writer_->ClearLeadSegmentPmt(&psi)) { + return false; + } + } else if (encrypted_) { + if (!pmt_writer_->EncryptedSegmentPmt(&psi)) { + return false; + } + } else { + if (!pmt_writer_->ClearSegmentPmt(&psi)) { + return false; + } + } + + if (!psi.WriteToFile(current_file_.get()).ok()) { LOG(ERROR) << "Failed to write PSI to file."; return false; } @@ -459,15 +233,15 @@ bool TsWriter::NewSegment(const std::string& file_name) { return true; } +void TsWriter::SignalEncypted() { + encrypted_ = 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())) { @@ -479,6 +253,11 @@ bool TsWriter::AddPesPacket(scoped_ptr pes_packet) { return true; } +void TsWriter::SetProgramMapTableWriterForTesting( + scoped_ptr table_writer) { + pmt_writer_ = table_writer.Pass(); +} + } // 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 index 9584e9482e..ffcbf46ea9 100644 --- a/packager/media/formats/mp2t/ts_writer.h +++ b/packager/media/formats/mp2t/ts_writer.h @@ -15,27 +15,14 @@ #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/continuity_counter.h" #include "packager/media/formats/mp2t/pes_packet.h" +#include "packager/media/formats/mp2t/program_map_table_writer.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 { @@ -44,14 +31,24 @@ class TsWriter { virtual ~TsWriter(); /// This must be called before calling other methods. + /// @param stream_info is the information about this stream. + /// @param will_be_encrypted must be true if some segment would be encrypted. + /// It is ok if the entire stream is not encrypted but have this true + /// e.g. if the clear lead is very long. /// @return true on success, false otherwise. - virtual bool Initialize(const StreamInfo& stream_info); + virtual bool Initialize(const StreamInfo& stream_info, + bool will_be_encrypted); /// This will fail if the current segment is not finalized. /// @param file_name is the output file name. + /// @param encrypted must be true if the new segment is encrypted. /// @return true on success, false otherwise. virtual bool NewSegment(const std::string& file_name); + /// Signals the writer that the rest of the segments are encrypted. + /// |will_be_encrypted| passed to Initialize() should be true. + void SignalEncypted(); + /// Flush all the pending PesPackets that have not been written to file and /// close the file. /// @return true on success, false otherwise. @@ -63,15 +60,22 @@ class TsWriter { /// @return true on success, false otherwise. virtual bool AddPesPacket(scoped_ptr pes_packet); - private: - std::vector psi_ts_packets_; + /// Only for testing. + void SetProgramMapTableWriterForTesting( + scoped_ptr table_writer); - uint32_t time_scale_ = 0u; + private: + // True if further segments generated by this instance should be encrypted. + bool encrypted_ = false; + // The stream will be encrypted some time later. + bool will_be_encrypted_ = false; ContinuityCounter pmt_continuity_counter_; ContinuityCounter pat_continuity_counter_; ContinuityCounter elementary_stream_continuity_counter_; + scoped_ptr pmt_writer_; + scoped_ptr current_file_; DISALLOW_COPY_AND_ASSIGN(TsWriter); diff --git a/packager/media/formats/mp2t/ts_writer_unittest.cc b/packager/media/formats/mp2t/ts_writer_unittest.cc index 541230a2f4..caa18c68fc 100644 --- a/packager/media/formats/mp2t/ts_writer_unittest.cc +++ b/packager/media/formats/mp2t/ts_writer_unittest.cc @@ -4,15 +4,21 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd +#include #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/buffer_writer.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" +using ::testing::InSequence; +using ::testing::Return; +using ::testing::_; + namespace edash_packager { namespace media { namespace mp2t { @@ -43,10 +49,57 @@ const uint32_t kSamplingFrequency = 44100; const uint32_t kMaxBitrate = 320000; const uint32_t kAverageBitrate = 256000; -const uint8_t kExtraData[] = { - 0x01, 0x02, +// Bogus extra data. +const uint8_t kExtraData[] = {0x01, 0x02}; + +const uint8_t kAacBasicProfileExtraData[] = {0x12, 0x10}; + +const bool kWillBeEncrypted = true; + +class MockProgramMapTableWriter : public ProgramMapTableWriter { + public: + MockProgramMapTableWriter() {} + ~MockProgramMapTableWriter() override {} + + MOCK_METHOD1(ClearLeadSegmentPmt, bool(BufferWriter* writer)); + MOCK_METHOD1(EncryptedSegmentPmt, bool(BufferWriter* writer)); + MOCK_METHOD1(ClearSegmentPmt, bool(BufferWriter* writer)); }; +// This is not a real TS Packet. But is used to check that the result from the +// MockProgramMapTableWriter is used at the right place. +const uint8_t kMockPmtWriterData[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, + 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, + 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, + 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, + 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, +}; + +ACTION(WriteOnePmt) { + BufferWriter* writer = arg0; + writer->AppendArray(kMockPmtWriterData, arraysize(kMockPmtWriterData)); + return true; +} + +ACTION(WriteTwoPmts) { + BufferWriter* writer = arg0; + writer->AppendArray(kMockPmtWriterData, arraysize(kMockPmtWriterData)); + writer->AppendArray(kMockPmtWriterData, arraysize(kMockPmtWriterData)); + return true; +} + } // namespace class TsWriterTest : public ::testing::Test { @@ -76,9 +129,11 @@ class TsWriterTest : public ::testing::Test { // 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, + void ExpectTsPacketEqual(const uint8_t* prefix, + size_t prefix_size, int padding_length, - const uint8_t* suffix, size_t suffix_size, + 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), @@ -106,7 +161,7 @@ TEST_F(TsWriterTest, InitializeVideoH264) { 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_.Initialize(*stream_info, !kWillBeEncrypted)); } TEST_F(TsWriterTest, InitializeVideoNonH264) { @@ -114,7 +169,7 @@ TEST_F(TsWriterTest, InitializeVideoNonH264) { kTrackId, kTimeScale, kDuration, VideoCodec::kCodecVP9, kCodecString, kLanguage, kWidth, kHeight, kPixelWidth, kPixelHeight, kTrickPlayRate, kNaluLengthSize, kExtraData, arraysize(kExtraData), kIsEncrypted)); - EXPECT_FALSE(ts_writer_.Initialize(*stream_info)); + EXPECT_FALSE(ts_writer_.Initialize(*stream_info, !kWillBeEncrypted)); } TEST_F(TsWriterTest, InitializeAudioAac) { @@ -122,7 +177,7 @@ TEST_F(TsWriterTest, InitializeAudioAac) { kTrackId, kTimeScale, kDuration, kAacAudioCodec, kCodecString, kLanguage, kSampleBits, kNumChannels, kSamplingFrequency, kMaxBitrate, kAverageBitrate, kExtraData, arraysize(kExtraData), kIsEncrypted)); - EXPECT_TRUE(ts_writer_.Initialize(*stream_info)); + EXPECT_TRUE(ts_writer_.Initialize(*stream_info, !kWillBeEncrypted)); } TEST_F(TsWriterTest, InitializeAudioNonAac) { @@ -130,30 +185,39 @@ TEST_F(TsWriterTest, InitializeAudioNonAac) { kTrackId, kTimeScale, kDuration, AudioCodec::kCodecOpus, kCodecString, kLanguage, kSampleBits, kNumChannels, kSamplingFrequency, kMaxBitrate, kAverageBitrate, kExtraData, arraysize(kExtraData), kIsEncrypted)); - EXPECT_FALSE(ts_writer_.Initialize(*stream_info)); + EXPECT_FALSE(ts_writer_.Initialize(*stream_info, !kWillBeEncrypted)); } -TEST_F(TsWriterTest, NewSegment) { +// Verify that PAT and PMT are correct for clear segment. +// This test covers verifies the PAT, and since it doesn't change, other tests +// shouldn't have to check this. +TEST_F(TsWriterTest, ClearH264Psi) { + scoped_ptr mock_pmt_writer( + new MockProgramMapTableWriter()); + EXPECT_CALL(*mock_pmt_writer, ClearSegmentPmt(_)).WillOnce(WriteOnePmt()); + 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_.Initialize(*stream_info, !kWillBeEncrypted)); + + ts_writer_.SetProgramMapTableWriterForTesting(mock_pmt_writer.Pass()); 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. + // 2 TS Packets one for PAT and the fake PMT data. 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. + 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[] = { @@ -177,45 +241,165 @@ TEST_F(TsWriterTest, NewSegment) { 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. - 0x43, 0x49, 0x97, 0xbe, - }; - - EXPECT_NO_FATAL_FAILURE(ExpectTsPacketEqual( - kExpectedPmtPrefix, kExpectedPmtPrefixSize, 160, kPmtH264, - arraysize(kPmtH264), content.data() + kTsPacketSize)); + EXPECT_EQ(0, memcmp(kMockPmtWriterData, content.data() + kTsPacketSize, + kTsPacketSize)); } +TEST_F(TsWriterTest, ClearAacPmt) { + scoped_ptr mock_pmt_writer( + new MockProgramMapTableWriter()); + EXPECT_CALL(*mock_pmt_writer, ClearSegmentPmt(_)).WillOnce(WriteOnePmt()); + + scoped_refptr stream_info(new AudioStreamInfo( + kTrackId, kTimeScale, kDuration, kAacAudioCodec, kCodecString, kLanguage, + kSampleBits, kNumChannels, kSamplingFrequency, kMaxBitrate, + kAverageBitrate, kAacBasicProfileExtraData, + arraysize(kAacBasicProfileExtraData), kIsEncrypted)); + EXPECT_TRUE(ts_writer_.Initialize(*stream_info, !kWillBeEncrypted)); + + ts_writer_.SetProgramMapTableWriterForTesting(mock_pmt_writer.Pass()); + 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()); + + EXPECT_EQ(0, memcmp(kMockPmtWriterData, content.data() + kTsPacketSize, + kTsPacketSize)); +} + +// The stream is flaged with will_be_encrypted. Verify that 2 PMTs are created. +// One for clear lead and another for encrypted segments that follow. +TEST_F(TsWriterTest, ClearLeadH264Pmt) { + scoped_ptr mock_pmt_writer( + new MockProgramMapTableWriter()); + EXPECT_CALL(*mock_pmt_writer, ClearLeadSegmentPmt(_)) + .WillOnce(WriteTwoPmts()); + + 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, kWillBeEncrypted)); + + ts_writer_.SetProgramMapTableWriterForTesting(mock_pmt_writer.Pass()); + EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_)); + EXPECT_TRUE(ts_writer_.FinalizeSegment()); + + std::vector content; + ASSERT_TRUE(ReadFileToVector(test_file_path_, &content)); + + ASSERT_EQ(564u, content.size()); + + EXPECT_EQ(0, memcmp(kMockPmtWriterData, content.data() + kTsPacketSize, + kTsPacketSize)); + EXPECT_EQ(0, memcmp(kMockPmtWriterData, content.data() + 2 * kTsPacketSize, + kTsPacketSize)); +} + +// Check the encrypted segments' PMT (after clear lead). +TEST_F(TsWriterTest, EncryptedSegmentsH264Pmt) { + scoped_ptr mock_pmt_writer( + new MockProgramMapTableWriter()); + InSequence s; + EXPECT_CALL(*mock_pmt_writer, ClearLeadSegmentPmt(_)).WillOnce(Return(true)); + EXPECT_CALL(*mock_pmt_writer, EncryptedSegmentPmt(_)).WillOnce(WriteOnePmt()); + + 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, kWillBeEncrypted)); + + ts_writer_.SetProgramMapTableWriterForTesting(mock_pmt_writer.Pass()); + EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_)); + EXPECT_TRUE(ts_writer_.FinalizeSegment()); + + // Overwrite the file but as encrypted segment. + ts_writer_.SignalEncypted(); + EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_)); + EXPECT_TRUE(ts_writer_.FinalizeSegment()); + + std::vector content; + ASSERT_TRUE(ReadFileToVector(test_file_path_, &content)); + + ASSERT_EQ(376u, content.size()); + + EXPECT_EQ(0, memcmp(kMockPmtWriterData, content.data() + kTsPacketSize, + kTsPacketSize)); +} + +// Same as ClearLeadH264Pmt but for AAC. +TEST_F(TsWriterTest, ClearLeadAacPmt) { + scoped_ptr mock_pmt_writer( + new MockProgramMapTableWriter()); + EXPECT_CALL(*mock_pmt_writer, ClearLeadSegmentPmt(_)) + .WillOnce(WriteTwoPmts()); + + scoped_refptr stream_info(new AudioStreamInfo( + kTrackId, kTimeScale, kDuration, kAacAudioCodec, kCodecString, kLanguage, + kSampleBits, kNumChannels, kSamplingFrequency, kMaxBitrate, + kAverageBitrate, kAacBasicProfileExtraData, + arraysize(kAacBasicProfileExtraData), kIsEncrypted)); + EXPECT_TRUE(ts_writer_.Initialize(*stream_info, kWillBeEncrypted)); + + ts_writer_.SetProgramMapTableWriterForTesting(mock_pmt_writer.Pass()); + EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_)); + ASSERT_TRUE(ts_writer_.FinalizeSegment()); + + std::vector content; + ASSERT_TRUE(ReadFileToVector(test_file_path_, &content)); + + ASSERT_EQ(564u, content.size()); + + EXPECT_EQ(0, memcmp(kMockPmtWriterData, content.data() + kTsPacketSize, + kTsPacketSize)); + EXPECT_EQ(0, memcmp(kMockPmtWriterData, content.data() + 2 * kTsPacketSize, + kTsPacketSize)); +} + +// Same as EncryptedSegmentsH264Pmt but for AAC. +TEST_F(TsWriterTest, EncryptedSegmentsAacPmt) { + scoped_ptr mock_pmt_writer( + new MockProgramMapTableWriter()); + InSequence s; + EXPECT_CALL(*mock_pmt_writer, ClearLeadSegmentPmt(_)).WillOnce(Return(true)); + EXPECT_CALL(*mock_pmt_writer, EncryptedSegmentPmt(_)).WillOnce(WriteOnePmt()); + + scoped_refptr stream_info(new AudioStreamInfo( + kTrackId, kTimeScale, kDuration, kAacAudioCodec, kCodecString, kLanguage, + kSampleBits, kNumChannels, kSamplingFrequency, kMaxBitrate, + kAverageBitrate, kAacBasicProfileExtraData, + arraysize(kAacBasicProfileExtraData), kIsEncrypted)); + EXPECT_TRUE(ts_writer_.Initialize(*stream_info, kWillBeEncrypted)); + + ts_writer_.SetProgramMapTableWriterForTesting(mock_pmt_writer.Pass()); + EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_)); + EXPECT_TRUE(ts_writer_.FinalizeSegment()); + + // Overwrite the file but as encrypted segment. + ts_writer_.SignalEncypted(); + EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_)); + EXPECT_TRUE(ts_writer_.FinalizeSegment()); + + std::vector content; + ASSERT_TRUE(ReadFileToVector(test_file_path_, &content)); + + ASSERT_EQ(376u, content.size()); + + EXPECT_EQ(0, memcmp(kMockPmtWriterData, content.data() + kTsPacketSize, + 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_.Initialize(*stream_info, !kWillBeEncrypted)); EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_)); scoped_ptr pes(new PesPacket()); @@ -280,7 +464,7 @@ TEST_F(TsWriterTest, BigPesPacket) { 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_.Initialize(*stream_info, !kWillBeEncrypted)); EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_)); scoped_ptr pes(new PesPacket()); @@ -305,8 +489,8 @@ TEST_F(TsWriterTest, BigPesPacket) { // 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)); + 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 @@ -316,7 +500,7 @@ TEST_F(TsWriterTest, PesPtsZeroNoDts) { 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_.Initialize(*stream_info, !kWillBeEncrypted)); EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_)); scoped_ptr pes(new PesPacket()); @@ -376,7 +560,7 @@ TEST_F(TsWriterTest, TsPacketPayload183Bytes) { 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_.Initialize(*stream_info, !kWillBeEncrypted)); EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_)); scoped_ptr pes(new PesPacket());