Support encrypted AC3 in TS

Implemented MPEG-2 Stream Encryption Format for HTTP Live Streaming
specified in https://goo.gl/N7Tvqi.

This change also moved ProgramMapTableWriter creation from TsWriter
to TsSegment.

Issue #165

Change-Id: Ia89dd16a5e6405706dd3d85ec6b6de580f7b13a7
This commit is contained in:
KongQun Yang 2017-10-23 15:59:30 -07:00
parent d7f531fe10
commit 72df5af150
8 changed files with 272 additions and 287 deletions

View File

@ -19,6 +19,9 @@ namespace mp2t {
namespace {
const size_t kTsPacketSize = 188;
const uint8_t kAacBasicProfileExtraData[] = {0x12, 0x10};
// Bogus data, the value does not matter.
const uint8_t kAc3SetupData[] = {0x00, 0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08, 0x09};
} // namespace
class ProgramMapTableWriterTest : public ::testing::Test {
@ -206,13 +209,49 @@ TEST_F(ProgramMapTableWriterTest, ClearAac) {
0x00, // No descriptor at this level.
0x0F, 0xE0, 0x50, // stream_type -> PID.
0xF0, 0x00, // Es_info_length is 0.
// CRC32.
0xE0, 0x6F, 0x1A, 0x31,
0xE0, 0x6F, 0x1A, 0x31, // CRC32.
};
EXPECT_NO_FATAL_FAILURE(
ExpectTsPacketEqual(kExpectedPmtPrefix, arraysize(kExpectedPmtPrefix),
160, kPmtAac, arraysize(kPmtAac), buffer.Buffer()));
}
TEST_F(ProgramMapTableWriterTest, ClearAc3) {
const std::vector<uint8_t> audio_specific_config(std::begin(kAc3SetupData),
std::end(kAc3SetupData));
AudioProgramMapTableWriter writer(kCodecAC3, audio_specific_config);
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 kPmtAc3[] = {
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.
0x81, 0xE0, 0x50, // stream_type -> PID.
0xF0, 0x00, // Es_info_length is 0.
0x1E, 0xFC, 0x57, 0x12, // CRC32.
};
EXPECT_NO_FATAL_FAILURE(
ExpectTsPacketEqual(kExpectedPmtPrefix, arraysize(kExpectedPmtPrefix),
160, kPmtAac, arraysize(kPmtAac), buffer.Buffer()));
160, kPmtAc3, arraysize(kPmtAc3), buffer.Buffer()));
}
// Verify that PSI for encrypted segments after clear lead is generated
@ -251,23 +290,21 @@ TEST_F(ProgramMapTableWriterTest, EncryptedSegmentsAfterClearLeadAac) {
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
0xF0, 0x16, // Es_info_length is 22 for descriptors.
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,
0x0E, // space for 'zaac' + priming (0x0000) + version (0x01) +
// setup_data_length size + size of kAacBasicProfileExtraData +
// space for 'apad'. Which is 14.
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,
0xC6, 0xB3, 0x31, 0x3A, // CRC32.
};
EXPECT_NO_FATAL_FAILURE(ExpectTsPacketEqual(
@ -310,23 +347,21 @@ TEST_F(ProgramMapTableWriterTest, EncryptedSegmentsAacPmt) {
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
0xF0, 0x16, // Es_info_length is 22 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,
0x0E, // space for 'zaac' + priming (0x0000) + version (0x01) +
// setup_data_length size + size of kAacBasicProfileExtraData +
// space for 'apad'. Which is 14.
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.
0xF7, 0xD5, 0x2A, 0x53,
0xF7, 0xD5, 0x2A, 0x53, // CRC32.
};
EXPECT_NO_FATAL_FAILURE(ExpectTsPacketEqual(
@ -335,6 +370,60 @@ TEST_F(ProgramMapTableWriterTest, EncryptedSegmentsAacPmt) {
buffer.Buffer()));
}
TEST_F(ProgramMapTableWriterTest, EncryptedSegmentsAc3Pmt) {
const std::vector<uint8_t> audio_specific_config(std::begin(kAc3SetupData),
std::end(kAc3SetupData));
AudioProgramMapTableWriter writer(kCodecAC3, audio_specific_config);
BufferWriter buffer;
writer.EncryptedSegmentPmt(&buffer);
EXPECT_EQ(kTsPacketSize, buffer.Size());
// Second PMT is for the encrypted segments after clear lead.
const uint8_t kPmtEncryptedAc3Prefix[] = {
0x47, // Sync byte.
0x40, // payload_unit_start_indicator set.
0x20, // pid.
0x30, // Adaptation field and payload are both present. counter = 0.
0x83, // Adaptation Field length.
0x00, // All adaptation field flags 0.
};
const uint8_t kPmtEncryptedAc3[] = {
0x00, // pointer field
0x02, // table id.
0xB0, // The first 4 bits must be '1011'.
0x30, // 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.
0xC1, 0xE0, 0x50, // stream_type -> PID.
0xF0, 0x1E, // Es_info_length is 30 for private_data_indicator
0x0F, // private_data_indicator descriptor_tag.
0x04, // Length of the rest of this descriptor
0x61, 0x63, 0x33, 0x64, // 'ac3d'.
0x05, // registration_descriptor tag.
0x16, // space for 'zac3' + priming (0x0000) + version (0x01) +
// setup_data_length size + size of kAc3SetupData + space for
// 'apad'. Which is 22.
0x61, 0x70, 0x61, 0x64, // 'apad'.
0x7A, 0x61, 0x63, 0x33, // 'zac3'.
0x00, 0x00, // priming.
0x01, // version.
0x0A, // setup_data_length
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // setup_data
0xCE, 0xB6, 0x52, 0x5C, // CRC32.
};
EXPECT_NO_FATAL_FAILURE(ExpectTsPacketEqual(
kPmtEncryptedAc3Prefix, arraysize(kPmtEncryptedAc3Prefix), 130,
kPmtEncryptedAc3, arraysize(kPmtEncryptedAc3), buffer.Buffer()));
}
} // namespace mp2t
} // namespace media
} // namespace shaka

