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) {
|
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) {
|
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();
|
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) {
|
std::vector<uint8_t>* GetFile(const std::string& file_name) {
|
||||||
return &files_[file_name];
|
return &files_[file_name];
|
||||||
}
|
}
|
||||||
|
@ -50,10 +54,8 @@ scoped_ptr<FileSystem> FileSystem::g_file_system_;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
MemoryFile::MemoryFile(const std::string& file_name)
|
MemoryFile::MemoryFile(const std::string& file_name, const std::string& mode)
|
||||||
: File(file_name),
|
: File(file_name), mode_(mode), file_(NULL), position_(0) {}
|
||||||
file_(FileSystem::Instance()->GetFile(file_name)),
|
|
||||||
position_(0) {}
|
|
||||||
|
|
||||||
MemoryFile::~MemoryFile() {}
|
MemoryFile::~MemoryFile() {}
|
||||||
|
|
||||||
|
@ -86,6 +88,7 @@ int64_t MemoryFile::Write(const void* buffer, uint64_t length) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t MemoryFile::Size() {
|
int64_t MemoryFile::Size() {
|
||||||
|
DCHECK(file_);
|
||||||
return file_->size();
|
return file_->size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,6 +110,19 @@ bool MemoryFile::Tell(uint64_t* position) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MemoryFile::Open() {
|
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;
|
position_ = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace media {
|
||||||
/// testing, since this does not support larger files.
|
/// testing, since this does not support larger files.
|
||||||
class MemoryFile : public File {
|
class MemoryFile : public File {
|
||||||
public:
|
public:
|
||||||
MemoryFile(const std::string& file_name);
|
MemoryFile(const std::string& file_name, const std::string& mode);
|
||||||
|
|
||||||
/// @name File implementation overrides.
|
/// @name File implementation overrides.
|
||||||
/// @{
|
/// @{
|
||||||
|
@ -47,6 +47,7 @@ class MemoryFile : public File {
|
||||||
bool Open() override;
|
bool Open() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::string mode_;
|
||||||
std::vector<uint8_t>* file_;
|
std::vector<uint8_t>* file_;
|
||||||
uint64_t position_;
|
uint64_t position_;
|
||||||
|
|
||||||
|
|
|
@ -27,12 +27,14 @@ class MemoryFileTest : public testing::Test {
|
||||||
|
|
||||||
TEST_F(MemoryFileTest, ModifiesSameFile) {
|
TEST_F(MemoryFileTest, ModifiesSameFile) {
|
||||||
scoped_ptr<File, FileCloser> writer(File::Open("memory://file1", "w"));
|
scoped_ptr<File, FileCloser> writer(File::Open("memory://file1", "w"));
|
||||||
|
ASSERT_TRUE(writer);
|
||||||
ASSERT_EQ(kWriteBufferSize, writer->Write(kWriteBuffer, kWriteBufferSize));
|
ASSERT_EQ(kWriteBufferSize, writer->Write(kWriteBuffer, kWriteBufferSize));
|
||||||
|
|
||||||
// Since File::Open should not create a ThreadedIoFile so there should be
|
// Since File::Open should not create a ThreadedIoFile so there should be
|
||||||
// no cache.
|
// no cache.
|
||||||
|
|
||||||
scoped_ptr<File, FileCloser> reader(File::Open("memory://file1", "r"));
|
scoped_ptr<File, FileCloser> reader(File::Open("memory://file1", "r"));
|
||||||
|
ASSERT_TRUE(reader);
|
||||||
|
|
||||||
uint8_t read_buffer[kWriteBufferSize];
|
uint8_t read_buffer[kWriteBufferSize];
|
||||||
ASSERT_EQ(kWriteBufferSize, reader->Read(read_buffer, kWriteBufferSize));
|
ASSERT_EQ(kWriteBufferSize, reader->Read(read_buffer, kWriteBufferSize));
|
||||||
|
@ -40,15 +42,19 @@ TEST_F(MemoryFileTest, ModifiesSameFile) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MemoryFileTest, SupportsDifferentFiles) {
|
TEST_F(MemoryFileTest, SupportsDifferentFiles) {
|
||||||
scoped_ptr<MemoryFile, FileCloser> writer(new MemoryFile("memory://file1"));
|
scoped_ptr<File, FileCloser> writer(File::Open("memory://file1", "w"));
|
||||||
scoped_ptr<MemoryFile, FileCloser> reader(new MemoryFile("memory://file2"));
|
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(kWriteBufferSize, writer->Write(kWriteBuffer, kWriteBufferSize));
|
||||||
ASSERT_EQ(0, reader->Size());
|
ASSERT_EQ(0, reader->Size());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MemoryFileTest, SeekAndTell) {
|
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_EQ(kWriteBufferSize, file->Write(kWriteBuffer, kWriteBufferSize));
|
||||||
ASSERT_TRUE(file->Seek(0));
|
ASSERT_TRUE(file->Seek(0));
|
||||||
|
|
||||||
|
@ -61,7 +67,9 @@ TEST_F(MemoryFileTest, SeekAndTell) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MemoryFileTest, EndOfFile) {
|
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_EQ(kWriteBufferSize, file->Write(kWriteBuffer, kWriteBufferSize));
|
||||||
ASSERT_TRUE(file->Seek(0));
|
ASSERT_TRUE(file->Seek(0));
|
||||||
|
|
||||||
|
@ -75,7 +83,8 @@ TEST_F(MemoryFileTest, EndOfFile) {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(MemoryFileTest, ExtendsSize) {
|
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->Write(kWriteBuffer, kWriteBufferSize));
|
||||||
|
|
||||||
ASSERT_EQ(kWriteBufferSize, file->Size());
|
ASSERT_EQ(kWriteBufferSize, file->Size());
|
||||||
|
@ -87,5 +96,20 @@ TEST_F(MemoryFileTest, ExtendsSize) {
|
||||||
EXPECT_EQ(2 * kWriteBufferSize, static_cast<int64_t>(size));
|
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 media
|
||||||
} // namespace edash_packager
|
} // namespace edash_packager
|
||||||
|
|
|
@ -49,7 +49,7 @@ Status MultiSegmentSegmenter::FinalizeSegment() {
|
||||||
const uint64_t start_webm_timecode = cluster()->timecode();
|
const uint64_t start_webm_timecode = cluster()->timecode();
|
||||||
const uint64_t start_timescale = FromWebMTimecode(start_webm_timecode);
|
const uint64_t start_timescale = FromWebMTimecode(start_webm_timecode);
|
||||||
const uint64_t length = static_cast<uint64_t>(
|
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);
|
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)
|
Segmenter::Segmenter(const MuxerOptions& options)
|
||||||
: options_(options),
|
: options_(options),
|
||||||
stream_(NULL),
|
info_(NULL),
|
||||||
muxer_listener_(NULL),
|
muxer_listener_(NULL),
|
||||||
progress_listener_(NULL),
|
progress_listener_(NULL),
|
||||||
progress_target_(0),
|
progress_target_(0),
|
||||||
|
@ -43,15 +43,15 @@ Segmenter::Segmenter(const MuxerOptions& options)
|
||||||
Segmenter::~Segmenter() {}
|
Segmenter::~Segmenter() {}
|
||||||
|
|
||||||
Status Segmenter::Initialize(scoped_ptr<MkvWriter> writer,
|
Status Segmenter::Initialize(scoped_ptr<MkvWriter> writer,
|
||||||
MediaStream* streams,
|
StreamInfo* info,
|
||||||
ProgressListener* progress_listener,
|
ProgressListener* progress_listener,
|
||||||
MuxerListener* muxer_listener,
|
MuxerListener* muxer_listener,
|
||||||
KeySource* encryption_key_source) {
|
KeySource* encryption_key_source) {
|
||||||
muxer_listener_ = muxer_listener;
|
muxer_listener_ = muxer_listener;
|
||||||
stream_ = streams;
|
info_ = info;
|
||||||
|
|
||||||
// Use media duration as progress target.
|
// Use media duration as progress target.
|
||||||
progress_target_ = stream_->info()->duration();
|
progress_target_ = info_->duration();
|
||||||
progress_listener_ = progress_listener;
|
progress_listener_ = progress_listener;
|
||||||
|
|
||||||
segment_info_.Init();
|
segment_info_.Init();
|
||||||
|
@ -64,18 +64,16 @@ Status Segmenter::Initialize(scoped_ptr<MkvWriter> writer,
|
||||||
|
|
||||||
// Create the track info.
|
// Create the track info.
|
||||||
Status status;
|
Status status;
|
||||||
switch (stream_->info()->stream_type()) {
|
switch (info_->stream_type()) {
|
||||||
case kStreamVideo:
|
case kStreamVideo:
|
||||||
status = CreateVideoTrack(
|
status = CreateVideoTrack(static_cast<VideoStreamInfo*>(info_));
|
||||||
static_cast<VideoStreamInfo*>(stream_->info().get()));
|
|
||||||
break;
|
break;
|
||||||
case kStreamAudio:
|
case kStreamAudio:
|
||||||
status = CreateAudioTrack(
|
status = CreateAudioTrack(static_cast<AudioStreamInfo*>(info_));
|
||||||
static_cast<AudioStreamInfo*>(stream_->info().get()));
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
NOTIMPLEMENTED() << "Not implemented for stream type: "
|
NOTIMPLEMENTED() << "Not implemented for stream type: "
|
||||||
<< stream_->info()->stream_type();
|
<< info_->stream_type();
|
||||||
status = Status(error::UNIMPLEMENTED, "Not implemented for stream type");
|
status = Status(error::UNIMPLEMENTED, "Not implemented for stream type");
|
||||||
}
|
}
|
||||||
if (!status.ok())
|
if (!status.ok())
|
||||||
|
@ -118,14 +116,14 @@ Status Segmenter::AddSample(scoped_refptr<MediaSample> sample) {
|
||||||
return status;
|
return status;
|
||||||
|
|
||||||
const int64_t time_ns =
|
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_,
|
if (!cluster_->AddFrame(sample->data(), sample->data_size(), track_id_,
|
||||||
time_ns, sample->is_key_frame())) {
|
time_ns, sample->is_key_frame())) {
|
||||||
LOG(ERROR) << "Error adding sample to segment.";
|
LOG(ERROR) << "Error adding sample to segment.";
|
||||||
return Status(error::FILE_FAILURE, "Error adding sample to segment.");
|
return Status(error::FILE_FAILURE, "Error adding sample to segment.");
|
||||||
}
|
}
|
||||||
const double duration_sec =
|
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;
|
cluster_length_sec_ += duration_sec;
|
||||||
segment_length_sec_ += duration_sec;
|
segment_length_sec_ += duration_sec;
|
||||||
total_duration_ += sample->duration();
|
total_duration_ += sample->duration();
|
||||||
|
@ -141,14 +139,14 @@ float Segmenter::GetDuration() const {
|
||||||
uint64_t Segmenter::FromBMFFTimescale(uint64_t time_timescale) {
|
uint64_t Segmenter::FromBMFFTimescale(uint64_t time_timescale) {
|
||||||
// Convert the time from BMFF time_code to WebM timecode scale.
|
// Convert the time from BMFF time_code to WebM timecode scale.
|
||||||
const int64_t time_ns =
|
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();
|
return time_ns / segment_info_.timecode_scale();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t Segmenter::FromWebMTimecode(uint64_t time_webm_timecode) {
|
uint64_t Segmenter::FromWebMTimecode(uint64_t time_webm_timecode) {
|
||||||
// Convert the time to BMFF time_code from WebM timecode scale.
|
// Convert the time to BMFF time_code from WebM timecode scale.
|
||||||
const int64_t time_ns = time_webm_timecode * segment_info_.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) {
|
Status Segmenter::WriteSegmentHeader(uint64_t file_size, MkvWriter* writer) {
|
||||||
|
|
|
@ -22,7 +22,7 @@ struct MuxerOptions;
|
||||||
class AudioStreamInfo;
|
class AudioStreamInfo;
|
||||||
class KeySource;
|
class KeySource;
|
||||||
class MediaSample;
|
class MediaSample;
|
||||||
class MediaStream;
|
class StreamInfo;
|
||||||
class MuxerListener;
|
class MuxerListener;
|
||||||
class ProgressListener;
|
class ProgressListener;
|
||||||
class StreamInfo;
|
class StreamInfo;
|
||||||
|
@ -39,14 +39,14 @@ class Segmenter {
|
||||||
/// Calling other public methods of this class without this method returning
|
/// Calling other public methods of this class without this method returning
|
||||||
/// Status::OK results in an undefined behavior.
|
/// Status::OK results in an undefined behavior.
|
||||||
/// @param writer contains the output file (or init file in multi-segment).
|
/// @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 muxer_listener receives muxer events. Can be NULL.
|
||||||
/// @param encryption_key_source points to the key source which contains
|
/// @param encryption_key_source points to the key source which contains
|
||||||
/// the encryption keys. It can be NULL to indicate that no encryption
|
/// the encryption keys. It can be NULL to indicate that no encryption
|
||||||
/// is required.
|
/// is required.
|
||||||
/// @return OK on success, an error status otherwise.
|
/// @return OK on success, an error status otherwise.
|
||||||
Status Initialize(scoped_ptr<MkvWriter> writer,
|
Status Initialize(scoped_ptr<MkvWriter> writer,
|
||||||
MediaStream* streams,
|
StreamInfo* info,
|
||||||
ProgressListener* progress_listener,
|
ProgressListener* progress_listener,
|
||||||
MuxerListener* muxer_listener,
|
MuxerListener* muxer_listener,
|
||||||
KeySource* encryption_key_source);
|
KeySource* encryption_key_source);
|
||||||
|
@ -94,7 +94,7 @@ class Segmenter {
|
||||||
mkvmuxer::Cluster* cluster() { return cluster_.get(); }
|
mkvmuxer::Cluster* cluster() { return cluster_.get(); }
|
||||||
mkvmuxer::Cues* cues() { return &cues_; }
|
mkvmuxer::Cues* cues() { return &cues_; }
|
||||||
MuxerListener* muxer_listener() { return muxer_listener_; }
|
MuxerListener* muxer_listener() { return muxer_listener_; }
|
||||||
MediaStream* stream() { return stream_; }
|
StreamInfo* info() { return info_; }
|
||||||
SeekHead* seek_head() { return &seek_head_; }
|
SeekHead* seek_head() { return &seek_head_; }
|
||||||
|
|
||||||
int track_id() const { return track_id_; }
|
int track_id() const { return track_id_; }
|
||||||
|
@ -128,7 +128,7 @@ class Segmenter {
|
||||||
mkvmuxer::SegmentInfo segment_info_;
|
mkvmuxer::SegmentInfo segment_info_;
|
||||||
mkvmuxer::Tracks tracks_;
|
mkvmuxer::Tracks tracks_;
|
||||||
|
|
||||||
MediaStream* stream_;
|
StreamInfo* info_;
|
||||||
MuxerListener* muxer_listener_;
|
MuxerListener* muxer_listener_;
|
||||||
ProgressListener* progress_listener_;
|
ProgressListener* progress_listener_;
|
||||||
uint64_t progress_target_;
|
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.
|
// Write the Cues to the end of the file.
|
||||||
index_start_ = writer_->Position();
|
index_start_ = writer_->Position();
|
||||||
|
seek_head()->set_cues_pos(index_start_);
|
||||||
if (!cues()->Write(writer_.get()))
|
if (!cues()->Write(writer_.get()))
|
||||||
return Status(error::FILE_FAILURE, "Error writing Cues data.");
|
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) {
|
TwoPassSingleSegmentSegmenter::DoInitialize(scoped_ptr<MkvWriter> writer) {
|
||||||
// Assume the amount of time to copy the temp file as the same amount
|
// Assume the amount of time to copy the temp file as the same amount
|
||||||
// of time as to make it.
|
// of time as to make it.
|
||||||
set_progress_target(stream()->info()->duration() * 2);
|
set_progress_target(info()->duration() * 2);
|
||||||
|
|
||||||
real_writer_ = writer.Pass();
|
real_writer_ = writer.Pass();
|
||||||
|
|
||||||
std::string temp_name = TempFileName(options());
|
temp_file_name_ = TempFileName(options());
|
||||||
scoped_ptr<MkvWriter> temp(new MkvWriter);
|
scoped_ptr<MkvWriter> temp(new MkvWriter);
|
||||||
Status status = temp->Open(temp_name);
|
Status status = temp->Open(temp_file_name_);
|
||||||
if (!status.ok())
|
if (!status.ok())
|
||||||
return status;
|
return status;
|
||||||
|
|
||||||
|
@ -105,9 +105,9 @@ Status TwoPassSingleSegmentSegmenter::DoFinalize() {
|
||||||
return temp;
|
return temp;
|
||||||
|
|
||||||
// Close the temp file and open it for reading.
|
// Close the temp file and open it for reading.
|
||||||
std::string temp_name = writer()->file()->file_name();
|
|
||||||
set_writer(scoped_ptr<MkvWriter>());
|
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)
|
if (!temp_reader)
|
||||||
return Status(error::FILE_FAILURE, "Error opening temp file.");
|
return Status(error::FILE_FAILURE, "Error opening temp file.");
|
||||||
|
|
||||||
|
@ -123,8 +123,8 @@ Status TwoPassSingleSegmentSegmenter::DoFinalize() {
|
||||||
|
|
||||||
// Close and delete the temp file.
|
// Close and delete the temp file.
|
||||||
temp_reader.reset();
|
temp_reader.reset();
|
||||||
if (!File::Delete(temp_name.c_str())) {
|
if (!File::Delete(temp_file_name_.c_str())) {
|
||||||
LOG(WARNING) << "Unable to delete temporary file " << temp_name;
|
LOG(WARNING) << "Unable to delete temporary file " << temp_file_name_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the writer back to the real file so GetIndexRangeStartAndEnd works.
|
// 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_
|
#ifndef MEDIA_FORMATS_WEBM_TWO_PASS_SINGLE_SEGMENT_SEGMENTER_H_
|
||||||
#define 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/media/formats/webm/single_segment_segmenter.h"
|
||||||
|
|
||||||
#include "packager/base/memory/scoped_ptr.h"
|
#include "packager/base/memory/scoped_ptr.h"
|
||||||
|
@ -41,6 +43,7 @@ class TwoPassSingleSegmentSegmenter : public SingleSegmentSegmenter {
|
||||||
uint64_t last_size);
|
uint64_t last_size);
|
||||||
|
|
||||||
scoped_ptr<MkvWriter> real_writer_;
|
scoped_ptr<MkvWriter> real_writer_;
|
||||||
|
std::string temp_file_name_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(TwoPassSingleSegmentSegmenter);
|
DISALLOW_COPY_AND_ASSIGN(TwoPassSingleSegmentSegmenter);
|
||||||
};
|
};
|
||||||
|
|
|
@ -65,8 +65,12 @@
|
||||||
'sources': [
|
'sources': [
|
||||||
'cluster_builder.cc',
|
'cluster_builder.cc',
|
||||||
'cluster_builder.h',
|
'cluster_builder.h',
|
||||||
|
'multi_segment_segmenter_unittest.cc',
|
||||||
'opus_packet_builder.cc',
|
'opus_packet_builder.cc',
|
||||||
'opus_packet_builder.h',
|
'opus_packet_builder.h',
|
||||||
|
'segmenter_test_base.cc',
|
||||||
|
'segmenter_test_base.h',
|
||||||
|
'single_segment_segmenter_unittest.cc',
|
||||||
'tracks_builder.cc',
|
'tracks_builder.cc',
|
||||||
'tracks_builder.h',
|
'tracks_builder.h',
|
||||||
'webm_cluster_parser_unittest.cc',
|
'webm_cluster_parser_unittest.cc',
|
||||||
|
@ -78,6 +82,7 @@
|
||||||
'dependencies': [
|
'dependencies': [
|
||||||
'../../../testing/gtest.gyp:gtest',
|
'../../../testing/gtest.gyp:gtest',
|
||||||
'../../../testing/gmock.gyp:gmock',
|
'../../../testing/gmock.gyp:gmock',
|
||||||
|
'../../file/file.gyp:file',
|
||||||
'../../test/media_test.gyp:media_test_support',
|
'../../test/media_test.gyp:media_test_support',
|
||||||
'webm',
|
'webm',
|
||||||
]
|
]
|
||||||
|
|
|
@ -37,9 +37,9 @@ Status WebMMuxer::Initialize() {
|
||||||
segmenter_.reset(new TwoPassSingleSegmentSegmenter(options()));
|
segmenter_.reset(new TwoPassSingleSegmentSegmenter(options()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Status initialized =
|
Status initialized = segmenter_->Initialize(
|
||||||
segmenter_->Initialize(writer.Pass(), streams()[0], progress_listener(),
|
writer.Pass(), streams()[0]->info().get(), progress_listener(),
|
||||||
muxer_listener(), encryption_key_source());
|
muxer_listener(), encryption_key_source());
|
||||||
|
|
||||||
if (!initialized.ok())
|
if (!initialized.ok())
|
||||||
return initialized;
|
return initialized;
|
||||||
|
|
Loading…
Reference in New Issue