Added unit tests for WebM Segmenters.
* Changed Segmenters to accept StreamInfo rather than MediaStream to help in testing. * Changed MemoryFile behavior to mirror local file: * Read non-existent file is an error. * Write deletes any existing file. * Fixed a bug in SingleSegmentSegmenter. Change-Id: I339e35597ca4661b7a26c6fdbbfa2f9f511c7da0
This commit is contained in:
parent
b3e85ff810
commit
7b52f0a3ed
|
@ -61,7 +61,7 @@ File* CreateUdpFile(const char* file_name, const char* mode) {
|
|||
}
|
||||
|
||||
File* CreateMemoryFile(const char* file_name, const char* mode) {
|
||||
return new MemoryFile(file_name);
|
||||
return new MemoryFile(file_name, mode);
|
||||
}
|
||||
|
||||
bool DeleteMemoryFile(const char* file_name) {
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
#ifndef MEDIA_FILE_FILE_TEST_UTIL_H_
|
||||
#define MEDIA_FILE_FILE_TEST_UTIL_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "packager/media/file/file.h"
|
||||
|
||||
namespace edash_packager {
|
||||
namespace media {
|
||||
|
||||
#define ASSERT_FILE_EQ(file_name, array) \
|
||||
do { \
|
||||
std::string temp_data; \
|
||||
ASSERT_TRUE(File::ReadFileToString((file_name), &temp_data)); \
|
||||
const char* array_ptr = reinterpret_cast<const char*>(array); \
|
||||
ASSERT_EQ(std::string(array_ptr, arraysize(array)), temp_data); \
|
||||
} while (false)
|
||||
|
||||
} // namespace media
|
||||
} // namespace edash_packager
|
||||
|
||||
#endif // MEDIA_FILE_FILE_TEST_UTIL_H_
|
||||
|
|
@ -29,6 +29,10 @@ class FileSystem {
|
|||
return g_file_system_.get();
|
||||
}
|
||||
|
||||
bool Exists(const std::string& file_name) const {
|
||||
return files_.find(file_name) != files_.end();
|
||||
}
|
||||
|
||||
std::vector<uint8_t>* GetFile(const std::string& file_name) {
|
||||
return &files_[file_name];
|
||||
}
|
||||
|
@ -50,10 +54,8 @@ scoped_ptr<FileSystem> FileSystem::g_file_system_;
|
|||
|
||||
} // namespace
|
||||
|
||||
MemoryFile::MemoryFile(const std::string& file_name)
|
||||
: File(file_name),
|
||||
file_(FileSystem::Instance()->GetFile(file_name)),
|
||||
position_(0) {}
|
||||
MemoryFile::MemoryFile(const std::string& file_name, const std::string& mode)
|
||||
: File(file_name), mode_(mode), file_(NULL), position_(0) {}
|
||||
|
||||
MemoryFile::~MemoryFile() {}
|
||||
|
||||
|
@ -86,6 +88,7 @@ int64_t MemoryFile::Write(const void* buffer, uint64_t length) {
|
|||
}
|
||||
|
||||
int64_t MemoryFile::Size() {
|
||||
DCHECK(file_);
|
||||
return file_->size();
|
||||
}
|
||||
|
||||
|
@ -107,6 +110,19 @@ bool MemoryFile::Tell(uint64_t* position) {
|
|||
}
|
||||
|
||||
bool MemoryFile::Open() {
|
||||
FileSystem* file_system = FileSystem::Instance();
|
||||
if (mode_ == "r") {
|
||||
if (!file_system->Exists(file_name()))
|
||||
return false;
|
||||
} else if (mode_ == "w") {
|
||||
file_system->Delete(file_name());
|
||||
} else {
|
||||
NOTIMPLEMENTED() << "File mode " << mode_ << " not supported by MemoryFile";
|
||||
return false;
|
||||
}
|
||||
|
||||
file_ = file_system->GetFile(file_name());
|
||||
DCHECK(file_);
|
||||
position_ = 0;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace media {
|
|||
/// testing, since this does not support larger files.
|
||||
class MemoryFile : public File {
|
||||
public:
|
||||
MemoryFile(const std::string& file_name);
|
||||
MemoryFile(const std::string& file_name, const std::string& mode);
|
||||
|
||||
/// @name File implementation overrides.
|
||||
/// @{
|
||||
|
@ -47,6 +47,7 @@ class MemoryFile : public File {
|
|||
bool Open() override;
|
||||
|
||||
private:
|
||||
std::string mode_;
|
||||
std::vector<uint8_t>* file_;
|
||||
uint64_t position_;
|
||||
|
||||
|
|
|
@ -27,12 +27,14 @@ class MemoryFileTest : public testing::Test {
|
|||
|
||||
TEST_F(MemoryFileTest, ModifiesSameFile) {
|
||||
scoped_ptr<File, FileCloser> writer(File::Open("memory://file1", "w"));
|
||||
ASSERT_TRUE(writer);
|
||||
ASSERT_EQ(kWriteBufferSize, writer->Write(kWriteBuffer, kWriteBufferSize));
|
||||
|
||||
// Since File::Open should not create a ThreadedIoFile so there should be
|
||||
// no cache.
|
||||
|
||||
scoped_ptr<File, FileCloser> reader(File::Open("memory://file1", "r"));
|
||||
ASSERT_TRUE(reader);
|
||||
|
||||
uint8_t read_buffer[kWriteBufferSize];
|
||||
ASSERT_EQ(kWriteBufferSize, reader->Read(read_buffer, kWriteBufferSize));
|
||||
|
@ -40,15 +42,19 @@ TEST_F(MemoryFileTest, ModifiesSameFile) {
|
|||
}
|
||||
|
||||
TEST_F(MemoryFileTest, SupportsDifferentFiles) {
|
||||
scoped_ptr<MemoryFile, FileCloser> writer(new MemoryFile("memory://file1"));
|
||||
scoped_ptr<MemoryFile, FileCloser> reader(new MemoryFile("memory://file2"));
|
||||
scoped_ptr<File, FileCloser> writer(File::Open("memory://file1", "w"));
|
||||
scoped_ptr<File, FileCloser> reader(File::Open("memory://file2", "w"));
|
||||
ASSERT_TRUE(writer);
|
||||
ASSERT_TRUE(reader);
|
||||
|
||||
ASSERT_EQ(kWriteBufferSize, writer->Write(kWriteBuffer, kWriteBufferSize));
|
||||
ASSERT_EQ(0, reader->Size());
|
||||
}
|
||||
|
||||
TEST_F(MemoryFileTest, SeekAndTell) {
|
||||
scoped_ptr<MemoryFile, FileCloser> file(new MemoryFile("memory://file1"));
|
||||
scoped_ptr<File, FileCloser> file(File::Open("memory://file1", "w"));
|
||||
ASSERT_TRUE(file);
|
||||
|
||||
ASSERT_EQ(kWriteBufferSize, file->Write(kWriteBuffer, kWriteBufferSize));
|
||||
ASSERT_TRUE(file->Seek(0));
|
||||
|
||||
|
@ -61,7 +67,9 @@ TEST_F(MemoryFileTest, SeekAndTell) {
|
|||
}
|
||||
|
||||
TEST_F(MemoryFileTest, EndOfFile) {
|
||||
scoped_ptr<MemoryFile, FileCloser> file(new MemoryFile("memory://file1"));
|
||||
scoped_ptr<File, FileCloser> file(File::Open("memory://file1", "w"));
|
||||
ASSERT_TRUE(file);
|
||||
|
||||
ASSERT_EQ(kWriteBufferSize, file->Write(kWriteBuffer, kWriteBufferSize));
|
||||
ASSERT_TRUE(file->Seek(0));
|
||||
|
||||
|
@ -75,7 +83,8 @@ TEST_F(MemoryFileTest, EndOfFile) {
|
|||
}
|
||||
|
||||
TEST_F(MemoryFileTest, ExtendsSize) {
|
||||
scoped_ptr<MemoryFile, FileCloser> file(new MemoryFile("memory://file1"));
|
||||
scoped_ptr<File, FileCloser> file(File::Open("memory://file1", "w"));
|
||||
ASSERT_TRUE(file);
|
||||
ASSERT_EQ(kWriteBufferSize, file->Write(kWriteBuffer, kWriteBufferSize));
|
||||
|
||||
ASSERT_EQ(kWriteBufferSize, file->Size());
|
||||
|
@ -87,5 +96,20 @@ TEST_F(MemoryFileTest, ExtendsSize) {
|
|||
EXPECT_EQ(2 * kWriteBufferSize, static_cast<int64_t>(size));
|
||||
}
|
||||
|
||||
TEST_F(MemoryFileTest, ReadMissingFileFails) {
|
||||
scoped_ptr<File, FileCloser> file(File::Open("memory://file1", "r"));
|
||||
EXPECT_FALSE(file);
|
||||
}
|
||||
|
||||
TEST_F(MemoryFileTest, WriteExistingFileDeletes) {
|
||||
scoped_ptr<File, FileCloser> file1(File::Open("memory://file1", "w"));
|
||||
ASSERT_TRUE(file1);
|
||||
ASSERT_EQ(kWriteBufferSize, file1->Write(kWriteBuffer, kWriteBufferSize));
|
||||
|
||||
scoped_ptr<File, FileCloser> file2(File::Open("memory://file1", "w"));
|
||||
ASSERT_TRUE(file2);
|
||||
EXPECT_EQ(0, file2->Size());
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
} // namespace edash_packager
|
||||
|
|
|
@ -49,7 +49,7 @@ Status MultiSegmentSegmenter::FinalizeSegment() {
|
|||
const uint64_t start_webm_timecode = cluster()->timecode();
|
||||
const uint64_t start_timescale = FromWebMTimecode(start_webm_timecode);
|
||||
const uint64_t length = static_cast<uint64_t>(
|
||||
cluster_length_sec() * stream()->info()->time_scale());
|
||||
cluster_length_sec() * info()->time_scale());
|
||||
muxer_listener()->OnNewSegment(start_timescale, length, size);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,234 @@
|
|||
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "packager/media/formats/webm/multi_segment_segmenter.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "packager/base/memory/scoped_ptr.h"
|
||||
#include "packager/media/formats/webm/segmenter_test_base.h"
|
||||
|
||||
namespace edash_packager {
|
||||
namespace media {
|
||||
namespace {
|
||||
|
||||
const uint64_t kDuration = 1000;
|
||||
|
||||
const uint8_t kBasicSupportDataInit[] = {
|
||||
// ID: EBML Header, Size: 31
|
||||
0x1a, 0x45, 0xdf, 0xa3, 0x9f,
|
||||
// EBMLVersion: 1
|
||||
0x42, 0x86, 0x81, 0x01,
|
||||
// EBMLReadVersion: 1
|
||||
0x42, 0xf7, 0x81, 0x01,
|
||||
// EBMLMaxIDLength: 4
|
||||
0x42, 0xf2, 0x81, 0x04,
|
||||
// EBMLMaxSizeLength: 8
|
||||
0x42, 0xf3, 0x81, 0x08,
|
||||
// DocType: 'webm'
|
||||
0x42, 0x82, 0x84, 0x77, 0x65, 0x62, 0x6d,
|
||||
// DocTypeVersion: 2
|
||||
0x42, 0x87, 0x81, 0x02,
|
||||
// DocTypeReadVersion: 2
|
||||
0x42, 0x85, 0x81, 0x02,
|
||||
// ID: Segment, Size: -1
|
||||
0x18, 0x53, 0x80, 0x67, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
// ID: Void, Size: 87
|
||||
0xec, 0xd7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// ID: Info, size: 50
|
||||
0x15, 0x49, 0xa9, 0x66, 0xb2,
|
||||
// TimecodeScale: 1000000
|
||||
0x2a, 0xd7, 0xb1, 0x83, 0x0f, 0x42, 0x40,
|
||||
// Duration: float(5000)
|
||||
0x44, 0x89, 0x84, 0x3f, 0x80, 0x00, 0x00,
|
||||
// MuxingApp: 'libwebm-0.2.1.0'
|
||||
0x4d, 0x80, 0x8f, 0x6c, 0x69, 0x62, 0x77, 0x65, 0x62, 0x6d, 0x2d, 0x30,
|
||||
0x2e, 0x32, 0x2e, 0x31, 0x2e, 0x30,
|
||||
// WritingApp: 'libwebm-0.2.1.0'
|
||||
0x57, 0x41, 0x8f, 0x6c, 0x69, 0x62, 0x77, 0x65, 0x62, 0x6d, 0x2d, 0x30,
|
||||
0x2e, 0x32, 0x2e, 0x31, 0x2e, 0x30,
|
||||
// ID: Tracks, size: 41
|
||||
0x16, 0x54, 0xae, 0x6b, 0xa9,
|
||||
// ID: Track, size: 39
|
||||
0xae, 0xa7,
|
||||
// TrackNumber: 1
|
||||
0xd7, 0x81, 0x01,
|
||||
// TrackUID: 1
|
||||
0x73, 0xc5, 0x81, 0x01,
|
||||
// TrackType: 1
|
||||
0x83, 0x81, 0x01,
|
||||
// CodecID: 'V_VP8'
|
||||
0x86, 0x85, 0x56, 0x5f, 0x56, 0x50, 0x38,
|
||||
// Language: 'en'
|
||||
0x22, 0xb5, 0x9c, 0x82, 0x65, 0x6e,
|
||||
// ID: Video, Size: 14
|
||||
0xe0, 0x8e,
|
||||
// PixelWidth: 100
|
||||
0xb0, 0x81, 0x64,
|
||||
// PixelHeight: 100
|
||||
0xba, 0x81, 0x64,
|
||||
// DisplayWidth: 100
|
||||
0x54, 0xb0, 0x81, 0x64,
|
||||
// DisplayHeight: 100
|
||||
0x54, 0xba, 0x81, 0x64
|
||||
};
|
||||
const uint8_t kBasicSupportDataSegment[] = {
|
||||
// ID: Cluster, size: 58
|
||||
0x1f, 0x43, 0xb6, 0x75, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a,
|
||||
// Timecode: 0
|
||||
0xe7, 0x81, 0x00,
|
||||
// ID: SimpleBlock, Size: 9
|
||||
0xa3, 0x89, 0x81, 0x00, 0x00, 0x80, 0xde, 0xad, 0xbe, 0xef, 0x00,
|
||||
// ID: SimpleBlock, Size: 9
|
||||
0xa3, 0x89, 0x81, 0x03, 0xe8, 0x80, 0xde, 0xad, 0xbe, 0xef, 0x00,
|
||||
// ID: SimpleBlock, Size: 9
|
||||
0xa3, 0x89, 0x81, 0x07, 0xd0, 0x80, 0xde, 0xad, 0xbe, 0xef, 0x00,
|
||||
// ID: SimpleBlock, Size: 9
|
||||
0xa3, 0x89, 0x81, 0x0b, 0xb8, 0x80, 0xde, 0xad, 0xbe, 0xef, 0x00,
|
||||
// ID: SimpleBlock, Size: 9
|
||||
0xa3, 0x89, 0x81, 0x0f, 0xa0, 0x80, 0xde, 0xad, 0xbe, 0xef, 0x00
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class MultiSegmentSegmenterTest : public SegmentTestBase {
|
||||
public:
|
||||
MultiSegmentSegmenterTest() : info_(CreateVideoStreamInfo()) {}
|
||||
|
||||
protected:
|
||||
void InitializeSegmenter(const MuxerOptions& options) {
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
CreateAndInitializeSegmenter<webm::MultiSegmentSegmenter>(
|
||||
options, info_.get(), &segmenter_));
|
||||
}
|
||||
|
||||
scoped_refptr<StreamInfo> info_;
|
||||
scoped_ptr<webm::Segmenter> segmenter_;
|
||||
};
|
||||
|
||||
TEST_F(MultiSegmentSegmenterTest, BasicSupport) {
|
||||
MuxerOptions options = CreateMuxerOptions();
|
||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
||||
|
||||
// Write the samples to the Segmenter.
|
||||
for (int i = 0; i < 5; i++) {
|
||||
scoped_refptr<MediaSample> sample = CreateSample(true, kDuration);
|
||||
ASSERT_OK(segmenter_->AddSample(sample));
|
||||
}
|
||||
ASSERT_OK(segmenter_->Finalize());
|
||||
|
||||
// Verify the resulting data.
|
||||
ASSERT_FILE_EQ(OutputFileName().c_str(), kBasicSupportDataInit);
|
||||
ASSERT_FILE_EQ(TemplateFileName(0).c_str(), kBasicSupportDataSegment);
|
||||
|
||||
// There is no second segment.
|
||||
EXPECT_FALSE(File::Open(TemplateFileName(1).c_str(), "r"));
|
||||
}
|
||||
|
||||
TEST_F(MultiSegmentSegmenterTest, SplitsFilesOnSegmentDuration) {
|
||||
MuxerOptions options = CreateMuxerOptions();
|
||||
options.segment_duration = 5; // seconds
|
||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
||||
|
||||
// Write the samples to the Segmenter.
|
||||
for (int i = 0; i < 8; i++) {
|
||||
scoped_refptr<MediaSample> sample = CreateSample(true, kDuration);
|
||||
ASSERT_OK(segmenter_->AddSample(sample));
|
||||
}
|
||||
ASSERT_OK(segmenter_->Finalize());
|
||||
|
||||
// Verify the resulting data.
|
||||
ClusterParser parser;
|
||||
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0)));
|
||||
ASSERT_EQ(1, parser.cluster_count());
|
||||
EXPECT_EQ(5, parser.GetFrameCountForCluster(0));
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(1)));
|
||||
ASSERT_EQ(1, parser.cluster_count());
|
||||
EXPECT_EQ(3, parser.GetFrameCountForCluster(0));
|
||||
|
||||
EXPECT_FALSE(File::Open(TemplateFileName(2).c_str(), "r"));
|
||||
}
|
||||
|
||||
TEST_F(MultiSegmentSegmenterTest, RespectsSegmentSAPAlign) {
|
||||
MuxerOptions options = CreateMuxerOptions();
|
||||
options.segment_duration = 3; // seconds
|
||||
options.segment_sap_aligned = true;
|
||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
||||
|
||||
// Write the samples to the Segmenter.
|
||||
for (int i = 0; i < 10; i++) {
|
||||
scoped_refptr<MediaSample> sample = CreateSample(i == 6, kDuration);
|
||||
ASSERT_OK(segmenter_->AddSample(sample));
|
||||
}
|
||||
ASSERT_OK(segmenter_->Finalize());
|
||||
|
||||
// Verify the resulting data.
|
||||
ClusterParser parser;
|
||||
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0)));
|
||||
ASSERT_EQ(1, parser.cluster_count());
|
||||
EXPECT_EQ(6, parser.GetFrameCountForCluster(0));
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(1)));
|
||||
ASSERT_EQ(1, parser.cluster_count());
|
||||
EXPECT_EQ(4, parser.GetFrameCountForCluster(0));
|
||||
|
||||
EXPECT_FALSE(File::Open(TemplateFileName(2).c_str(), "r"));
|
||||
}
|
||||
|
||||
TEST_F(MultiSegmentSegmenterTest, SplitsClustersOnFragmentDuration) {
|
||||
MuxerOptions options = CreateMuxerOptions();
|
||||
options.fragment_duration = 5; // seconds
|
||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
||||
|
||||
// Write the samples to the Segmenter.
|
||||
for (int i = 0; i < 8; i++) {
|
||||
scoped_refptr<MediaSample> sample = CreateSample(true, kDuration);
|
||||
ASSERT_OK(segmenter_->AddSample(sample));
|
||||
}
|
||||
ASSERT_OK(segmenter_->Finalize());
|
||||
|
||||
// Verify the resulting data.
|
||||
ClusterParser parser;
|
||||
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0)));
|
||||
ASSERT_EQ(2, parser.cluster_count());
|
||||
EXPECT_EQ(5, parser.GetFrameCountForCluster(0));
|
||||
EXPECT_EQ(3, parser.GetFrameCountForCluster(1));
|
||||
|
||||
EXPECT_FALSE(File::Open(TemplateFileName(1).c_str(), "r"));
|
||||
}
|
||||
|
||||
TEST_F(MultiSegmentSegmenterTest, RespectsFragmentSAPAlign) {
|
||||
MuxerOptions options = CreateMuxerOptions();
|
||||
options.fragment_duration = 3; // seconds
|
||||
options.fragment_sap_aligned = true;
|
||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
||||
|
||||
// Write the samples to the Segmenter.
|
||||
for (int i = 0; i < 10; i++) {
|
||||
scoped_refptr<MediaSample> sample = CreateSample(i == 6, kDuration);
|
||||
ASSERT_OK(segmenter_->AddSample(sample));
|
||||
}
|
||||
ASSERT_OK(segmenter_->Finalize());
|
||||
|
||||
// Verify the resulting data.
|
||||
ClusterParser parser;
|
||||
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromCluster(TemplateFileName(0)));
|
||||
ASSERT_EQ(2, parser.cluster_count());
|
||||
EXPECT_EQ(6, parser.GetFrameCountForCluster(0));
|
||||
EXPECT_EQ(4, parser.GetFrameCountForCluster(1));
|
||||
|
||||
EXPECT_FALSE(File::Open(TemplateFileName(1).c_str(), "r"));
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
} // namespace edash_packager
|
||||
|
|
@ -28,7 +28,7 @@ int64_t kSecondsToNs = 1000000000L;
|
|||
|
||||
Segmenter::Segmenter(const MuxerOptions& options)
|
||||
: options_(options),
|
||||
stream_(NULL),
|
||||
info_(NULL),
|
||||
muxer_listener_(NULL),
|
||||
progress_listener_(NULL),
|
||||
progress_target_(0),
|
||||
|
@ -43,15 +43,15 @@ Segmenter::Segmenter(const MuxerOptions& options)
|
|||
Segmenter::~Segmenter() {}
|
||||
|
||||
Status Segmenter::Initialize(scoped_ptr<MkvWriter> writer,
|
||||
MediaStream* streams,
|
||||
StreamInfo* info,
|
||||
ProgressListener* progress_listener,
|
||||
MuxerListener* muxer_listener,
|
||||
KeySource* encryption_key_source) {
|
||||
muxer_listener_ = muxer_listener;
|
||||
stream_ = streams;
|
||||
info_ = info;
|
||||
|
||||
// Use media duration as progress target.
|
||||
progress_target_ = stream_->info()->duration();
|
||||
progress_target_ = info_->duration();
|
||||
progress_listener_ = progress_listener;
|
||||
|
||||
segment_info_.Init();
|
||||
|
@ -64,18 +64,16 @@ Status Segmenter::Initialize(scoped_ptr<MkvWriter> writer,
|
|||
|
||||
// Create the track info.
|
||||
Status status;
|
||||
switch (stream_->info()->stream_type()) {
|
||||
switch (info_->stream_type()) {
|
||||
case kStreamVideo:
|
||||
status = CreateVideoTrack(
|
||||
static_cast<VideoStreamInfo*>(stream_->info().get()));
|
||||
status = CreateVideoTrack(static_cast<VideoStreamInfo*>(info_));
|
||||
break;
|
||||
case kStreamAudio:
|
||||
status = CreateAudioTrack(
|
||||
static_cast<AudioStreamInfo*>(stream_->info().get()));
|
||||
status = CreateAudioTrack(static_cast<AudioStreamInfo*>(info_));
|
||||
break;
|
||||
default:
|
||||
NOTIMPLEMENTED() << "Not implemented for stream type: "
|
||||
<< stream_->info()->stream_type();
|
||||
<< info_->stream_type();
|
||||
status = Status(error::UNIMPLEMENTED, "Not implemented for stream type");
|
||||
}
|
||||
if (!status.ok())
|
||||
|
@ -118,14 +116,14 @@ Status Segmenter::AddSample(scoped_refptr<MediaSample> sample) {
|
|||
return status;
|
||||
|
||||
const int64_t time_ns =
|
||||
sample->pts() * kSecondsToNs / stream_->info()->time_scale();
|
||||
sample->pts() * kSecondsToNs / info_->time_scale();
|
||||
if (!cluster_->AddFrame(sample->data(), sample->data_size(), track_id_,
|
||||
time_ns, sample->is_key_frame())) {
|
||||
LOG(ERROR) << "Error adding sample to segment.";
|
||||
return Status(error::FILE_FAILURE, "Error adding sample to segment.");
|
||||
}
|
||||
const double duration_sec =
|
||||
static_cast<double>(sample->duration()) / stream_->info()->time_scale();
|
||||
static_cast<double>(sample->duration()) / info_->time_scale();
|
||||
cluster_length_sec_ += duration_sec;
|
||||
segment_length_sec_ += duration_sec;
|
||||
total_duration_ += sample->duration();
|
||||
|
@ -141,14 +139,14 @@ float Segmenter::GetDuration() const {
|
|||
uint64_t Segmenter::FromBMFFTimescale(uint64_t time_timescale) {
|
||||
// Convert the time from BMFF time_code to WebM timecode scale.
|
||||
const int64_t time_ns =
|
||||
kSecondsToNs * time_timescale / stream_->info()->time_scale();
|
||||
kSecondsToNs * time_timescale / info_->time_scale();
|
||||
return time_ns / segment_info_.timecode_scale();
|
||||
}
|
||||
|
||||
uint64_t Segmenter::FromWebMTimecode(uint64_t time_webm_timecode) {
|
||||
// Convert the time to BMFF time_code from WebM timecode scale.
|
||||
const int64_t time_ns = time_webm_timecode * segment_info_.timecode_scale();
|
||||
return time_ns * stream_->info()->time_scale() / kSecondsToNs;
|
||||
return time_ns * info_->time_scale() / kSecondsToNs;
|
||||
}
|
||||
|
||||
Status Segmenter::WriteSegmentHeader(uint64_t file_size, MkvWriter* writer) {
|
||||
|
|
|
@ -22,7 +22,7 @@ struct MuxerOptions;
|
|||
class AudioStreamInfo;
|
||||
class KeySource;
|
||||
class MediaSample;
|
||||
class MediaStream;
|
||||
class StreamInfo;
|
||||
class MuxerListener;
|
||||
class ProgressListener;
|
||||
class StreamInfo;
|
||||
|
@ -39,14 +39,14 @@ class Segmenter {
|
|||
/// Calling other public methods of this class without this method returning
|
||||
/// Status::OK results in an undefined behavior.
|
||||
/// @param writer contains the output file (or init file in multi-segment).
|
||||
/// @param streams contains the MediaStream to be segmented.
|
||||
/// @param info The stream info for the stream being segmented.
|
||||
/// @param muxer_listener receives muxer events. Can be NULL.
|
||||
/// @param encryption_key_source points to the key source which contains
|
||||
/// the encryption keys. It can be NULL to indicate that no encryption
|
||||
/// is required.
|
||||
/// @return OK on success, an error status otherwise.
|
||||
Status Initialize(scoped_ptr<MkvWriter> writer,
|
||||
MediaStream* streams,
|
||||
StreamInfo* info,
|
||||
ProgressListener* progress_listener,
|
||||
MuxerListener* muxer_listener,
|
||||
KeySource* encryption_key_source);
|
||||
|
@ -94,7 +94,7 @@ class Segmenter {
|
|||
mkvmuxer::Cluster* cluster() { return cluster_.get(); }
|
||||
mkvmuxer::Cues* cues() { return &cues_; }
|
||||
MuxerListener* muxer_listener() { return muxer_listener_; }
|
||||
MediaStream* stream() { return stream_; }
|
||||
StreamInfo* info() { return info_; }
|
||||
SeekHead* seek_head() { return &seek_head_; }
|
||||
|
||||
int track_id() const { return track_id_; }
|
||||
|
@ -128,7 +128,7 @@ class Segmenter {
|
|||
mkvmuxer::SegmentInfo segment_info_;
|
||||
mkvmuxer::Tracks tracks_;
|
||||
|
||||
MediaStream* stream_;
|
||||
StreamInfo* info_;
|
||||
MuxerListener* muxer_listener_;
|
||||
ProgressListener* progress_listener_;
|
||||
uint64_t progress_target_;
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
#include "packager/media/formats/webm/segmenter_test_base.h"
|
||||
|
||||
#include "packager/media/base/muxer_util.h"
|
||||
#include "packager/media/file/memory_file.h"
|
||||
#include "packager/media/formats/webm/webm_constants.h"
|
||||
|
||||
namespace edash_packager {
|
||||
namespace media {
|
||||
namespace {
|
||||
|
||||
// The contents of a frame does not mater.
|
||||
const uint8_t kTestMediaSampleData[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x00};
|
||||
const size_t kTestMediaSampleDataSize = sizeof(kTestMediaSampleData);
|
||||
|
||||
const int kTrackId = 1;
|
||||
const uint32_t kTimeScale = 1000;
|
||||
const uint64_t kDuration = 8000;
|
||||
const VideoCodec kVideoCodec = kCodecVP8;
|
||||
const std::string kCodecString = "vp8";
|
||||
const std::string kLanguage = "en";
|
||||
const uint16_t kWidth = 100;
|
||||
const uint16_t kHeight = 100;
|
||||
const uint16_t kPixelWidth = 100;
|
||||
const uint16_t kPixelHeight = 100;
|
||||
const int16_t kTrickPlayRate = 1;
|
||||
const uint8_t kNaluLengthSize = 0;
|
||||
|
||||
} // namespace
|
||||
|
||||
SegmentTestBase::SegmentTestBase() {}
|
||||
|
||||
void SegmentTestBase::SetUp() {
|
||||
output_file_name_ = "memory://output-file.webm";
|
||||
segment_template_ = "memory://output-template-$Number$.webm";
|
||||
cur_time_timescale_ = 0;
|
||||
single_segment_ = true;
|
||||
}
|
||||
|
||||
void SegmentTestBase::TearDown() {
|
||||
MemoryFile::DeleteAll();
|
||||
}
|
||||
|
||||
scoped_refptr<MediaSample> SegmentTestBase::CreateSample(bool is_key_frame,
|
||||
uint64_t duration) {
|
||||
scoped_refptr<MediaSample> sample = MediaSample::CopyFrom(
|
||||
kTestMediaSampleData, kTestMediaSampleDataSize, is_key_frame);
|
||||
sample->set_dts(cur_time_timescale_);
|
||||
sample->set_pts(cur_time_timescale_);
|
||||
sample->set_duration(duration);
|
||||
|
||||
cur_time_timescale_ += duration;
|
||||
return sample.Pass();
|
||||
}
|
||||
|
||||
MuxerOptions SegmentTestBase::CreateMuxerOptions() const {
|
||||
MuxerOptions ret;
|
||||
ret.single_segment = single_segment_;
|
||||
ret.output_file_name = output_file_name_;
|
||||
ret.segment_template = segment_template_;
|
||||
ret.segment_duration = 30; // seconds
|
||||
ret.fragment_duration = 30; // seconds
|
||||
ret.segment_sap_aligned = false;
|
||||
ret.fragment_sap_aligned = false;
|
||||
// Use memory files for temp storage. Normally this would be a bad idea
|
||||
// since it wouldn't support large files, but for tests the files are small.
|
||||
ret.temp_dir = "memory://temp/";
|
||||
return ret;
|
||||
}
|
||||
|
||||
VideoStreamInfo* SegmentTestBase::CreateVideoStreamInfo() const {
|
||||
return new VideoStreamInfo(kTrackId, kTimeScale, kDuration, kVideoCodec,
|
||||
kCodecString, kLanguage, kWidth, kHeight,
|
||||
kPixelWidth, kPixelHeight, kTrickPlayRate,
|
||||
kNaluLengthSize, NULL, 0, false);
|
||||
}
|
||||
|
||||
std::string SegmentTestBase::OutputFileName() const {
|
||||
return output_file_name_;
|
||||
}
|
||||
|
||||
std::string SegmentTestBase::TemplateFileName(int number) const {
|
||||
return GetSegmentName(segment_template_, 0, number, 0);
|
||||
}
|
||||
|
||||
SegmentTestBase::ClusterParser::ClusterParser() : in_cluster_(false) {}
|
||||
|
||||
SegmentTestBase::ClusterParser::~ClusterParser() {}
|
||||
|
||||
void SegmentTestBase::ClusterParser::PopulateFromCluster(
|
||||
const std::string& file_name) {
|
||||
cluster_sizes_.clear();
|
||||
std::string file_contents;
|
||||
ASSERT_TRUE(File::ReadFileToString(file_name.c_str(), &file_contents));
|
||||
|
||||
const uint8_t* data = reinterpret_cast<const uint8_t*>(file_contents.c_str());
|
||||
const size_t size = file_contents.size();
|
||||
WebMListParser cluster_parser(kWebMIdCluster, this);
|
||||
size_t position = 0;
|
||||
while (position < size) {
|
||||
int read = cluster_parser.Parse(data + position, size - position);
|
||||
ASSERT_LT(0, read);
|
||||
|
||||
cluster_parser.Reset();
|
||||
position += read;
|
||||
}
|
||||
}
|
||||
|
||||
void SegmentTestBase::ClusterParser::PopulateFromSegment(
|
||||
const std::string& file_name) {
|
||||
cluster_sizes_.clear();
|
||||
std::string file_contents;
|
||||
ASSERT_TRUE(File::ReadFileToString(file_name.c_str(), &file_contents));
|
||||
|
||||
const uint8_t* data = reinterpret_cast<const uint8_t*>(file_contents.c_str());
|
||||
const size_t size = file_contents.size();
|
||||
WebMListParser header_parser(kWebMIdEBMLHeader, this);
|
||||
int offset = header_parser.Parse(data, size);
|
||||
ASSERT_LT(0, offset);
|
||||
|
||||
WebMListParser segment_parser(kWebMIdSegment, this);
|
||||
ASSERT_LT(0, segment_parser.Parse(data + offset, size - offset));
|
||||
}
|
||||
|
||||
int SegmentTestBase::ClusterParser::GetFrameCountForCluster(size_t i) const {
|
||||
DCHECK(i < cluster_sizes_.size());
|
||||
return cluster_sizes_[i];
|
||||
}
|
||||
|
||||
int SegmentTestBase::ClusterParser::cluster_count() const {
|
||||
return cluster_sizes_.size();
|
||||
}
|
||||
|
||||
WebMParserClient* SegmentTestBase::ClusterParser::OnListStart(int id) {
|
||||
if (id == kWebMIdCluster) {
|
||||
if (in_cluster_)
|
||||
return NULL;
|
||||
|
||||
cluster_sizes_.push_back(0);
|
||||
in_cluster_ = true;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
bool SegmentTestBase::ClusterParser::OnListEnd(int id) {
|
||||
if (id == kWebMIdCluster) {
|
||||
if (!in_cluster_)
|
||||
return false;
|
||||
in_cluster_ = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SegmentTestBase::ClusterParser::OnUInt(int id, int64_t val) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SegmentTestBase::ClusterParser::OnFloat(int id, double val) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SegmentTestBase::ClusterParser::OnBinary(int id,
|
||||
const uint8_t* data,
|
||||
int size) {
|
||||
if (in_cluster_ && id == kWebMIdSimpleBlock) {
|
||||
cluster_sizes_[cluster_sizes_.size() - 1]++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SegmentTestBase::ClusterParser::OnString(int id, const std::string& str) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace media
|
||||
} // namespace edash_packager
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
#ifndef MEDIA_FORMATS_WEBM_SEGMENTER_TEST_UTILS_H_
|
||||
#define MEDIA_FORMATS_WEBM_SEGMENTER_TEST_UTILS_H_
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "packager/media/base/media_sample.h"
|
||||
#include "packager/media/base/muxer_options.h"
|
||||
#include "packager/media/base/video_stream_info.h"
|
||||
#include "packager/media/base/status.h"
|
||||
#include "packager/media/base/stream_info.h"
|
||||
#include "packager/media/base/test/status_test_util.h"
|
||||
#include "packager/media/file/file_closer.h"
|
||||
#include "packager/media/file/file_test_util.h"
|
||||
#include "packager/media/file/memory_file.h"
|
||||
#include "packager/media/formats/webm/mkv_writer.h"
|
||||
#include "packager/media/formats/webm/segmenter.h"
|
||||
#include "packager/media/formats/webm/webm_parser.h"
|
||||
|
||||
namespace edash_packager {
|
||||
namespace media {
|
||||
|
||||
class SegmentTestBase : public ::testing::Test {
|
||||
protected:
|
||||
SegmentTestBase();
|
||||
|
||||
void SetUp() override;
|
||||
void TearDown() override;
|
||||
|
||||
/// Creates a Segmenter of the given type and initializes it.
|
||||
template <typename S>
|
||||
void CreateAndInitializeSegmenter(const MuxerOptions& options,
|
||||
StreamInfo* info,
|
||||
scoped_ptr<webm::Segmenter>* result) const {
|
||||
scoped_ptr<S> segmenter(new S(options));
|
||||
|
||||
scoped_ptr<MkvWriter> writer(new MkvWriter());
|
||||
ASSERT_OK(writer->Open(this->output_file_name_));
|
||||
ASSERT_OK(segmenter->Initialize(writer.Pass(), info, NULL, NULL, NULL));
|
||||
*result = segmenter.Pass();
|
||||
}
|
||||
|
||||
/// Creates a new media sample.
|
||||
scoped_refptr<MediaSample> CreateSample(bool is_key_frame, uint64_t duration);
|
||||
/// Creates a Muxer options object for testing.
|
||||
MuxerOptions CreateMuxerOptions() const;
|
||||
/// Creates a video stream info object for testing.
|
||||
VideoStreamInfo* CreateVideoStreamInfo() const;
|
||||
|
||||
/// Gets the file name of the current output file.
|
||||
std::string OutputFileName() const;
|
||||
/// Gets the file name of the given template file.
|
||||
std::string TemplateFileName(int number) const;
|
||||
|
||||
protected:
|
||||
// A helper class used to determine the number of clusters and frames for a
|
||||
// given WebM file.
|
||||
class ClusterParser : private WebMParserClient {
|
||||
public:
|
||||
ClusterParser();
|
||||
~ClusterParser() override;
|
||||
|
||||
// Make sure to use ASSERT_NO_FATAL_FAILURE.
|
||||
void PopulateFromCluster(const std::string& file_name);
|
||||
void PopulateFromSegment(const std::string& file_name);
|
||||
|
||||
int GetFrameCountForCluster(size_t i) const;
|
||||
|
||||
int cluster_count() const;
|
||||
|
||||
private:
|
||||
// WebMParserClient overrides.
|
||||
WebMParserClient* OnListStart(int id) override;
|
||||
bool OnListEnd(int id) override;
|
||||
bool OnUInt(int id, int64_t val) override;
|
||||
bool OnFloat(int id, double val) override;
|
||||
bool OnBinary(int id, const uint8_t* data, int size) override;
|
||||
bool OnString(int id, const std::string& str) override;
|
||||
|
||||
private:
|
||||
std::vector<int> cluster_sizes_;
|
||||
bool in_cluster_;
|
||||
};
|
||||
|
||||
protected:
|
||||
std::string output_file_name_;
|
||||
std::string segment_template_;
|
||||
uint64_t cur_time_timescale_;
|
||||
bool single_segment_;
|
||||
};
|
||||
|
||||
} // namespace media
|
||||
} // namespace edash_packager
|
||||
|
||||
#endif // MEDIA_FORMATS_WEBM_SEGMENTER_TEST_UTILS_H_
|
|
@ -32,6 +32,7 @@ Status SingleSegmentSegmenter::DoFinalize() {
|
|||
|
||||
// Write the Cues to the end of the file.
|
||||
index_start_ = writer_->Position();
|
||||
seek_head()->set_cues_pos(index_start_);
|
||||
if (!cues()->Write(writer_.get()))
|
||||
return Status(error::FILE_FAILURE, "Error writing Cues data.");
|
||||
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "packager/media/formats/webm/single_segment_segmenter.h"
|
||||
#include "packager/media/formats/webm/two_pass_single_segment_segmenter.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "packager/base/memory/scoped_ptr.h"
|
||||
#include "packager/media/formats/webm/segmenter_test_base.h"
|
||||
|
||||
namespace edash_packager {
|
||||
namespace media {
|
||||
namespace {
|
||||
|
||||
const uint64_t kDuration = 1000;
|
||||
|
||||
const uint8_t kBasicSupportData[] = {
|
||||
// ID: EBML Header, Size: 31
|
||||
0x1a, 0x45, 0xdf, 0xa3, 0x9f,
|
||||
// EBMLVersion: 1
|
||||
0x42, 0x86, 0x81, 0x01,
|
||||
// EBMLReadVersion: 1
|
||||
0x42, 0xf7, 0x81, 0x01,
|
||||
// EBMLMaxIDLength: 4
|
||||
0x42, 0xf2, 0x81, 0x04,
|
||||
// EBMLMaxSizeLength: 8
|
||||
0x42, 0xf3, 0x81, 0x08,
|
||||
// DocType: 'webm'
|
||||
0x42, 0x82, 0x84, 0x77, 0x65, 0x62, 0x6d,
|
||||
// DocTypeVersion: 2
|
||||
0x42, 0x87, 0x81, 0x02,
|
||||
// DocTypeReadVersion: 2
|
||||
0x42, 0x85, 0x81, 0x02,
|
||||
// ID: Segment, Size: 287
|
||||
0x18, 0x53, 0x80, 0x67, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x16,
|
||||
// ID: SeekHead, Size: 29
|
||||
0x11, 0x4d, 0x9b, 0x74, 0x9d,
|
||||
// ID: Seek, Size: 11
|
||||
0x4d, 0xbb, 0x8b,
|
||||
// SeekID: binary(4)
|
||||
0x53, 0xab, 0x84, 0x1f, 0x43, 0xb6, 0x75,
|
||||
// SeekPosition: 238
|
||||
0x53, 0xac, 0x81, 0xee,
|
||||
// ID: Seek, Size: 12
|
||||
0x4d, 0xbb, 0x8c,
|
||||
// SeekID: binary(4)
|
||||
0x53, 0xab, 0x84, 0x1c, 0x53, 0xbb, 0x6b,
|
||||
// SeekPosition: 174
|
||||
0x53, 0xac, 0x82, 0x01, 0x34,
|
||||
// ID: Void, Size: 53
|
||||
0xec, 0xb5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// ID: Info, size: 50
|
||||
0x15, 0x49, 0xa9, 0x66, 0xb2,
|
||||
// TimecodeScale: 1000000
|
||||
0x2a, 0xd7, 0xb1, 0x83, 0x0f, 0x42, 0x40,
|
||||
// Duration: float(5000)
|
||||
0x44, 0x89, 0x84, 0x45, 0x9c, 0x40, 0x00,
|
||||
// MuxingApp: 'libwebm-0.2.1.0'
|
||||
0x4d, 0x80, 0x8f, 0x6c, 0x69, 0x62, 0x77, 0x65, 0x62, 0x6d, 0x2d, 0x30,
|
||||
0x2e, 0x32, 0x2e, 0x31, 0x2e, 0x30,
|
||||
// WritingApp: 'libwebm-0.2.1.0'
|
||||
0x57, 0x41, 0x8f, 0x6c, 0x69, 0x62, 0x77, 0x65, 0x62, 0x6d, 0x2d, 0x30,
|
||||
0x2e, 0x32, 0x2e, 0x31, 0x2e, 0x30,
|
||||
// ID: Tracks, size: 41
|
||||
0x16, 0x54, 0xae, 0x6b, 0xa9,
|
||||
// ID: Track, size: 39
|
||||
0xae, 0xa7,
|
||||
// TrackNumber: 1
|
||||
0xd7, 0x81, 0x01,
|
||||
// TrackUID: 1
|
||||
0x73, 0xc5, 0x81, 0x01,
|
||||
// TrackType: 1
|
||||
0x83, 0x81, 0x01,
|
||||
// CodecID: 'V_VP8'
|
||||
0x86, 0x85, 0x56, 0x5f, 0x56, 0x50, 0x38,
|
||||
// Language: 'en'
|
||||
0x22, 0xb5, 0x9c, 0x82, 0x65, 0x6e,
|
||||
// ID: Video, Size: 14
|
||||
0xe0, 0x8e,
|
||||
// PixelWidth: 100
|
||||
0xb0, 0x81, 0x64,
|
||||
// PixelHeight: 100
|
||||
0xba, 0x81, 0x64,
|
||||
// DisplayWidth: 100
|
||||
0x54, 0xb0, 0x81, 0x64,
|
||||
// DisplayHeight: 100
|
||||
0x54, 0xba, 0x81, 0x64,
|
||||
// ID: Cluster, size: 58
|
||||
0x1f, 0x43, 0xb6, 0x75, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a,
|
||||
// Timecode: 0
|
||||
0xe7, 0x81, 0x00,
|
||||
// ID: SimpleBlock, Size: 9
|
||||
0xa3, 0x89, 0x81, 0x00, 0x00, 0x80, 0xde, 0xad, 0xbe, 0xef, 0x00,
|
||||
// ID: SimpleBlock, Size: 9
|
||||
0xa3, 0x89, 0x81, 0x03, 0xe8, 0x80, 0xde, 0xad, 0xbe, 0xef, 0x00,
|
||||
// ID: SimpleBlock, Size: 9
|
||||
0xa3, 0x89, 0x81, 0x07, 0xd0, 0x80, 0xde, 0xad, 0xbe, 0xef, 0x00,
|
||||
// ID: SimpleBlock, Size: 9
|
||||
0xa3, 0x89, 0x81, 0x0b, 0xb8, 0x80, 0xde, 0xad, 0xbe, 0xef, 0x00,
|
||||
// ID: SimpleBlock, Size: 9
|
||||
0xa3, 0x89, 0x81, 0x0f, 0xa0, 0x80, 0xde, 0xad, 0xbe, 0xef, 0x00,
|
||||
// ID: Cues, Size: 13
|
||||
0x1c, 0x53, 0xbb, 0x6b, 0x8d,
|
||||
// ID: CuePoint, Size: 11
|
||||
0xbb, 0x8b,
|
||||
// CueTime: 0
|
||||
0xb3, 0x81, 0x00,
|
||||
// ID: CueTrackPositions, Size: 6
|
||||
0xb7, 0x86,
|
||||
// CueTrack: 1
|
||||
0xf7, 0x81, 0x01,
|
||||
// CueClusterPosition: 190
|
||||
0xf1, 0x81, 0xbe
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// This is a parameterized test that tests both SingleSegmentSegmenter and
|
||||
// TwoPassSingleSegmentSegmenter, since they should provide the exact same
|
||||
// output.
|
||||
class SingleSegmentSegmenterTest : public SegmentTestBase,
|
||||
public ::testing::WithParamInterface<bool> {
|
||||
public:
|
||||
SingleSegmentSegmenterTest() : info_(CreateVideoStreamInfo()) {}
|
||||
|
||||
protected:
|
||||
void InitializeSegmenter(const MuxerOptions& options) {
|
||||
if (!GetParam()) {
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
CreateAndInitializeSegmenter<webm::SingleSegmentSegmenter>(
|
||||
options, info_.get(), &segmenter_));
|
||||
} else {
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
CreateAndInitializeSegmenter<webm::TwoPassSingleSegmentSegmenter>(
|
||||
options, info_.get(), &segmenter_));
|
||||
}
|
||||
}
|
||||
|
||||
scoped_refptr<StreamInfo> info_;
|
||||
scoped_ptr<webm::Segmenter> segmenter_;
|
||||
};
|
||||
|
||||
TEST_P(SingleSegmentSegmenterTest, BasicSupport) {
|
||||
MuxerOptions options = CreateMuxerOptions();
|
||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
||||
|
||||
// Write the samples to the Segmenter.
|
||||
for (int i = 0; i < 5; i++) {
|
||||
scoped_refptr<MediaSample> sample = CreateSample(true, kDuration);
|
||||
ASSERT_OK(segmenter_->AddSample(sample));
|
||||
}
|
||||
ASSERT_OK(segmenter_->Finalize());
|
||||
|
||||
ASSERT_FILE_EQ(OutputFileName().c_str(), kBasicSupportData);
|
||||
}
|
||||
|
||||
TEST_P(SingleSegmentSegmenterTest, SplitsClustersOnSegmentDuration) {
|
||||
MuxerOptions options = CreateMuxerOptions();
|
||||
options.segment_duration = 4.5; // seconds
|
||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
||||
|
||||
// Write the samples to the Segmenter.
|
||||
for (int i = 0; i < 8; i++) {
|
||||
scoped_refptr<MediaSample> sample = CreateSample(true, kDuration);
|
||||
ASSERT_OK(segmenter_->AddSample(sample));
|
||||
}
|
||||
ASSERT_OK(segmenter_->Finalize());
|
||||
|
||||
// Verify the resulting data.
|
||||
ClusterParser parser;
|
||||
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromSegment(OutputFileName()));
|
||||
ASSERT_EQ(2, parser.cluster_count());
|
||||
EXPECT_EQ(5, parser.GetFrameCountForCluster(0));
|
||||
EXPECT_EQ(3, parser.GetFrameCountForCluster(1));
|
||||
}
|
||||
|
||||
TEST_P(SingleSegmentSegmenterTest, IgnoresFragmentDuration) {
|
||||
MuxerOptions options = CreateMuxerOptions();
|
||||
options.fragment_duration = 5; // seconds
|
||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
||||
|
||||
// Write the samples to the Segmenter.
|
||||
for (int i = 0; i < 8; i++) {
|
||||
scoped_refptr<MediaSample> sample = CreateSample(true, kDuration);
|
||||
ASSERT_OK(segmenter_->AddSample(sample));
|
||||
}
|
||||
ASSERT_OK(segmenter_->Finalize());
|
||||
|
||||
// Verify the resulting data.
|
||||
ClusterParser parser;
|
||||
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromSegment(OutputFileName()));
|
||||
ASSERT_EQ(1, parser.cluster_count());
|
||||
EXPECT_EQ(8, parser.GetFrameCountForCluster(0));
|
||||
}
|
||||
|
||||
TEST_P(SingleSegmentSegmenterTest, RespectsSAPAlign) {
|
||||
MuxerOptions options = CreateMuxerOptions();
|
||||
options.segment_duration = 3; // seconds
|
||||
options.segment_sap_aligned = true;
|
||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
||||
|
||||
// Write the samples to the Segmenter.
|
||||
for (int i = 0; i < 10; i++) {
|
||||
scoped_refptr<MediaSample> sample = CreateSample(i == 6, kDuration);
|
||||
ASSERT_OK(segmenter_->AddSample(sample));
|
||||
}
|
||||
ASSERT_OK(segmenter_->Finalize());
|
||||
|
||||
// Verify the resulting data.
|
||||
ClusterParser parser;
|
||||
ASSERT_NO_FATAL_FAILURE(parser.PopulateFromSegment(OutputFileName()));
|
||||
// Segments are 1 second, so there would normally be 3 frames per cluster,
|
||||
// but since it's SAP aligned and only frame 7 is a key-frame, there are
|
||||
// two clusters with 6 and 4 frames respectively.
|
||||
ASSERT_EQ(2, parser.cluster_count());
|
||||
EXPECT_EQ(6, parser.GetFrameCountForCluster(0));
|
||||
EXPECT_EQ(4, parser.GetFrameCountForCluster(1));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(TrueIsTwoPass,
|
||||
SingleSegmentSegmenterTest,
|
||||
::testing::Bool());
|
||||
|
||||
} // namespace media
|
||||
} // namespace edash_packager
|
||||
|
|
@ -75,13 +75,13 @@ Status
|
|||
TwoPassSingleSegmentSegmenter::DoInitialize(scoped_ptr<MkvWriter> writer) {
|
||||
// Assume the amount of time to copy the temp file as the same amount
|
||||
// of time as to make it.
|
||||
set_progress_target(stream()->info()->duration() * 2);
|
||||
set_progress_target(info()->duration() * 2);
|
||||
|
||||
real_writer_ = writer.Pass();
|
||||
|
||||
std::string temp_name = TempFileName(options());
|
||||
temp_file_name_ = TempFileName(options());
|
||||
scoped_ptr<MkvWriter> temp(new MkvWriter);
|
||||
Status status = temp->Open(temp_name);
|
||||
Status status = temp->Open(temp_file_name_);
|
||||
if (!status.ok())
|
||||
return status;
|
||||
|
||||
|
@ -105,9 +105,9 @@ Status TwoPassSingleSegmentSegmenter::DoFinalize() {
|
|||
return temp;
|
||||
|
||||
// Close the temp file and open it for reading.
|
||||
std::string temp_name = writer()->file()->file_name();
|
||||
set_writer(scoped_ptr<MkvWriter>());
|
||||
scoped_ptr<File, FileCloser> temp_reader(File::Open(temp_name.c_str(), "r"));
|
||||
scoped_ptr<File, FileCloser> temp_reader(
|
||||
File::Open(temp_file_name_.c_str(), "r"));
|
||||
if (!temp_reader)
|
||||
return Status(error::FILE_FAILURE, "Error opening temp file.");
|
||||
|
||||
|
@ -123,8 +123,8 @@ Status TwoPassSingleSegmentSegmenter::DoFinalize() {
|
|||
|
||||
// Close and delete the temp file.
|
||||
temp_reader.reset();
|
||||
if (!File::Delete(temp_name.c_str())) {
|
||||
LOG(WARNING) << "Unable to delete temporary file " << temp_name;
|
||||
if (!File::Delete(temp_file_name_.c_str())) {
|
||||
LOG(WARNING) << "Unable to delete temporary file " << temp_file_name_;
|
||||
}
|
||||
|
||||
// Set the writer back to the real file so GetIndexRangeStartAndEnd works.
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#ifndef MEDIA_FORMATS_WEBM_TWO_PASS_SINGLE_SEGMENT_SEGMENTER_H_
|
||||
#define MEDIA_FORMATS_WEBM_TWO_PASS_SINGLE_SEGMENT_SEGMENTER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "packager/media/formats/webm/single_segment_segmenter.h"
|
||||
|
||||
#include "packager/base/memory/scoped_ptr.h"
|
||||
|
@ -41,6 +43,7 @@ class TwoPassSingleSegmentSegmenter : public SingleSegmentSegmenter {
|
|||
uint64_t last_size);
|
||||
|
||||
scoped_ptr<MkvWriter> real_writer_;
|
||||
std::string temp_file_name_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TwoPassSingleSegmentSegmenter);
|
||||
};
|
||||
|
|
|
@ -65,8 +65,12 @@
|
|||
'sources': [
|
||||
'cluster_builder.cc',
|
||||
'cluster_builder.h',
|
||||
'multi_segment_segmenter_unittest.cc',
|
||||
'opus_packet_builder.cc',
|
||||
'opus_packet_builder.h',
|
||||
'segmenter_test_base.cc',
|
||||
'segmenter_test_base.h',
|
||||
'single_segment_segmenter_unittest.cc',
|
||||
'tracks_builder.cc',
|
||||
'tracks_builder.h',
|
||||
'webm_cluster_parser_unittest.cc',
|
||||
|
@ -78,6 +82,7 @@
|
|||
'dependencies': [
|
||||
'../../../testing/gtest.gyp:gtest',
|
||||
'../../../testing/gmock.gyp:gmock',
|
||||
'../../file/file.gyp:file',
|
||||
'../../test/media_test.gyp:media_test_support',
|
||||
'webm',
|
||||
]
|
||||
|
|
|
@ -37,8 +37,8 @@ Status WebMMuxer::Initialize() {
|
|||
segmenter_.reset(new TwoPassSingleSegmentSegmenter(options()));
|
||||
}
|
||||
|
||||
Status initialized =
|
||||
segmenter_->Initialize(writer.Pass(), streams()[0], progress_listener(),
|
||||
Status initialized = segmenter_->Initialize(
|
||||
writer.Pass(), streams()[0]->info().get(), progress_listener(),
|
||||
muxer_listener(), encryption_key_source());
|
||||
|
||||
if (!initialized.ok())
|
||||
|
|
Loading…
Reference in New Issue