View File

@ -8,8 +8,12 @@
#include <memory>
#include "packager/media/base/audio_stream_info.h"
#include "packager/media/base/muxer_util.h"
#include "packager/media/base/video_stream_info.h"
#include "packager/media/event/muxer_listener.h"
#include "packager/media/formats/mp2t/pes_packet.h"
#include "packager/media/formats/mp2t/program_map_table_writer.h"
#include "packager/status.h"
namespace shaka {
@ -18,25 +22,43 @@ namespace mp2t {
namespace {
const double kTsTimescale = 90000;
bool IsAudioCodec(Codec codec) {
return codec >= kCodecAudio && codec < kCodecAudioMaxPlusOne;
}
bool IsVideoCodec(Codec codec) {
return codec >= kCodecVideo && codec < kCodecVideoMaxPlusOne;
}
} // namespace
TsSegmenter::TsSegmenter(const MuxerOptions& options, MuxerListener* listener)
: muxer_options_(options),
listener_(listener),
ts_writer_(new TsWriter()),
pes_packet_generator_(new PesPacketGenerator()) {}
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))
return Status(error::MUXER_FAILURE, "Failed to initialize TsWriter.");
if (!pes_packet_generator_->Initialize(stream_info)) {
return Status(error::MUXER_FAILURE,
"Failed to initialize PesPacketGenerator.");
}
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 Status(error::MUXER_FAILURE, "Unsupported stream type.");
}
codec_ = stream_info.codec();
if (stream_type == StreamType::kStreamAudio)
audio_codec_config_ = stream_info.codec_config();
timescale_scale_ = kTsTimescale / stream_info.time_scale();
return Status::OK;
}
@ -46,6 +68,33 @@ Status TsSegmenter::Finalize() {
}
Status TsSegmenter::AddSample(const MediaSample& sample) {
if (!ts_writer_) {
std::unique_ptr<ProgramMapTableWriter> pmt_writer;
if (codec_ == kCodecAC3) {
// https://goo.gl/N7Tvqi MPEG-2 Stream Encryption Format for HTTP Live
// Streaming 2.3.2.2 AC-3 Setup: For AC-3, the setup_data in the
// audio_setup_information is the first 10 bytes of the audio data (the
// syncframe()).
// For unencrypted AC3, the setup_data is not used, so what is in there
// does not matter.
const size_t kSetupDataSize = 10u;
if (sample.data_size() < kSetupDataSize) {
LOG(ERROR) << "Sample is too small for AC3: " << sample.data_size();
return Status(error::MUXER_FAILURE, "Sample is too small for AC3.");
}
const std::vector<uint8_t> setup_data(sample.data(),
sample.data() + kSetupDataSize);
pmt_writer.reset(new AudioProgramMapTableWriter(codec_, setup_data));
} else if (IsAudioCodec(codec_)) {
pmt_writer.reset(
new AudioProgramMapTableWriter(codec_, audio_codec_config_));
} else {
DCHECK(IsVideoCodec(codec_));
pmt_writer.reset(new VideoProgramMapTableWriter(codec_));
}
ts_writer_.reset(new TsWriter(std::move(pmt_writer)));
}
if (sample.is_encrypted())
ts_writer_->SignalEncrypted();

View File

@ -80,6 +80,10 @@ class TsSegmenter {
const MuxerOptions& muxer_options_;
MuxerListener* const listener_;
// Codec for the stream.
Codec codec_ = kUnknownCodec;
std::vector<uint8_t> audio_codec_config_;
// Scale used to scale the input stream to TS's timesccale (which is 90000).
// Used for calculating the duration in seconds fo the current segment.
double timescale_scale_ = 1.0;

View File

@ -10,6 +10,8 @@
#include "packager/media/base/audio_stream_info.h"
#include "packager/media/base/video_stream_info.h"
#include "packager/media/event/mock_muxer_listener.h"
#include "packager/media/formats/mp2t/pes_packet.h"
#include "packager/media/formats/mp2t/program_map_table_writer.h"
#include "packager/media/formats/mp2t/ts_segmenter.h"
#include "packager/status_test_util.h"
@ -69,7 +71,11 @@ class MockPesPacketGenerator : public PesPacketGenerator {
class MockTsWriter : public TsWriter {
public:
MOCK_METHOD1(Initialize, bool(const StreamInfo& stream_info));
MockTsWriter()
: TsWriter(std::unique_ptr<ProgramMapTableWriter>(
// Create a bogus pmt writer, which we don't really care.
new VideoProgramMapTableWriter(kUnknownCodec))) {}
MOCK_METHOD1(NewSegment, bool(const std::string& file_name));
MOCK_METHOD0(SignalEncrypted, void());
MOCK_METHOD0(FinalizeSegment, bool());
@ -105,11 +111,9 @@ 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_pes_packet_generator_, Initialize(_))
.WillOnce(Return(true));
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_));
@ -126,7 +130,6 @@ 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_pes_packet_generator_, Initialize(_))
.WillOnce(Return(true));
@ -156,11 +159,11 @@ TEST_F(TsSegmenterTest, AddSample) {
EXPECT_CALL(*mock_pes_packet_generator_, GetNextPesPacketMock())
.WillOnce(Return(new PesPacket()));
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_));
EXPECT_OK(segmenter.Initialize(*stream_info));
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
EXPECT_OK(segmenter.AddSample(*sample));
}
@ -182,7 +185,6 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) {
const uint32_t kFirstPts = 1000;
EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true));
EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_))
.WillOnce(Return(true));
@ -253,10 +255,10 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) {
.InSequence(pes_packet_sequence)
.WillOnce(Return(new PesPacket()));
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_));
EXPECT_OK(segmenter.Initialize(*stream_info));
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
EXPECT_OK(segmenter.AddSample(*sample1));
EXPECT_OK(segmenter.FinalizeSegment(kFirstPts, sample1->duration()));
EXPECT_OK(segmenter.AddSample(*sample2));
@ -273,7 +275,6 @@ 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_pes_packet_generator_, Initialize(_))
.WillOnce(Return(true));
@ -281,7 +282,6 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) {
ON_CALL(*mock_pes_packet_generator_, NumberOfReadyPesPackets())
.WillByDefault(Return(0));
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_));
EXPECT_OK(segmenter.Initialize(*stream_info));
@ -302,7 +302,6 @@ TEST_F(TsSegmenterTest, FinalizeSegment) {
options.segment_template = "file$Number$.ts";
TsSegmenter segmenter(options, nullptr);
EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true));
EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_))
.WillOnce(Return(true));
@ -312,10 +311,10 @@ TEST_F(TsSegmenterTest, FinalizeSegment) {
.WillOnce(Return(0u));
EXPECT_CALL(*mock_ts_writer_, FinalizeSegment()).WillOnce(Return(true));
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_));
EXPECT_OK(segmenter.Initialize(*stream_info));
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
segmenter.SetTsWriterFileOpenedForTesting(true);
EXPECT_OK(segmenter.FinalizeSegment(0, 100 /* arbitrary duration */));
}
@ -333,7 +332,6 @@ TEST_F(TsSegmenterTest, EncryptedSample) {
MockMuxerListener mock_listener;
TsSegmenter segmenter(options, &mock_listener);
ON_CALL(*mock_ts_writer_, Initialize(_)).WillByDefault(Return(true));
ON_CALL(*mock_ts_writer_, NewSegment(_)).WillByDefault(Return(true));
ON_CALL(*mock_ts_writer_, FinalizeSegment()).WillByDefault(Return(true));
ON_CALL(*mock_ts_writer_, AddPesPacketMock(_)).WillByDefault(Return(true));
@ -390,11 +388,11 @@ TEST_F(TsSegmenterTest, EncryptedSample) {
MockTsWriter* mock_ts_writer_raw = mock_ts_writer_.get();
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_));
EXPECT_OK(segmenter.Initialize(*stream_info));
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
EXPECT_OK(segmenter.AddSample(*sample1));
EXPECT_OK(segmenter.FinalizeSegment(1, sample1->duration()));

