From 33c4f344e7ef1f9bb84a639d1940458fa3f8b6d3 Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Thu, 10 May 2018 14:49:12 -0700 Subject: [PATCH] Implemented PackedAudioSegmenter Per HLS specification: https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-3.4 Issue #342. Change-Id: Iba7fae7a9b8912bcd13ae55d25b22a5803f7f7ea --- packager/media/base/id3_tag.h | 12 +- .../formats/packed_audio/packed_audio.gyp | 39 +++ .../packed_audio/packed_audio_segmenter.cc | 136 +++++++ .../packed_audio/packed_audio_segmenter.h | 101 ++++++ .../packed_audio_segmenter_unittest.cc | 331 ++++++++++++++++++ packager/packager.gyp | 1 + 6 files changed, 616 insertions(+), 4 deletions(-) create mode 100644 packager/media/formats/packed_audio/packed_audio.gyp create mode 100644 packager/media/formats/packed_audio/packed_audio_segmenter.cc create mode 100644 packager/media/formats/packed_audio/packed_audio_segmenter.h create mode 100644 packager/media/formats/packed_audio/packed_audio_segmenter_unittest.cc diff --git a/packager/media/base/id3_tag.h b/packager/media/base/id3_tag.h index 62056fe4ef..963814aee7 100644 --- a/packager/media/base/id3_tag.h +++ b/packager/media/base/id3_tag.h @@ -20,23 +20,27 @@ class BufferWriter; class Id3Tag { public: Id3Tag() = default; - ~Id3Tag() = default; + virtual ~Id3Tag() = default; /// Add a "Private Frame". /// See http://id3.org/id3v2.4.0-frames 4.27. /// @owner contains the owner identifier. /// @data contains the data for this private frame. - void AddPrivateFrame(const std::string& owner, const std::string& data); + // This function is made virtual for testing. + virtual void AddPrivateFrame(const std::string& owner, + const std::string& data); /// Write the ID3 tag to a buffer. /// @param buffer_writer points to the @a BufferWriter to write to. /// @return true on success. - bool WriteToBuffer(BufferWriter* buffer_writer); + // This function is made virtual for testing. + virtual bool WriteToBuffer(BufferWriter* buffer_writer); /// Write the ID3 tag to vector. /// @param output points to the vector to write to. /// @return true on success. - bool WriteToVector(std::vector* output); + // This function is made virtual for testing. + virtual bool WriteToVector(std::vector* output); private: Id3Tag(const Id3Tag&) = delete; diff --git a/packager/media/formats/packed_audio/packed_audio.gyp b/packager/media/formats/packed_audio/packed_audio.gyp new file mode 100644 index 0000000000..a5e5f5366a --- /dev/null +++ b/packager/media/formats/packed_audio/packed_audio.gyp @@ -0,0 +1,39 @@ +# Copyright 2018 Google LLC. 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 + +{ + 'variables': { + 'shaka_code': 1, + }, + 'targets': [ + { + 'target_name': 'packed_audio', + 'type': '<(component)', + 'sources': [ + 'packed_audio_segmenter.cc', + 'packed_audio_segmenter.h', + ], + 'dependencies': [ + '../../base/media_base.gyp:media_base', + '../../codecs/codecs.gyp:codecs', + ], + }, + { + 'target_name': 'packed_audio_unittest', + 'type': '<(gtest_target_type)', + 'sources': [ + 'packed_audio_segmenter_unittest.cc', + ], + 'dependencies': [ + '../../../testing/gtest.gyp:gtest', + '../../../testing/gmock.gyp:gmock', + '../../codecs/codecs.gyp:codecs', + '../../test/media_test.gyp:media_test_support', + 'packed_audio', + ], + }, + ], +} diff --git a/packager/media/formats/packed_audio/packed_audio_segmenter.cc b/packager/media/formats/packed_audio/packed_audio_segmenter.cc new file mode 100644 index 0000000000..3d7516ae5b --- /dev/null +++ b/packager/media/formats/packed_audio/packed_audio_segmenter.cc @@ -0,0 +1,136 @@ +// Copyright 2018 Google LLC. 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/packed_audio/packed_audio_segmenter.h" + +#include + +#include "packager/media/base/id3_tag.h" +#include "packager/media/base/media_sample.h" +#include "packager/media/codecs/aac_audio_specific_config.h" +#include "packager/media/codecs/hls_audio_util.h" +#include "packager/status_macros.h" + +namespace shaka { +namespace media { +namespace { +std::string TimestampToString(uint64_t timestamp) { + BufferWriter buffer; + buffer.AppendInt(timestamp); + return std::string(buffer.Buffer(), buffer.Buffer() + buffer.Size()); +} +} // namespace + +PackedAudioSegmenter::PackedAudioSegmenter() = default; +PackedAudioSegmenter::~PackedAudioSegmenter() = default; + +Status PackedAudioSegmenter::Initialize(const StreamInfo& stream_info) { + const StreamType stream_type = stream_info.stream_type(); + if (stream_type != StreamType::kStreamAudio) { + LOG(ERROR) << "PackedAudioSegmenter cannot handle stream type " + << stream_type; + return Status(error::MUXER_FAILURE, "Unsupported stream type."); + } + + codec_ = stream_info.codec(); + audio_codec_config_ = stream_info.codec_config(); + timescale_scale_ = kPackedAudioTimescale / stream_info.time_scale(); + + if (codec_ == kCodecAAC) { + adts_converter_ = CreateAdtsConverter(); + if (!adts_converter_->Parse(audio_codec_config_)) { + return Status(error::MUXER_FAILURE, "Invalid audio codec configuration."); + } + } + + return Status::OK; +} + +Status PackedAudioSegmenter::AddSample(const MediaSample& sample) { + if (sample.is_encrypted() && audio_setup_information_.empty()) + RETURN_IF_ERROR(EncryptionAudioSetup(sample)); + + if (start_of_new_segment_) { + StartNewSegment(sample); + start_of_new_segment_ = false; + } + + if (adts_converter_) { + std::vector audio_frame(sample.data(), + sample.data() + sample.data_size()); + if (!adts_converter_->ConvertToADTS(&audio_frame)) + return Status(error::MUXER_FAILURE, "Failed to convert to ADTS."); + segment_buffer_.AppendArray(audio_frame.data(), audio_frame.size()); + } else { + segment_buffer_.AppendArray(sample.data(), sample.data_size()); + } + return Status::OK; +} + +Status PackedAudioSegmenter::FinalizeSegment() { + start_of_new_segment_ = true; + return Status::OK; +} + +double PackedAudioSegmenter::TimescaleScale() const { + return timescale_scale_; +} + +std::unique_ptr +PackedAudioSegmenter::CreateAdtsConverter() { + return std::unique_ptr(new AACAudioSpecificConfig); +} + +std::unique_ptr PackedAudioSegmenter::CreateId3Tag() { + return std::unique_ptr(new Id3Tag); +} + +Status PackedAudioSegmenter::EncryptionAudioSetup(const MediaSample& sample) { + // For codecs other than AC3, audio setup data is the audio codec + // configuration data. + const uint8_t* audio_setup_data = audio_codec_config_.data(); + size_t audio_setup_data_size = audio_codec_config_.size(); + 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()). + 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."); + } + audio_setup_data = sample.data(); + audio_setup_data_size = kSetupDataSize; + } + + BufferWriter buffer; + if (!WriteAudioSetupInformation(codec_, audio_setup_data, + audio_setup_data_size, &buffer)) { + return Status(error::MUXER_FAILURE, + "Failed to write audio setup information."); + } + audio_setup_information_.assign(buffer.Buffer(), + buffer.Buffer() + buffer.Size()); + return Status::OK; +} + +void PackedAudioSegmenter::StartNewSegment(const MediaSample& sample) { + segment_buffer_.Clear(); + + // Use a unique_ptr so it can be mocked for testing. + std::unique_ptr id3_tag = CreateId3Tag(); + id3_tag->AddPrivateFrame(kTimestampOwnerIdentifier, + TimestampToString(sample.pts() * timescale_scale_)); + if (!audio_setup_information_.empty()) { + id3_tag->AddPrivateFrame(kAudioDescriptionOwnerIdentifier, + audio_setup_information_); + } + CHECK(id3_tag->WriteToBuffer(&segment_buffer_)); +} + +} // namespace media +} // namespace shaka diff --git a/packager/media/formats/packed_audio/packed_audio_segmenter.h b/packager/media/formats/packed_audio/packed_audio_segmenter.h new file mode 100644 index 0000000000..c912c75328 --- /dev/null +++ b/packager/media/formats/packed_audio/packed_audio_segmenter.h @@ -0,0 +1,101 @@ +// Copyright 2018 Google LLC. 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_PACKED_AUDIO_PACKED_AUDIO_SEGMENTER_H_ +#define PACKAGER_MEDIA_FORMATS_PACKED_AUDIO_PACKED_AUDIO_SEGMENTER_H_ + +#include + +#include "packager/media/base/buffer_writer.h" +#include "packager/media/base/stream_info.h" +#include "packager/status.h" + +namespace shaka { +namespace media { + +class AACAudioSpecificConfig; +class Id3Tag; +class MediaSample; + +/// PackedAudio uses transport stream timescale. +constexpr double kPackedAudioTimescale = 90000; + +/// https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-3.4 +/// Timestamp is carried inside an ID3 PRIV tag with identifier: +constexpr char kTimestampOwnerIdentifier[] = + "com.apple.streaming.transportStreamTimestamp"; + +/// http://goo.gl/FPhLma 2.4.3.4 Elementary Stream Setup for fairplay streaming +/// Audio setup information is carried inside an ID3 PRIV tag with identifier: +constexpr char kAudioDescriptionOwnerIdentifier[] = + "com.apple.streaming.audioDescription"; + +/// Implements packed audio segment writer. +/// https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-3.4 +/// A Packed Audio Segment contains encoded audio samples and ID3 tags that are +/// simply packed together with minimal framing and no per-sample timestamps. +class PackedAudioSegmenter { + public: + PackedAudioSegmenter(); + virtual ~PackedAudioSegmenter(); + + /// Initialize the object. + /// @param stream_info is the stream info for the segmenter. + /// @return OK on success. + // This function is made virtual for testing. + virtual Status Initialize(const StreamInfo& stream_info); + + /// @param sample gets added to this object. + /// @return OK on success. + // This function is made virtual for testing. + virtual Status AddSample(const MediaSample& sample); + + /// Flush all the samples that are (possibly) buffered and write them to the + /// current segment. + /// @return OK on success. + // This function is made virtual for testing. + virtual Status FinalizeSegment(); + + /// @return The scale for converting timestamp in input stream's scale to + /// output stream's scale. + // This function is made virtual for testing. + virtual double TimescaleScale() const; + + /// @return A pointer to the buffer for the current segment. + BufferWriter* segment_buffer() { return &segment_buffer_; } + + private: + PackedAudioSegmenter(const PackedAudioSegmenter&) = delete; + PackedAudioSegmenter& operator=(const PackedAudioSegmenter&) = delete; + + // These functions is made virtual for testing. + virtual std::unique_ptr CreateAdtsConverter(); + virtual std::unique_ptr CreateId3Tag(); + + Status EncryptionAudioSetup(const MediaSample& sample); + void StartNewSegment(const MediaSample& first_sample); + + // Codec for the stream. + Codec codec_ = kUnknownCodec; + std::vector audio_codec_config_; + // Calculated by output stream's timescale / input stream's timescale. This is + // used to scale the timestamps. + double timescale_scale_ = 0.0; + // Whether it is the start of a new segment. + bool start_of_new_segment_ = true; + + // Audio setup information for encrypted segment. + std::string audio_setup_information_; + // AAC is carried in ADTS. + std::unique_ptr adts_converter_; + + BufferWriter segment_buffer_; +}; + +} // namespace media +} // namespace shaka + +#endif // PACKAGER_MEDIA_FORMATS_PACKED_AUDIO_PACKED_AUDIO_SEGMENTER_H_ diff --git a/packager/media/formats/packed_audio/packed_audio_segmenter_unittest.cc b/packager/media/formats/packed_audio/packed_audio_segmenter_unittest.cc new file mode 100644 index 0000000000..5fb6ec19a3 --- /dev/null +++ b/packager/media/formats/packed_audio/packed_audio_segmenter_unittest.cc @@ -0,0 +1,331 @@ +// Copyright 2018 Google LLC. 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/packed_audio/packed_audio_segmenter.h" + +#include +#include + +#include "packager/media/base/audio_stream_info.h" +#include "packager/media/base/id3_tag.h" +#include "packager/media/base/media_sample.h" +#include "packager/media/codecs/aac_audio_specific_config.h" +#include "packager/status_test_util.h" + +using ::testing::_; +using ::testing::ByMove; +using ::testing::DoAll; +using ::testing::ElementsAreArray; +using ::testing::Eq; +using ::testing::Invoke; +using ::testing::Pointee; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::Test; + +namespace shaka { +namespace media { +namespace { + +constexpr uint32_t kTimescale = 5625; +constexpr double kExpectedTimescaleScale = kPackedAudioTimescale / kTimescale; +static_assert(kExpectedTimescaleScale == 16.0, ""); + +const int kTrackId = 0; +const uint64_t kDuration = 180000; +const char kCodecString[] = "codec-string"; +const char kLanguage[] = "eng"; +const bool kIsEncrypted = true; + +const uint8_t kCodecConfig[] = {0x2B, 0x92, 8, 0}; + +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; + +const char kSample1Data[] = "sample 1 data"; +const char kAdtsSample1Data[] = "adts sample 1 data"; +const char kSample2Data[] = "sample 2 data"; +const int64_t kPts1 = 0x12345; +const int64_t kDts1 = 0x12000; +const int64_t kPts2 = 0x12445; +const int64_t kDts2 = 0x12100; + +// String form of kPts1 * kExpectedTimescaleScale. +const char kScaledPts1[] = {0, 0, 0, 0, 0, 0x12, 0x34, 0x50}; +// String form of kPts2 * kExpectedTimescaleScale. +const char kScaledPts2[] = {0, 0, 0, 0, 0, 0x12, 0x44, 0x50}; + +const char kSegment1Data[] = "segment 1 data"; +const char kSegment2Data[] = "segment 2 data"; + +std::vector StringToVector(const std::string& s) { + return std::vector(s.begin(), s.end()); +} + +std::shared_ptr CreateAudioStreamInfo(Codec codec) { + std::shared_ptr stream_info(new AudioStreamInfo( + kTrackId, kTimescale, kDuration, codec, kCodecString, kCodecConfig, + sizeof(kCodecConfig), kSampleBits, kNumChannels, kSamplingFrequency, + kSeekPreroll, kCodecDelay, kMaxBitrate, kAverageBitrate, kLanguage, + kIsEncrypted)); + return stream_info; +} + +std::shared_ptr CreateSample(int64_t pts, + int64_t dts, + const std::string& sample_data) { + const bool kIsKeyFrame = true; + std::shared_ptr sample = MediaSample::CopyFrom( + reinterpret_cast(sample_data.data()), sample_data.size(), + kIsKeyFrame); + sample->set_pts(pts); + sample->set_dts(dts); + return sample; +} + +std::shared_ptr CreateEncryptedSample( + int64_t pts, + int64_t dts, + const std::string& sample_data) { + auto sample = CreateSample(pts, dts, sample_data); + sample->set_is_encrypted(true); + return sample; +} + +class MockAACAudioSpecificConfig : public AACAudioSpecificConfig { + public: + MOCK_METHOD1(Parse, bool(const std::vector& data)); + MOCK_CONST_METHOD1(ConvertToADTS, bool(std::vector* buffer)); +}; + +class MockId3Tag : public Id3Tag { + public: + MOCK_METHOD2(AddPrivateFrame, + void(const std::string&, const std::string& data)); + MOCK_METHOD1(WriteToBuffer, bool(BufferWriter* buffer_writer)); +}; + +class TestablePackedAudioSegmenter : public PackedAudioSegmenter { + public: + MOCK_METHOD0(CreateAdtsConverter, std::unique_ptr()); + MOCK_METHOD0(CreateId3Tag, std::unique_ptr()); +}; + +} // namespace + +class PackedAudioSegmenterTest : public ::testing::Test { + public: + PackedAudioSegmenterTest() + : mock_adts_converter_(new MockAACAudioSpecificConfig) {} + + std::string GetSegmentData() { + const BufferWriter& buffer = *segmenter_.segment_buffer(); + return std::string(buffer.Buffer(), buffer.Buffer() + buffer.Size()); + } + + protected: + TestablePackedAudioSegmenter segmenter_; + std::unique_ptr mock_adts_converter_; +}; + +TEST_F(PackedAudioSegmenterTest, AacInitialize) { + EXPECT_CALL(*mock_adts_converter_, Parse(ElementsAreArray(kCodecConfig))) + .WillOnce(Return(true)); + EXPECT_CALL(segmenter_, CreateAdtsConverter()) + .WillOnce(Return(ByMove(std::move(mock_adts_converter_)))); + ASSERT_OK(segmenter_.Initialize(*CreateAudioStreamInfo(kCodecAAC))); + EXPECT_EQ(kExpectedTimescaleScale, segmenter_.TimescaleScale()); +} + +TEST_F(PackedAudioSegmenterTest, AacInitializeFailed) { + EXPECT_CALL(*mock_adts_converter_, Parse(ElementsAreArray(kCodecConfig))) + .WillOnce(Return(false)); + EXPECT_CALL(segmenter_, CreateAdtsConverter()) + .WillOnce(Return(ByMove(std::move(mock_adts_converter_)))); + ASSERT_NOT_OK(segmenter_.Initialize(*CreateAudioStreamInfo(kCodecAAC))); +} + +TEST_F(PackedAudioSegmenterTest, Ac3Initialize) { + EXPECT_CALL(segmenter_, CreateAdtsConverter()).Times(0); + ASSERT_OK(segmenter_.Initialize(*CreateAudioStreamInfo(kCodecAC3))); + EXPECT_EQ(kExpectedTimescaleScale, segmenter_.TimescaleScale()); +} + +TEST_F(PackedAudioSegmenterTest, AacAddSample) { + EXPECT_CALL(*mock_adts_converter_, Parse(ElementsAreArray(kCodecConfig))) + .WillOnce(Return(true)); + EXPECT_CALL(*mock_adts_converter_, + ConvertToADTS(Pointee(Eq(StringToVector(kSample1Data))))) + .WillOnce(DoAll(SetArgPointee<0>(StringToVector(kAdtsSample1Data)), + Return(true))); + + EXPECT_CALL(segmenter_, CreateAdtsConverter()) + .WillOnce(Return(ByMove(std::move(mock_adts_converter_)))); + ASSERT_OK(segmenter_.Initialize(*CreateAudioStreamInfo(kCodecAAC))); + + std::unique_ptr mock_id3_tag(new MockId3Tag); + EXPECT_CALL(*mock_id3_tag, + AddPrivateFrame( + kTimestampOwnerIdentifier, + std::string(std::begin(kScaledPts1), std::end(kScaledPts1)))); + EXPECT_CALL(*mock_id3_tag, WriteToBuffer(_)) + .WillOnce(Invoke([](BufferWriter* buffer) { + buffer->AppendString(kSegment1Data); + return true; + })); + EXPECT_CALL(segmenter_, CreateId3Tag()) + .WillOnce(Return(ByMove(std::move(mock_id3_tag)))); + + ASSERT_OK(segmenter_.AddSample(*CreateSample(kPts1, kDts1, kSample1Data))); + EXPECT_EQ(std::string(kSegment1Data) + kAdtsSample1Data, GetSegmentData()); +} + +TEST_F(PackedAudioSegmenterTest, Ac3AddSample) { + ASSERT_OK(segmenter_.Initialize(*CreateAudioStreamInfo(kCodecAC3))); + + std::unique_ptr mock_id3_tag(new MockId3Tag); + EXPECT_CALL(*mock_id3_tag, AddPrivateFrame(kTimestampOwnerIdentifier, _)); + EXPECT_CALL(*mock_id3_tag, WriteToBuffer(_)) + .WillOnce(Invoke([](BufferWriter* buffer) { + buffer->AppendString(kSegment1Data); + return true; + })); + EXPECT_CALL(segmenter_, CreateId3Tag()) + .WillOnce(Return(ByMove(std::move(mock_id3_tag)))); + + ASSERT_OK(segmenter_.AddSample(*CreateSample(kPts1, kDts1, kSample1Data))); + EXPECT_EQ(std::string(kSegment1Data) + kSample1Data, GetSegmentData()); +} + +TEST_F(PackedAudioSegmenterTest, Ac3AddSampleTwice) { + ASSERT_OK(segmenter_.Initialize(*CreateAudioStreamInfo(kCodecAC3))); + + std::unique_ptr mock_id3_tag(new MockId3Tag); + EXPECT_CALL(*mock_id3_tag, AddPrivateFrame(kTimestampOwnerIdentifier, _)); + EXPECT_CALL(*mock_id3_tag, WriteToBuffer(_)) + .WillOnce(Invoke([](BufferWriter* buffer) { + buffer->AppendString(kSegment1Data); + return true; + })); + EXPECT_CALL(segmenter_, CreateId3Tag()) + .WillOnce(Return(ByMove(std::move(mock_id3_tag)))); + + ASSERT_OK(segmenter_.AddSample(*CreateSample(kPts1, kDts1, kSample1Data))); + ASSERT_OK(segmenter_.AddSample(*CreateSample(kPts2, kDts2, kSample2Data))); + EXPECT_EQ(std::string(kSegment1Data) + kSample1Data + kSample2Data, + GetSegmentData()); +} + +TEST_F(PackedAudioSegmenterTest, Ac3AddSampleTwiceWithFinalize) { + ASSERT_OK(segmenter_.Initialize(*CreateAudioStreamInfo(kCodecAC3))); + + std::unique_ptr mock_id3_tag(new MockId3Tag); + EXPECT_CALL(*mock_id3_tag, + AddPrivateFrame( + kTimestampOwnerIdentifier, + std::string(std::begin(kScaledPts1), std::end(kScaledPts1)))); + EXPECT_CALL(*mock_id3_tag, WriteToBuffer(_)) + .WillOnce(Invoke([](BufferWriter* buffer) { + buffer->AppendString(kSegment1Data); + return true; + })); + EXPECT_CALL(segmenter_, CreateId3Tag()) + .WillOnce(Return(ByMove(std::move(mock_id3_tag)))); + + ASSERT_OK(segmenter_.AddSample(*CreateSample(kPts1, kDts1, kSample1Data))); + ASSERT_OK(segmenter_.FinalizeSegment()); + EXPECT_EQ(std::string(kSegment1Data) + kSample1Data, GetSegmentData()); + + std::unique_ptr mock_id3_tag2(new MockId3Tag); + EXPECT_CALL(*mock_id3_tag2, + AddPrivateFrame( + kTimestampOwnerIdentifier, + std::string(std::begin(kScaledPts2), std::end(kScaledPts2)))); + EXPECT_CALL(*mock_id3_tag2, WriteToBuffer(_)) + .WillOnce(Invoke([](BufferWriter* buffer) { + buffer->AppendString(kSegment2Data); + return true; + })); + EXPECT_CALL(segmenter_, CreateId3Tag()) + .WillOnce(Return(ByMove(std::move(mock_id3_tag2)))); + + ASSERT_OK(segmenter_.AddSample(*CreateSample(kPts2, kDts2, kSample2Data))); + EXPECT_EQ(std::string(kSegment2Data) + kSample2Data, GetSegmentData()); +} + +TEST_F(PackedAudioSegmenterTest, AacAddEncryptedSample) { + EXPECT_CALL(*mock_adts_converter_, Parse(ElementsAreArray(kCodecConfig))) + .WillOnce(Return(true)); + EXPECT_CALL(*mock_adts_converter_, + ConvertToADTS(Pointee(Eq(StringToVector(kSample1Data))))) + .WillOnce(DoAll(SetArgPointee<0>(StringToVector(kAdtsSample1Data)), + Return(true))); + + EXPECT_CALL(segmenter_, CreateAdtsConverter()) + .WillOnce(Return(ByMove(std::move(mock_adts_converter_)))); + ASSERT_OK(segmenter_.Initialize(*CreateAudioStreamInfo(kCodecAAC))); + + std::unique_ptr mock_id3_tag(new MockId3Tag); + EXPECT_CALL(*mock_id3_tag, AddPrivateFrame(kTimestampOwnerIdentifier, _)); + // Derived from |kCodecConfig|. + const char kExpectedAacSetup[] = "zach\x0\x0\x1\x4\x2B\x92\x8\x0"; + EXPECT_CALL(*mock_id3_tag, + AddPrivateFrame(kAudioDescriptionOwnerIdentifier, + std::string(std::begin(kExpectedAacSetup), + std::end(kExpectedAacSetup) - 1))); + EXPECT_CALL(*mock_id3_tag, WriteToBuffer(_)).WillOnce(Return(true)); + EXPECT_CALL(segmenter_, CreateId3Tag()) + .WillOnce(Return(ByMove(std::move(mock_id3_tag)))); + + ASSERT_OK( + segmenter_.AddSample(*CreateEncryptedSample(kPts1, kDts1, kSample1Data))); +} + +TEST_F(PackedAudioSegmenterTest, Ac3AddEncryptedSample) { + ASSERT_OK(segmenter_.Initialize(*CreateAudioStreamInfo(kCodecAC3))); + + std::unique_ptr mock_id3_tag(new MockId3Tag); + EXPECT_CALL(*mock_id3_tag, AddPrivateFrame(kTimestampOwnerIdentifier, _)); + // Derived from |kSample1Data|. + const char kExpectedAc3Setup[] = "zac3\x0\x0\x1\xAsample 1 d"; + EXPECT_CALL(*mock_id3_tag, + AddPrivateFrame(kAudioDescriptionOwnerIdentifier, + std::string(std::begin(kExpectedAc3Setup), + std::end(kExpectedAc3Setup) - 1))); + EXPECT_CALL(*mock_id3_tag, WriteToBuffer(_)).WillOnce(Return(true)); + EXPECT_CALL(segmenter_, CreateId3Tag()) + .WillOnce(Return(ByMove(std::move(mock_id3_tag)))); + + ASSERT_OK( + segmenter_.AddSample(*CreateEncryptedSample(kPts1, kDts1, kSample1Data))); +} + +TEST_F(PackedAudioSegmenterTest, Eac3AddEncryptedSample) { + ASSERT_OK(segmenter_.Initialize(*CreateAudioStreamInfo(kCodecEAC3))); + + std::unique_ptr mock_id3_tag(new MockId3Tag); + EXPECT_CALL(*mock_id3_tag, AddPrivateFrame(kTimestampOwnerIdentifier, _)); + // Derived from |kCodecConfig|. + const char kExpectedEac3Setup[] = "zec3\x0\x0\x1\x4\x2B\x92\x8\x0"; + EXPECT_CALL(*mock_id3_tag, + AddPrivateFrame(kAudioDescriptionOwnerIdentifier, + std::string(std::begin(kExpectedEac3Setup), + std::end(kExpectedEac3Setup) - 1))); + EXPECT_CALL(*mock_id3_tag, WriteToBuffer(_)).WillOnce(Return(true)); + EXPECT_CALL(segmenter_, CreateId3Tag()) + .WillOnce(Return(ByMove(std::move(mock_id3_tag)))); + + ASSERT_OK( + segmenter_.AddSample(*CreateEncryptedSample(kPts1, kDts1, kSample1Data))); +} + +} // namespace media +} // namespace shaka diff --git a/packager/packager.gyp b/packager/packager.gyp index d26b39fc19..d1ce240f27 100644 --- a/packager/packager.gyp +++ b/packager/packager.gyp @@ -199,6 +199,7 @@ 'media/event/media_event.gyp:media_event_unittest', 'media/formats/mp2t/mp2t.gyp:mp2t_unittest', 'media/formats/mp4/mp4.gyp:mp4_unittest', + 'media/formats/packed_audio/packed_audio.gyp:packed_audio_unittest', 'media/formats/webm/webm.gyp:webm_unittest', 'media/formats/webvtt/webvtt.gyp:webvtt_unittest', 'media/formats/wvm/wvm.gyp:wvm_unittest',