From 7b52f0a3ed668fa81deec31dee828f6a27e28d9b Mon Sep 17 00:00:00 2001 From: Jacob Trimble Date: Mon, 21 Dec 2015 16:33:55 -0800 Subject: [PATCH] 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 --- packager/media/file/file.cc | 2 +- packager/media/file/file_test_util.h | 29 +++ packager/media/file/memory_file.cc | 24 +- packager/media/file/memory_file.h | 3 +- packager/media/file/memory_file_unittest.cc | 34 ++- .../formats/webm/multi_segment_segmenter.cc | 2 +- .../webm/multi_segment_segmenter_unittest.cc | 234 ++++++++++++++++++ packager/media/formats/webm/segmenter.cc | 26 +- packager/media/formats/webm/segmenter.h | 10 +- .../media/formats/webm/segmenter_test_base.cc | 185 ++++++++++++++ .../media/formats/webm/segmenter_test_base.h | 100 ++++++++ .../formats/webm/single_segment_segmenter.cc | 1 + .../webm/single_segment_segmenter_unittest.cc | 232 +++++++++++++++++ .../webm/two_pass_single_segment_segmenter.cc | 14 +- .../webm/two_pass_single_segment_segmenter.h | 3 + packager/media/formats/webm/webm.gyp | 5 + packager/media/formats/webm/webm_muxer.cc | 6 +- 17 files changed, 869 insertions(+), 41 deletions(-) create mode 100644 packager/media/file/file_test_util.h create mode 100644 packager/media/formats/webm/multi_segment_segmenter_unittest.cc create mode 100644 packager/media/formats/webm/segmenter_test_base.cc create mode 100644 packager/media/formats/webm/segmenter_test_base.h create mode 100644 packager/media/formats/webm/single_segment_segmenter_unittest.cc diff --git a/packager/media/file/file.cc b/packager/media/file/file.cc index bbe9cbbf55..e93b3ee1b6 100644 --- a/packager/media/file/file.cc +++ b/packager/media/file/file.cc @@ -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) { diff --git a/packager/media/file/file_test_util.h b/packager/media/file/file_test_util.h new file mode 100644 index 0000000000..48ee0e5a1c --- /dev/null +++ b/packager/media/file/file_test_util.h @@ -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 + +#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(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_ + diff --git a/packager/media/file/memory_file.cc b/packager/media/file/memory_file.cc index 8c07ad87b4..c2d2be9b93 100644 --- a/packager/media/file/memory_file.cc +++ b/packager/media/file/memory_file.cc @@ -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* GetFile(const std::string& file_name) { return &files_[file_name]; } @@ -50,10 +54,8 @@ scoped_ptr 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; } diff --git a/packager/media/file/memory_file.h b/packager/media/file/memory_file.h index be2b009c2a..544189be19 100644 --- a/packager/media/file/memory_file.h +++ b/packager/media/file/memory_file.h @@ -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* file_; uint64_t position_; diff --git a/packager/media/file/memory_file_unittest.cc b/packager/media/file/memory_file_unittest.cc index ff968d51eb..7ad5d6d257 100644 --- a/packager/media/file/memory_file_unittest.cc +++ b/packager/media/file/memory_file_unittest.cc @@ -27,12 +27,14 @@ class MemoryFileTest : public testing::Test { TEST_F(MemoryFileTest, ModifiesSameFile) { scoped_ptr 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 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 writer(new MemoryFile("memory://file1")); - scoped_ptr reader(new MemoryFile("memory://file2")); + scoped_ptr writer(File::Open("memory://file1", "w")); + scoped_ptr 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 file(new MemoryFile("memory://file1")); + scoped_ptr 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 file(new MemoryFile("memory://file1")); + scoped_ptr 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 file(new MemoryFile("memory://file1")); + scoped_ptr 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(size)); } +TEST_F(MemoryFileTest, ReadMissingFileFails) { + scoped_ptr file(File::Open("memory://file1", "r")); + EXPECT_FALSE(file); +} + +TEST_F(MemoryFileTest, WriteExistingFileDeletes) { + scoped_ptr file1(File::Open("memory://file1", "w")); + ASSERT_TRUE(file1); + ASSERT_EQ(kWriteBufferSize, file1->Write(kWriteBuffer, kWriteBufferSize)); + + scoped_ptr file2(File::Open("memory://file1", "w")); + ASSERT_TRUE(file2); + EXPECT_EQ(0, file2->Size()); +} + } // namespace media } // namespace edash_packager diff --git a/packager/media/formats/webm/multi_segment_segmenter.cc b/packager/media/formats/webm/multi_segment_segmenter.cc index 4f6da88146..3d6dbba8c9 100644 --- a/packager/media/formats/webm/multi_segment_segmenter.cc +++ b/packager/media/formats/webm/multi_segment_segmenter.cc @@ -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( - cluster_length_sec() * stream()->info()->time_scale()); + cluster_length_sec() * info()->time_scale()); muxer_listener()->OnNewSegment(start_timescale, length, size); } diff --git a/packager/media/formats/webm/multi_segment_segmenter_unittest.cc b/packager/media/formats/webm/multi_segment_segmenter_unittest.cc new file mode 100644 index 0000000000..b8155b848a --- /dev/null +++ b/packager/media/formats/webm/multi_segment_segmenter_unittest.cc @@ -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 + +#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( + options, info_.get(), &segmenter_)); + } + + scoped_refptr info_; + scoped_ptr 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 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 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 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 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 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 + diff --git a/packager/media/formats/webm/segmenter.cc b/packager/media/formats/webm/segmenter.cc index b781168699..6517c5fd25 100644 --- a/packager/media/formats/webm/segmenter.cc +++ b/packager/media/formats/webm/segmenter.cc @@ -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 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 writer, // Create the track info. Status status; - switch (stream_->info()->stream_type()) { + switch (info_->stream_type()) { case kStreamVideo: - status = CreateVideoTrack( - static_cast(stream_->info().get())); + status = CreateVideoTrack(static_cast(info_)); break; case kStreamAudio: - status = CreateAudioTrack( - static_cast(stream_->info().get())); + status = CreateAudioTrack(static_cast(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 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(sample->duration()) / stream_->info()->time_scale(); + static_cast(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) { diff --git a/packager/media/formats/webm/segmenter.h b/packager/media/formats/webm/segmenter.h index 992b52629c..3c9bc956b4 100644 --- a/packager/media/formats/webm/segmenter.h +++ b/packager/media/formats/webm/segmenter.h @@ -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 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_; diff --git a/packager/media/formats/webm/segmenter_test_base.cc b/packager/media/formats/webm/segmenter_test_base.cc new file mode 100644 index 0000000000..56d6daa4fc --- /dev/null +++ b/packager/media/formats/webm/segmenter_test_base.cc @@ -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 SegmentTestBase::CreateSample(bool is_key_frame, + uint64_t duration) { + scoped_refptr 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(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(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 + diff --git a/packager/media/formats/webm/segmenter_test_base.h b/packager/media/formats/webm/segmenter_test_base.h new file mode 100644 index 0000000000..fa6bd2168d --- /dev/null +++ b/packager/media/formats/webm/segmenter_test_base.h @@ -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 + +#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 + void CreateAndInitializeSegmenter(const MuxerOptions& options, + StreamInfo* info, + scoped_ptr* result) const { + scoped_ptr segmenter(new S(options)); + + scoped_ptr 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 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 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_ diff --git a/packager/media/formats/webm/single_segment_segmenter.cc b/packager/media/formats/webm/single_segment_segmenter.cc index e4cf7c6a07..b8920b603b 100644 --- a/packager/media/formats/webm/single_segment_segmenter.cc +++ b/packager/media/formats/webm/single_segment_segmenter.cc @@ -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."); diff --git a/packager/media/formats/webm/single_segment_segmenter_unittest.cc b/packager/media/formats/webm/single_segment_segmenter_unittest.cc new file mode 100644 index 0000000000..741b1f9ce2 --- /dev/null +++ b/packager/media/formats/webm/single_segment_segmenter_unittest.cc @@ -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 + +#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 { + public: + SingleSegmentSegmenterTest() : info_(CreateVideoStreamInfo()) {} + + protected: + void InitializeSegmenter(const MuxerOptions& options) { + if (!GetParam()) { + ASSERT_NO_FATAL_FAILURE( + CreateAndInitializeSegmenter( + options, info_.get(), &segmenter_)); + } else { + ASSERT_NO_FATAL_FAILURE( + CreateAndInitializeSegmenter( + options, info_.get(), &segmenter_)); + } + } + + scoped_refptr info_; + scoped_ptr 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 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 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 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 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 + diff --git a/packager/media/formats/webm/two_pass_single_segment_segmenter.cc b/packager/media/formats/webm/two_pass_single_segment_segmenter.cc index b62a512f4b..5eb4311d15 100644 --- a/packager/media/formats/webm/two_pass_single_segment_segmenter.cc +++ b/packager/media/formats/webm/two_pass_single_segment_segmenter.cc @@ -75,13 +75,13 @@ Status TwoPassSingleSegmentSegmenter::DoInitialize(scoped_ptr 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 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()); - scoped_ptr temp_reader(File::Open(temp_name.c_str(), "r")); + scoped_ptr 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. diff --git a/packager/media/formats/webm/two_pass_single_segment_segmenter.h b/packager/media/formats/webm/two_pass_single_segment_segmenter.h index 8293ab9bc2..bcfb94ac5b 100644 --- a/packager/media/formats/webm/two_pass_single_segment_segmenter.h +++ b/packager/media/formats/webm/two_pass_single_segment_segmenter.h @@ -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 + #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 real_writer_; + std::string temp_file_name_; DISALLOW_COPY_AND_ASSIGN(TwoPassSingleSegmentSegmenter); }; diff --git a/packager/media/formats/webm/webm.gyp b/packager/media/formats/webm/webm.gyp index 13e26331c6..2fee605082 100644 --- a/packager/media/formats/webm/webm.gyp +++ b/packager/media/formats/webm/webm.gyp @@ -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', ] diff --git a/packager/media/formats/webm/webm_muxer.cc b/packager/media/formats/webm/webm_muxer.cc index 4f7f6534af..999c758980 100644 --- a/packager/media/formats/webm/webm_muxer.cc +++ b/packager/media/formats/webm/webm_muxer.cc @@ -37,9 +37,9 @@ Status WebMMuxer::Initialize() { segmenter_.reset(new TwoPassSingleSegmentSegmenter(options())); } - Status initialized = - segmenter_->Initialize(writer.Pass(), streams()[0], progress_listener(), - muxer_listener(), encryption_key_source()); + Status initialized = segmenter_->Initialize( + writer.Pass(), streams()[0]->info().get(), progress_listener(), + muxer_listener(), encryption_key_source()); if (!initialized.ok()) return initialized;