View File

@ -21,7 +21,8 @@ enum class TsStreamType {
// ATSC Standard A/52.
kAc3 = 0x81,
kEac3 = 0x87,
// Encrypted: https://goo.gl/N7Tvqi.
// MPEG-2 Stream Encryption Format for HTTP Live Streaming:
// https://goo.gl/N7Tvqi.
kEncryptedAc3 = 0xC1,
kEncryptedEac3 = 0xC2,
kEncryptedAdtsAac = 0xCF,

View File

@ -9,10 +9,10 @@
#include <algorithm>
#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"
#include "packager/media/base/media_sample.h"
#include "packager/media/formats/mp2t/pes_packet.h"
#include "packager/media/formats/mp2t/program_map_table_writer.h"
#include "packager/media/formats/mp2t/ts_packet_writer_util.h"
namespace shaka {
@ -158,34 +158,11 @@ bool WritePesToFile(const PesPacket& pes,
} // namespace
TsWriter::TsWriter() {}
TsWriter::TsWriter(std::unique_ptr<ProgramMapTableWriter> pmt_writer)
: pmt_writer_(std::move(pmt_writer)) {}
TsWriter::~TsWriter() {}
bool TsWriter::Initialize(const StreamInfo& stream_info) {
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;
}
if (stream_info.stream_type() == StreamType::kStreamVideo) {
const VideoStreamInfo& video_stream_info =
static_cast<const VideoStreamInfo&>(stream_info);
pmt_writer_.reset(
new VideoProgramMapTableWriter(video_stream_info.codec()));
} else {
DCHECK_EQ(stream_type, StreamType::kStreamAudio);
const AudioStreamInfo& audio_stream_info =
static_cast<const AudioStreamInfo&>(stream_info);
pmt_writer_.reset(new AudioProgramMapTableWriter(
audio_stream_info.codec(), audio_stream_info.codec_config()));
}
return true;
}
bool TsWriter::NewSegment(const std::string& file_name) {
if (current_file_) {
LOG(ERROR) << "File " << current_file_->file_name() << " still open.";
@ -237,11 +214,6 @@ bool TsWriter::AddPesPacket(std::unique_ptr<PesPacket> pes_packet) {
return true;
}
void TsWriter::SetProgramMapTableWriterForTesting(
std::unique_ptr<ProgramMapTableWriter> table_writer) {
pmt_writer_ = std::move(table_writer);
}
} // namespace mp2t
} // namespace media
} // namespace shaka

