Implemented PackedAudioSegmenter

Per HLS specification:
https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-3.4

Issue #342.

Change-Id: Iba7fae7a9b8912bcd13ae55d25b22a5803f7f7ea
This commit is contained in:
KongQun Yang 2018-05-10 14:49:12 -07:00
parent 5965b3e136
commit 33c4f344e7
6 changed files with 616 additions and 4 deletions

View File

@ -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<uint8_t>* output);
// This function is made virtual for testing.
virtual bool WriteToVector(std::vector<uint8_t>* output);
private:
Id3Tag(const Id3Tag&) = delete;

View File

@ -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',
],
},
],
}

View File

@ -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 <memory>
#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<uint8_t> 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<AACAudioSpecificConfig>
PackedAudioSegmenter::CreateAdtsConverter() {
return std::unique_ptr<AACAudioSpecificConfig>(new AACAudioSpecificConfig);
}
std::unique_ptr<Id3Tag> PackedAudioSegmenter::CreateId3Tag() {
return std::unique_ptr<Id3Tag>(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<Id3Tag> 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

View File

@ -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 <memory>
#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<AACAudioSpecificConfig> CreateAdtsConverter();
virtual std::unique_ptr<Id3Tag> CreateId3Tag();
Status EncryptionAudioSetup(const MediaSample& sample);
void StartNewSegment(const MediaSample& first_sample);
// Codec for the stream.
Codec codec_ = kUnknownCodec;
std::vector<uint8_t> 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<AACAudioSpecificConfig> adts_converter_;
BufferWriter segment_buffer_;
};
} // namespace media
} // namespace shaka
#endif // PACKAGER_MEDIA_FORMATS_PACKED_AUDIO_PACKED_AUDIO_SEGMENTER_H_

View File

@ -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 <gmock/gmock.h>
#include <gtest/gtest.h>
#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<uint8_t> StringToVector(const std::string& s) {
return std::vector<uint8_t>(s.begin(), s.end());
}
std::shared_ptr<AudioStreamInfo> CreateAudioStreamInfo(Codec codec) {
std::shared_ptr<AudioStreamInfo> 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<MediaSample> CreateSample(int64_t pts,
int64_t dts,
const std::string& sample_data) {
const bool kIsKeyFrame = true;
std::shared_ptr<MediaSample> sample = MediaSample::CopyFrom(
reinterpret_cast<const uint8_t*>(sample_data.data()), sample_data.size(),
kIsKeyFrame);
sample->set_pts(pts);
sample->set_dts(dts);
return sample;
}
std::shared_ptr<MediaSample> 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<uint8_t>& data));
MOCK_CONST_METHOD1(ConvertToADTS, bool(std::vector<uint8_t>* 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<AACAudioSpecificConfig>());
MOCK_METHOD0(CreateId3Tag, std::unique_ptr<Id3Tag>());
};
} // 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<MockAACAudioSpecificConfig> 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<MockId3Tag> 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<MockId3Tag> 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<MockId3Tag> 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<MockId3Tag> 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<MockId3Tag> 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<MockId3Tag> 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<MockId3Tag> 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<MockId3Tag> 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

View File

@ -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',