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:
parent
5965b3e136
commit
33c4f344e7
|
@ -20,23 +20,27 @@ class BufferWriter;
|
||||||
class Id3Tag {
|
class Id3Tag {
|
||||||
public:
|
public:
|
||||||
Id3Tag() = default;
|
Id3Tag() = default;
|
||||||
~Id3Tag() = default;
|
virtual ~Id3Tag() = default;
|
||||||
|
|
||||||
/// Add a "Private Frame".
|
/// Add a "Private Frame".
|
||||||
/// See http://id3.org/id3v2.4.0-frames 4.27.
|
/// See http://id3.org/id3v2.4.0-frames 4.27.
|
||||||
/// @owner contains the owner identifier.
|
/// @owner contains the owner identifier.
|
||||||
/// @data contains the data for this private frame.
|
/// @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.
|
/// Write the ID3 tag to a buffer.
|
||||||
/// @param buffer_writer points to the @a BufferWriter to write to.
|
/// @param buffer_writer points to the @a BufferWriter to write to.
|
||||||
/// @return true on success.
|
/// @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.
|
/// Write the ID3 tag to vector.
|
||||||
/// @param output points to the vector to write to.
|
/// @param output points to the vector to write to.
|
||||||
/// @return true on success.
|
/// @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:
|
private:
|
||||||
Id3Tag(const Id3Tag&) = delete;
|
Id3Tag(const Id3Tag&) = delete;
|
||||||
|
|
|
@ -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',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -199,6 +199,7 @@
|
||||||
'media/event/media_event.gyp:media_event_unittest',
|
'media/event/media_event.gyp:media_event_unittest',
|
||||||
'media/formats/mp2t/mp2t.gyp:mp2t_unittest',
|
'media/formats/mp2t/mp2t.gyp:mp2t_unittest',
|
||||||
'media/formats/mp4/mp4.gyp:mp4_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/webm/webm.gyp:webm_unittest',
|
||||||
'media/formats/webvtt/webvtt.gyp:webvtt_unittest',
|
'media/formats/webvtt/webvtt.gyp:webvtt_unittest',
|
||||||
'media/formats/wvm/wvm.gyp:wvm_unittest',
|
'media/formats/wvm/wvm.gyp:wvm_unittest',
|
||||||
|
|
Loading…
Reference in New Issue