View File

@ -15,28 +15,21 @@
#include "packager/file/file.h"
#include "packager/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 shaka {
namespace media {
class StreamInfo;
namespace mp2t {
class PesPacket;
class ProgramMapTableWriter;
/// 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();
explicit TsWriter(std::unique_ptr<ProgramMapTableWriter> pmt_writer);
virtual ~TsWriter();
/// This must be called before calling other methods.
/// @param stream_info is the information about this stream.
/// @return true on success, false otherwise.
virtual bool Initialize(const StreamInfo& stream_info);
/// 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.
@ -57,11 +50,10 @@ class TsWriter {
/// @return true on success, false otherwise.
virtual bool AddPesPacket(std::unique_ptr<PesPacket> pes_packet);
/// Only for testing.
void SetProgramMapTableWriterForTesting(
std::unique_ptr<ProgramMapTableWriter> table_writer);
private:
TsWriter(const TsWriter&) = delete;
TsWriter& operator=(const TsWriter&) = delete;
// True if further segments generated by this instance should be encrypted.
bool encrypted_ = false;
@ -71,8 +63,6 @@ class TsWriter {
std::unique_ptr<ProgramMapTableWriter> pmt_writer_;
std::unique_ptr<File, FileCloser> current_file_;
DISALLOW_COPY_AND_ASSIGN(TsWriter);
};
} // namespace mp2t

View File

@ -13,6 +13,7 @@
#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/program_map_table_writer.h"
#include "packager/media/formats/mp2t/ts_writer.h"
using ::testing::InSequence;
@ -26,40 +27,12 @@ namespace mp2t {
namespace {
const int kTsPacketSize = 188;
// Only {Audio,Video}Codec matter for this test. Other values are bogus.
const Codec kH264Codec = Codec::kCodecH264;
const Codec kAacCodec = Codec::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 kTrickPlayFactor = 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 uint64_t kSeekPreroll = 0;
const uint64_t kCodecDelay = 0;
const uint32_t kMaxBitrate = 320000;
const uint32_t kAverageBitrate = 256000;
// Bogus extra data.
const uint8_t kExtraData[] = {0x01, 0x02};
const uint8_t kAacBasicProfileExtraData[] = {0x12, 0x10};
const Codec kCodecForTesting = kCodecH264;
class MockProgramMapTableWriter : public ProgramMapTableWriter {
public:
MockProgramMapTableWriter() : ProgramMapTableWriter(kUnknownCodec) {}
~MockProgramMapTableWriter() override {}
MockProgramMapTableWriter() : ProgramMapTableWriter(kCodecForTesting) {}
~MockProgramMapTableWriter() override = default;
MOCK_METHOD1(EncryptedSegmentPmt, bool(BufferWriter* writer));
MOCK_METHOD1(ClearSegmentPmt, bool(BufferWriter* writer));
@ -158,29 +131,10 @@ class TsWriterTest : public ::testing::Test {
}
std::string test_file_name_;
TsWriter ts_writer_;
base::FilePath test_file_path_;
};
TEST_F(TsWriterTest, InitializeVideoH264) {
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
kTrackId, kTimeScale, kDuration, kH264Codec,
H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData,
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted));
EXPECT_TRUE(ts_writer_.Initialize(*stream_info));
}
TEST_F(TsWriterTest, InitializeAudioAac) {
std::shared_ptr<AudioStreamInfo> stream_info(new AudioStreamInfo(
kTrackId, kTimeScale, kDuration, kAacCodec, kCodecString, kExtraData,
arraysize(kExtraData), kSampleBits, kNumChannels, kSamplingFrequency,
kSeekPreroll, kCodecDelay, kMaxBitrate, kAverageBitrate, kLanguage,
kIsEncrypted));
EXPECT_TRUE(ts_writer_.Initialize(*stream_info));
}
// 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.
@ -189,16 +143,9 @@ TEST_F(TsWriterTest, ClearH264Psi) {
new MockProgramMapTableWriter());
EXPECT_CALL(*mock_pmt_writer, ClearSegmentPmt(_)).WillOnce(WriteOnePmt());
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
kTrackId, kTimeScale, kDuration, kH264Codec,
H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData,
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted));
EXPECT_TRUE(ts_writer_.Initialize(*stream_info));
ts_writer_.SetProgramMapTableWriterForTesting(std::move(mock_pmt_writer));
EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_));
ASSERT_TRUE(ts_writer_.FinalizeSegment());
TsWriter ts_writer(std::move(mock_pmt_writer));
EXPECT_TRUE(ts_writer.NewSegment(test_file_name_));
ASSERT_TRUE(ts_writer.FinalizeSegment());
std::vector<uint8_t> content;
ASSERT_TRUE(ReadFileToVector(test_file_path_, &content));
@ -244,16 +191,9 @@ TEST_F(TsWriterTest, ClearAacPmt) {
new MockProgramMapTableWriter());
EXPECT_CALL(*mock_pmt_writer, ClearSegmentPmt(_)).WillOnce(WriteOnePmt());
std::shared_ptr<AudioStreamInfo> stream_info(new AudioStreamInfo(
kTrackId, kTimeScale, kDuration, kAacCodec, kCodecString,
kAacBasicProfileExtraData, arraysize(kAacBasicProfileExtraData),
kSampleBits, kNumChannels, kSamplingFrequency, kSeekPreroll, kCodecDelay,
kMaxBitrate, kAverageBitrate, kLanguage, kIsEncrypted));
EXPECT_TRUE(ts_writer_.Initialize(*stream_info));
ts_writer_.SetProgramMapTableWriterForTesting(std::move(mock_pmt_writer));
EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_));
ASSERT_TRUE(ts_writer_.FinalizeSegment());
TsWriter ts_writer(std::move(mock_pmt_writer));
EXPECT_TRUE(ts_writer.NewSegment(test_file_name_));
ASSERT_TRUE(ts_writer.FinalizeSegment());
std::vector<uint8_t> content;
ASSERT_TRUE(ReadFileToVector(test_file_path_, &content));
@ -272,16 +212,9 @@ TEST_F(TsWriterTest, ClearLeadH264Pmt) {
EXPECT_CALL(*mock_pmt_writer, ClearSegmentPmt(_))
.WillOnce(WriteTwoPmts());
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
kTrackId, kTimeScale, kDuration, kH264Codec,
H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData,
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted));
EXPECT_TRUE(ts_writer_.Initialize(*stream_info));
ts_writer_.SetProgramMapTableWriterForTesting(std::move(mock_pmt_writer));
EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_));
EXPECT_TRUE(ts_writer_.FinalizeSegment());
TsWriter ts_writer(std::move(mock_pmt_writer));
EXPECT_TRUE(ts_writer.NewSegment(test_file_name_));
EXPECT_TRUE(ts_writer.FinalizeSegment());
std::vector<uint8_t> content;
ASSERT_TRUE(ReadFileToVector(test_file_path_, &content));
@ -299,15 +232,8 @@ TEST_F(TsWriterTest, ClearSegmentPmtFailure) {
new MockProgramMapTableWriter());
EXPECT_CALL(*mock_pmt_writer, ClearSegmentPmt(_)).WillOnce(Return(false));
std::shared_ptr<AudioStreamInfo> stream_info(new AudioStreamInfo(
kTrackId, kTimeScale, kDuration, kAacCodec, kCodecString,
kAacBasicProfileExtraData, arraysize(kAacBasicProfileExtraData),
kSampleBits, kNumChannels, kSamplingFrequency, kSeekPreroll, kCodecDelay,
kMaxBitrate, kAverageBitrate, kLanguage, kIsEncrypted));
EXPECT_TRUE(ts_writer_.Initialize(*stream_info));
ts_writer_.SetProgramMapTableWriterForTesting(std::move(mock_pmt_writer));
EXPECT_FALSE(ts_writer_.NewSegment(test_file_name_));
TsWriter ts_writer(std::move(mock_pmt_writer));
EXPECT_FALSE(ts_writer.NewSegment(test_file_name_));
}
// Check the encrypted segments' PMT (after clear lead).
@ -318,21 +244,14 @@ TEST_F(TsWriterTest, EncryptedSegmentsH264Pmt) {
EXPECT_CALL(*mock_pmt_writer, ClearSegmentPmt(_)).WillOnce(Return(true));
EXPECT_CALL(*mock_pmt_writer, EncryptedSegmentPmt(_)).WillOnce(WriteOnePmt());
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
kTrackId, kTimeScale, kDuration, kH264Codec,
H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData,
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted));
EXPECT_TRUE(ts_writer_.Initialize(*stream_info));
ts_writer_.SetProgramMapTableWriterForTesting(std::move(mock_pmt_writer));
EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_));
EXPECT_TRUE(ts_writer_.FinalizeSegment());
TsWriter ts_writer(std::move(mock_pmt_writer));
EXPECT_TRUE(ts_writer.NewSegment(test_file_name_));
EXPECT_TRUE(ts_writer.FinalizeSegment());
// Overwrite the file but as encrypted segment.
ts_writer_.SignalEncrypted();
EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_));
EXPECT_TRUE(ts_writer_.FinalizeSegment());
ts_writer.SignalEncrypted();
EXPECT_TRUE(ts_writer.NewSegment(test_file_name_));
EXPECT_TRUE(ts_writer.FinalizeSegment());
std::vector<uint8_t> content;
ASSERT_TRUE(ReadFileToVector(test_file_path_, &content));
@ -350,19 +269,12 @@ TEST_F(TsWriterTest, EncryptedSegmentPmtFailure) {
EXPECT_CALL(*mock_pmt_writer, ClearSegmentPmt(_)).WillOnce(Return(true));
EXPECT_CALL(*mock_pmt_writer, EncryptedSegmentPmt(_)).WillOnce(Return(false));
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
kTrackId, kTimeScale, kDuration, kH264Codec,
H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData,
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted));
EXPECT_TRUE(ts_writer_.Initialize(*stream_info));
TsWriter ts_writer(std::move(mock_pmt_writer));
EXPECT_TRUE(ts_writer.NewSegment(test_file_name_));
EXPECT_TRUE(ts_writer.FinalizeSegment());
ts_writer_.SetProgramMapTableWriterForTesting(std::move(mock_pmt_writer));
EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_));
EXPECT_TRUE(ts_writer_.FinalizeSegment());
ts_writer_.SignalEncrypted();
EXPECT_FALSE(ts_writer_.NewSegment(test_file_name_));
ts_writer.SignalEncrypted();
EXPECT_FALSE(ts_writer.NewSegment(test_file_name_));
}
// Same as ClearLeadH264Pmt but for AAC.
@ -372,16 +284,9 @@ TEST_F(TsWriterTest, ClearLeadAacPmt) {
EXPECT_CALL(*mock_pmt_writer, ClearSegmentPmt(_))
.WillOnce(WriteTwoPmts());
std::shared_ptr<AudioStreamInfo> stream_info(new AudioStreamInfo(
kTrackId, kTimeScale, kDuration, kAacCodec, kCodecString,
kAacBasicProfileExtraData, arraysize(kAacBasicProfileExtraData),
kSampleBits, kNumChannels, kSamplingFrequency, kSeekPreroll, kCodecDelay,
kMaxBitrate, kAverageBitrate, kLanguage, kIsEncrypted));
EXPECT_TRUE(ts_writer_.Initialize(*stream_info));
ts_writer_.SetProgramMapTableWriterForTesting(std::move(mock_pmt_writer));
EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_));
ASSERT_TRUE(ts_writer_.FinalizeSegment());
TsWriter ts_writer(std::move(mock_pmt_writer));
EXPECT_TRUE(ts_writer.NewSegment(test_file_name_));
ASSERT_TRUE(ts_writer.FinalizeSegment());
std::vector<uint8_t> content;
ASSERT_TRUE(ReadFileToVector(test_file_path_, &content));
@ -402,21 +307,14 @@ TEST_F(TsWriterTest, EncryptedSegmentsAacPmt) {
EXPECT_CALL(*mock_pmt_writer, ClearSegmentPmt(_)).WillOnce(Return(true));
EXPECT_CALL(*mock_pmt_writer, EncryptedSegmentPmt(_)).WillOnce(WriteOnePmt());
std::shared_ptr<AudioStreamInfo> stream_info(new AudioStreamInfo(
kTrackId, kTimeScale, kDuration, kAacCodec, kCodecString,
kAacBasicProfileExtraData, arraysize(kAacBasicProfileExtraData),
kSampleBits, kNumChannels, kSamplingFrequency, kSeekPreroll, kCodecDelay,
kMaxBitrate, kAverageBitrate, kLanguage, kIsEncrypted));
EXPECT_TRUE(ts_writer_.Initialize(*stream_info));
ts_writer_.SetProgramMapTableWriterForTesting(std::move(mock_pmt_writer));
EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_));
EXPECT_TRUE(ts_writer_.FinalizeSegment());
TsWriter ts_writer(std::move(mock_pmt_writer));
EXPECT_TRUE(ts_writer.NewSegment(test_file_name_));
EXPECT_TRUE(ts_writer.FinalizeSegment());
// Overwrite the file but as encrypted segment.
ts_writer_.SignalEncrypted();
EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_));
EXPECT_TRUE(ts_writer_.FinalizeSegment());
ts_writer.SignalEncrypted();
EXPECT_TRUE(ts_writer.NewSegment(test_file_name_));
EXPECT_TRUE(ts_writer.FinalizeSegment());
std::vector<uint8_t> content;
ASSERT_TRUE(ReadFileToVector(test_file_path_, &content));
@ -429,13 +327,9 @@ TEST_F(TsWriterTest, EncryptedSegmentsAacPmt) {
TEST_F(TsWriterTest, AddPesPacket) {
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
kTrackId, kTimeScale, kDuration, kH264Codec,
H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData,
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted));
EXPECT_TRUE(ts_writer_.Initialize(*stream_info));
EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_));
TsWriter ts_writer(std::unique_ptr<ProgramMapTableWriter>(
new VideoProgramMapTableWriter(kCodecForTesting)));
EXPECT_TRUE(ts_writer.NewSegment(test_file_name_));
std::unique_ptr<PesPacket> pes(new PesPacket());
pes->set_stream_id(0xE0);
@ -446,8 +340,8 @@ TEST_F(TsWriterTest, AddPesPacket) {
};
pes->mutable_data()->assign(kAnyData, kAnyData + arraysize(kAnyData));
EXPECT_TRUE(ts_writer_.AddPesPacket(std::move(pes)));
ASSERT_TRUE(ts_writer_.FinalizeSegment());
EXPECT_TRUE(ts_writer.AddPesPacket(std::move(pes)));
ASSERT_TRUE(ts_writer.FinalizeSegment());
std::vector<uint8_t> content;
ASSERT_TRUE(ReadFileToVector(test_file_path_, &content));
@ -495,13 +389,9 @@ TEST_F(TsWriterTest, AddPesPacket) {
// Verify that PES packet > 64KiB can be handled.
TEST_F(TsWriterTest, BigPesPacket) {
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
kTrackId, kTimeScale, kDuration, kH264Codec,
H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData,
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted));
EXPECT_TRUE(ts_writer_.Initialize(*stream_info));
EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_));
TsWriter ts_writer(std::unique_ptr<ProgramMapTableWriter>(
new VideoProgramMapTableWriter(kCodecForTesting)));
EXPECT_TRUE(ts_writer.NewSegment(test_file_name_));
std::unique_ptr<PesPacket> pes(new PesPacket());
pes->set_pts(0);
@ -510,8 +400,8 @@ TEST_F(TsWriterTest, BigPesPacket) {
const std::vector<uint8_t> big_data(400, 0x23);
*pes->mutable_data() = big_data;
EXPECT_TRUE(ts_writer_.AddPesPacket(std::move(pes)));
ASSERT_TRUE(ts_writer_.FinalizeSegment());
EXPECT_TRUE(ts_writer.AddPesPacket(std::move(pes)));
ASSERT_TRUE(ts_writer.FinalizeSegment());
std::vector<uint8_t> content;
ASSERT_TRUE(ReadFileToVector(test_file_path_, &content));
@ -532,13 +422,9 @@ TEST_F(TsWriterTest, BigPesPacket) {
// 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) {
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
kTrackId, kTimeScale, kDuration, kH264Codec,
H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData,
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted));
EXPECT_TRUE(ts_writer_.Initialize(*stream_info));
EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_));
TsWriter ts_writer(std::unique_ptr<ProgramMapTableWriter>(
new VideoProgramMapTableWriter(kCodecForTesting)));
EXPECT_TRUE(ts_writer.NewSegment(test_file_name_));
std::unique_ptr<PesPacket> pes(new PesPacket());
pes->set_stream_id(0xE0);
@ -548,8 +434,8 @@ TEST_F(TsWriterTest, PesPtsZeroNoDts) {
};
pes->mutable_data()->assign(kAnyData, kAnyData + arraysize(kAnyData));
EXPECT_TRUE(ts_writer_.AddPesPacket(std::move(pes)));
ASSERT_TRUE(ts_writer_.FinalizeSegment());
EXPECT_TRUE(ts_writer.AddPesPacket(std::move(pes)));
ASSERT_TRUE(ts_writer.FinalizeSegment());
std::vector<uint8_t> content;
ASSERT_TRUE(ReadFileToVector(test_file_path_, &content));
@ -593,13 +479,9 @@ TEST_F(TsWriterTest, PesPtsZeroNoDts) {
// Verify that TS packet with payload 183 is handled correctly, e.g.
// adaptation_field_length should be 0.
TEST_F(TsWriterTest, TsPacketPayload183Bytes) {
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
kTrackId, kTimeScale, kDuration, kH264Codec,
H26xStreamFormat::kAnnexbByteStream, kCodecString, kExtraData,
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
kTrickPlayFactor, kNaluLengthSize, kLanguage, kIsEncrypted));
EXPECT_TRUE(ts_writer_.Initialize(*stream_info));
EXPECT_TRUE(ts_writer_.NewSegment(test_file_name_));
TsWriter ts_writer(std::unique_ptr<ProgramMapTableWriter>(
new VideoProgramMapTableWriter(kCodecForTesting)));
EXPECT_TRUE(ts_writer.NewSegment(test_file_name_));
std::unique_ptr<PesPacket> pes(new PesPacket());
pes->set_stream_id(0xE0);
@ -613,8 +495,8 @@ TEST_F(TsWriterTest, TsPacketPayload183Bytes) {
std::vector<uint8_t> pes_payload(157 + 183, 0xAF);
*pes->mutable_data() = pes_payload;
EXPECT_TRUE(ts_writer_.AddPesPacket(std::move(pes)));
ASSERT_TRUE(ts_writer_.FinalizeSegment());
EXPECT_TRUE(ts_writer.AddPesPacket(std::move(pes)));
ASSERT_TRUE(ts_writer.FinalizeSegment());
const uint8_t kExpectedOutputPrefix[] = {
0x47, // Sync byte.