Added WebM muxer.
* Added WebM muxer that supports both single and multi-segment modes. * WebM muxer supports MPD generation. * Enabled WebM muxer and demuxer in packager. b/22463220 b/25089245 b/25089714 Change-Id: I9f6e8fc51e08fbb1d685229c5cb54ea60f7bed8f
This commit is contained in:
parent
1cdce29343
commit
07378e806c
|
@ -19,6 +19,7 @@
|
||||||
#include "packager/base/logging.h"
|
#include "packager/base/logging.h"
|
||||||
#include "packager/base/stl_util.h"
|
#include "packager/base/stl_util.h"
|
||||||
#include "packager/base/strings/string_split.h"
|
#include "packager/base/strings/string_split.h"
|
||||||
|
#include "packager/base/strings/string_util.h"
|
||||||
#include "packager/base/strings/stringprintf.h"
|
#include "packager/base/strings/stringprintf.h"
|
||||||
#include "packager/base/threading/simple_thread.h"
|
#include "packager/base/threading/simple_thread.h"
|
||||||
#include "packager/base/time/clock.h"
|
#include "packager/base/time/clock.h"
|
||||||
|
@ -31,6 +32,7 @@
|
||||||
#include "packager/media/event/vod_media_info_dump_muxer_listener.h"
|
#include "packager/media/event/vod_media_info_dump_muxer_listener.h"
|
||||||
#include "packager/media/file/file.h"
|
#include "packager/media/file/file.h"
|
||||||
#include "packager/media/formats/mp4/mp4_muxer.h"
|
#include "packager/media/formats/mp4/mp4_muxer.h"
|
||||||
|
#include "packager/media/formats/webm/webm_muxer.h"
|
||||||
#include "packager/mpd/base/dash_iop_mpd_notifier.h"
|
#include "packager/mpd/base/dash_iop_mpd_notifier.h"
|
||||||
#include "packager/mpd/base/media_info.pb.h"
|
#include "packager/mpd/base/media_info.pb.h"
|
||||||
#include "packager/mpd/base/mpd_builder.h"
|
#include "packager/mpd/base/mpd_builder.h"
|
||||||
|
@ -181,6 +183,25 @@ bool StreamInfoToTextMediaInfo(const StreamDescriptor& stream_descriptor,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scoped_ptr<Muxer> CreateOutputMuxer(const MuxerOptions& options) {
|
||||||
|
// TODO(modmaker): Add a config option for output format
|
||||||
|
const std::string& file_name = options.output_file_name;
|
||||||
|
if (base::EndsWith(file_name, ".webm",
|
||||||
|
base::CompareCase::INSENSITIVE_ASCII)) {
|
||||||
|
return scoped_ptr<Muxer>(new webm::WebMMuxer(options));
|
||||||
|
} else if (base::EndsWith(file_name, ".mp4",
|
||||||
|
base::CompareCase::INSENSITIVE_ASCII) ||
|
||||||
|
base::EndsWith(file_name, ".m4a",
|
||||||
|
base::CompareCase::INSENSITIVE_ASCII) ||
|
||||||
|
base::EndsWith(file_name, ".m4v",
|
||||||
|
base::CompareCase::INSENSITIVE_ASCII)) {
|
||||||
|
return scoped_ptr<Muxer>(new mp4::MP4Muxer(options));
|
||||||
|
} else {
|
||||||
|
LOG(ERROR) << "Unrecognized output format " << file_name;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
||||||
const MuxerOptions& muxer_options,
|
const MuxerOptions& muxer_options,
|
||||||
FakeClock* fake_clock,
|
FakeClock* fake_clock,
|
||||||
|
@ -261,7 +282,9 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
||||||
}
|
}
|
||||||
DCHECK(!remux_jobs->empty());
|
DCHECK(!remux_jobs->empty());
|
||||||
|
|
||||||
scoped_ptr<Muxer> muxer(new mp4::MP4Muxer(stream_muxer_options));
|
scoped_ptr<Muxer> muxer(CreateOutputMuxer(stream_muxer_options));
|
||||||
|
if (!muxer)
|
||||||
|
return false;
|
||||||
if (FLAGS_use_fake_clock_for_muxer) muxer->set_clock(fake_clock);
|
if (FLAGS_use_fake_clock_for_muxer) muxer->set_clock(fake_clock);
|
||||||
|
|
||||||
if (key_source) {
|
if (key_source) {
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
#include "packager/media/file/file.h"
|
#include "packager/media/file/file.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include <gflags/gflags.h>
|
#include <gflags/gflags.h>
|
||||||
#include "packager/base/logging.h"
|
#include "packager/base/logging.h"
|
||||||
#include "packager/base/memory/scoped_ptr.h"
|
#include "packager/base/memory/scoped_ptr.h"
|
||||||
|
@ -205,5 +207,43 @@ bool File::Copy(const char* from_file_name, const char* to_file_name) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int64_t File::CopyFile(File* source, File* destination) {
|
||||||
|
return CopyFile(source, destination, kWholeFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t File::CopyFile(File* source, File* destination, int64_t max_copy) {
|
||||||
|
DCHECK(source);
|
||||||
|
DCHECK(destination);
|
||||||
|
if (max_copy < 0)
|
||||||
|
max_copy = std::numeric_limits<int64_t>::max();
|
||||||
|
|
||||||
|
const int64_t kBufferSize = 0x40000; // 256KB.
|
||||||
|
scoped_ptr<uint8_t[]> buffer(new uint8_t[kBufferSize]);
|
||||||
|
int64_t bytes_copied = 0;
|
||||||
|
while (bytes_copied < max_copy) {
|
||||||
|
const int64_t size = std::min(kBufferSize, max_copy - bytes_copied);
|
||||||
|
const int64_t bytes_read = source->Read(buffer.get(), size);
|
||||||
|
if (bytes_read < 0)
|
||||||
|
return bytes_read;
|
||||||
|
if (bytes_read == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
int64_t total_bytes_written = 0;
|
||||||
|
while (total_bytes_written < bytes_read) {
|
||||||
|
const int64_t bytes_written = destination->Write(
|
||||||
|
buffer.get() + total_bytes_written, bytes_read - total_bytes_written);
|
||||||
|
if (bytes_written < 0)
|
||||||
|
return bytes_written;
|
||||||
|
|
||||||
|
total_bytes_written += bytes_written;
|
||||||
|
}
|
||||||
|
|
||||||
|
DCHECK_EQ(total_bytes_written, bytes_read);
|
||||||
|
bytes_copied += bytes_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes_copied;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
} // namespace edash_packager
|
} // namespace edash_packager
|
||||||
|
|
|
@ -17,6 +17,7 @@ namespace edash_packager {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
extern const char* kLocalFilePrefix;
|
extern const char* kLocalFilePrefix;
|
||||||
|
const int64_t kWholeFile = -1;
|
||||||
|
|
||||||
/// Define an abstract file interface.
|
/// Define an abstract file interface.
|
||||||
class File {
|
class File {
|
||||||
|
@ -110,6 +111,19 @@ class File {
|
||||||
/// @return true on success, false otherwise.
|
/// @return true on success, false otherwise.
|
||||||
static bool Copy(const char* from_file_name, const char* to_file_name);
|
static bool Copy(const char* from_file_name, const char* to_file_name);
|
||||||
|
|
||||||
|
/// Copies the contents from source to destination.
|
||||||
|
/// @param source The file to copy from.
|
||||||
|
/// @param destination The file to copy to.
|
||||||
|
/// @return Number of bytes written, or a value < 0 on error.
|
||||||
|
static int64_t CopyFile(File* source, File* destination);
|
||||||
|
|
||||||
|
/// Copies the contents from source to destination.
|
||||||
|
/// @param source The file to copy from.
|
||||||
|
/// @param destination The file to copy to.
|
||||||
|
/// @param max_copy The maximum number of bytes to copy; < 0 to copy to EOF.
|
||||||
|
/// @return Number of bytes written, or a value < 0 on error.
|
||||||
|
static int64_t CopyFile(File* source, File* destination, int64_t max_copy);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit File(const std::string& file_name) : file_name_(file_name) {}
|
explicit File(const std::string& file_name) : file_name_(file_name) {}
|
||||||
/// Do *not* call the destructor directly (with the "delete" keyword)
|
/// Do *not* call the destructor directly (with the "delete" keyword)
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
// 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/mkv_writer.h"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
|
||||||
|
MkvWriter::MkvWriter() : position_(0) {}
|
||||||
|
|
||||||
|
MkvWriter::~MkvWriter() {}
|
||||||
|
|
||||||
|
Status MkvWriter::Open(const std::string& name) {
|
||||||
|
DCHECK(!file_);
|
||||||
|
file_.reset(File::Open(name.c_str(), "w"));
|
||||||
|
if (!file_)
|
||||||
|
return Status(error::FILE_FAILURE, "Unable to open file for writing.");
|
||||||
|
|
||||||
|
// This may produce an error message; however there isn't a seekable method
|
||||||
|
// on File.
|
||||||
|
seekable_ = file_->Seek(0);
|
||||||
|
position_ = 0;
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
mkvmuxer::int32 MkvWriter::Write(const void* buf, mkvmuxer::uint32 len) {
|
||||||
|
DCHECK(file_);
|
||||||
|
|
||||||
|
const char* data = reinterpret_cast<const char*>(buf);
|
||||||
|
int64_t total_bytes_written = 0;
|
||||||
|
while (total_bytes_written < len) {
|
||||||
|
const int64_t written =
|
||||||
|
file_->Write(data + total_bytes_written, len - total_bytes_written);
|
||||||
|
if (written < 0)
|
||||||
|
return written;
|
||||||
|
|
||||||
|
total_bytes_written += written;
|
||||||
|
}
|
||||||
|
|
||||||
|
DCHECK_EQ(total_bytes_written, len);
|
||||||
|
position_ += len;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t MkvWriter::WriteFromFile(File* source) {
|
||||||
|
return WriteFromFile(source, kWholeFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t MkvWriter::WriteFromFile(File* source, uint64_t max_copy) {
|
||||||
|
DCHECK(file_);
|
||||||
|
|
||||||
|
const int64_t size = File::CopyFile(source, file_.get(), max_copy);
|
||||||
|
if (size < 0)
|
||||||
|
return size;
|
||||||
|
|
||||||
|
position_ += size;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
mkvmuxer::int64 MkvWriter::Position() const {
|
||||||
|
return position_;
|
||||||
|
}
|
||||||
|
|
||||||
|
mkvmuxer::int32 MkvWriter::Position(mkvmuxer::int64 position) {
|
||||||
|
DCHECK(file_);
|
||||||
|
|
||||||
|
if (file_->Seek(position)) {
|
||||||
|
position_ = position;
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MkvWriter::Seekable() const {
|
||||||
|
return seekable_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MkvWriter::ElementStartNotify(mkvmuxer::uint64 element_id,
|
||||||
|
mkvmuxer::int64 position) {}
|
||||||
|
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
|
@ -0,0 +1,73 @@
|
||||||
|
// 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_MKV_WRITER_H_
|
||||||
|
#define MEDIA_FORMATS_WEBM_MKV_WRITER_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "packager/base/memory/scoped_ptr.h"
|
||||||
|
#include "packager/media/base/status.h"
|
||||||
|
#include "packager/media/file/file_closer.h"
|
||||||
|
#include "packager/third_party/libwebm/src/mkvmuxer.hpp"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
|
||||||
|
/// An implementation of IMkvWriter using our File type.
|
||||||
|
class MkvWriter : public mkvmuxer::IMkvWriter {
|
||||||
|
public:
|
||||||
|
MkvWriter();
|
||||||
|
~MkvWriter() override;
|
||||||
|
|
||||||
|
/// Opens the given file for writing. This MUST be called before any other
|
||||||
|
/// calls.
|
||||||
|
/// @param name The path to the file to open.
|
||||||
|
/// @return Whether the operation succeeded.
|
||||||
|
Status Open(const std::string& name);
|
||||||
|
|
||||||
|
/// Writes out @a len bytes of @a buf.
|
||||||
|
/// @return 0 on success.
|
||||||
|
mkvmuxer::int32 Write(const void* buf, mkvmuxer::uint32 len) override;
|
||||||
|
/// @return The offset of the output position from the beginning of the
|
||||||
|
/// output.
|
||||||
|
mkvmuxer::int64 Position() const override;
|
||||||
|
/// Set the current File position.
|
||||||
|
/// @return 0 on success.
|
||||||
|
mkvmuxer::int32 Position(mkvmuxer::int64 position) override;
|
||||||
|
/// @return true if the writer is seekable.
|
||||||
|
bool Seekable() const override;
|
||||||
|
/// Element start notification. Called whenever an element identifier is about
|
||||||
|
/// to be written to the stream. @a element_id is the element identifier, and
|
||||||
|
/// @a position is the location in the WebM stream where the first octet of
|
||||||
|
/// the element identifier will be written.
|
||||||
|
/// Note: the |MkvId| enumeration in webmids.hpp defines element values.
|
||||||
|
void ElementStartNotify(mkvmuxer::uint64 element_id,
|
||||||
|
mkvmuxer::int64 position) override;
|
||||||
|
|
||||||
|
/// Writes the contents of the given file to this file.
|
||||||
|
/// @return The number of bytes written; or < 0 on error.
|
||||||
|
int64_t WriteFromFile(File* source);
|
||||||
|
/// Writes the contents of the given file to this file, up to a maximum
|
||||||
|
/// number of bytes. If @a max_copy is negative, will copy to EOF.
|
||||||
|
/// @return The number of bytes written; or < 0 on error.
|
||||||
|
int64_t WriteFromFile(File* source, uint64_t max_copy);
|
||||||
|
|
||||||
|
File* file() { return file_.get(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
scoped_ptr<File, FileCloser> file_;
|
||||||
|
// Keep track of the position and whether we can seek.
|
||||||
|
mkvmuxer::int64 position_;
|
||||||
|
bool seekable_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(MkvWriter);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
||||||
|
|
||||||
|
#endif // MEDIA_FORMATS_WEBM_MKV_WRITER_H_
|
|
@ -0,0 +1,91 @@
|
||||||
|
// 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/multi_segment_segmenter.h"
|
||||||
|
|
||||||
|
#include "packager/media/base/media_stream.h"
|
||||||
|
#include "packager/media/base/muxer_options.h"
|
||||||
|
#include "packager/media/base/muxer_util.h"
|
||||||
|
#include "packager/media/base/stream_info.h"
|
||||||
|
#include "packager/media/event/muxer_listener.h"
|
||||||
|
#include "packager/third_party/libwebm/src/mkvmuxer.hpp"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
namespace webm {
|
||||||
|
MultiSegmentSegmenter::MultiSegmentSegmenter(const MuxerOptions& options)
|
||||||
|
: Segmenter(options), num_segment_(0) {}
|
||||||
|
|
||||||
|
MultiSegmentSegmenter::~MultiSegmentSegmenter() {}
|
||||||
|
|
||||||
|
bool MultiSegmentSegmenter::GetInitRangeStartAndEnd(uint32_t* start,
|
||||||
|
uint32_t* end) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MultiSegmentSegmenter::GetIndexRangeStartAndEnd(uint32_t* start,
|
||||||
|
uint32_t* end) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status MultiSegmentSegmenter::DoInitialize(scoped_ptr<MkvWriter> writer) {
|
||||||
|
writer_ = writer.Pass();
|
||||||
|
return WriteSegmentHeader(0, writer_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
Status MultiSegmentSegmenter::DoFinalize() {
|
||||||
|
return FinalizeSegment();
|
||||||
|
}
|
||||||
|
|
||||||
|
Status MultiSegmentSegmenter::FinalizeSegment() {
|
||||||
|
if (!cluster()->Finalize())
|
||||||
|
return Status(error::FILE_FAILURE, "Error finalizing segment.");
|
||||||
|
|
||||||
|
if (muxer_listener()) {
|
||||||
|
const uint64_t size = cluster()->Size();
|
||||||
|
const uint64_t start_webm_timecode = cluster()->timecode();
|
||||||
|
const uint64_t start_timescale = FromWebMTimecode(start_webm_timecode);
|
||||||
|
const uint64_t length = static_cast<uint64_t>(
|
||||||
|
cluster_length_sec() * stream()->info()->time_scale());
|
||||||
|
muxer_listener()->OnNewSegment(start_timescale, length, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
VLOG(1) << "WEBM file '" << writer_->file()->file_name() << "' finalized.";
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status MultiSegmentSegmenter::NewSubsegment(uint64_t start_timescale) {
|
||||||
|
if (cluster() && !cluster()->Finalize())
|
||||||
|
return Status(error::FILE_FAILURE, "Error finalizing segment.");
|
||||||
|
|
||||||
|
uint64_t start_webm_timecode = FromBMFFTimescale(start_timescale);
|
||||||
|
return SetCluster(start_webm_timecode, 0, writer_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
Status MultiSegmentSegmenter::NewSegment(uint64_t start_timescale) {
|
||||||
|
if (cluster()) {
|
||||||
|
Status temp = FinalizeSegment();
|
||||||
|
if (!temp.ok())
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new file for the new segment.
|
||||||
|
std::string segment_name =
|
||||||
|
GetSegmentName(options().segment_template, start_timescale, num_segment_,
|
||||||
|
options().bandwidth);
|
||||||
|
writer_.reset(new MkvWriter);
|
||||||
|
Status status = writer_->Open(segment_name);
|
||||||
|
if (!status.ok())
|
||||||
|
return status;
|
||||||
|
num_segment_++;
|
||||||
|
|
||||||
|
uint64_t start_webm_timecode = FromBMFFTimescale(start_timescale);
|
||||||
|
return SetCluster(start_webm_timecode, 0, writer_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace webm
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
|
@ -0,0 +1,58 @@
|
||||||
|
// 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_MULTI_SEGMENT_SEGMENTER_H_
|
||||||
|
#define MEDIA_FORMATS_WEBM_MULTI_SEGMENT_SEGMENTER_H_
|
||||||
|
|
||||||
|
#include "packager/media/formats/webm/segmenter.h"
|
||||||
|
|
||||||
|
#include "packager/base/memory/scoped_ptr.h"
|
||||||
|
#include "packager/media/base/status.h"
|
||||||
|
#include "packager/media/formats/webm/mkv_writer.h"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
|
||||||
|
struct MuxerOptions;
|
||||||
|
|
||||||
|
namespace webm {
|
||||||
|
|
||||||
|
/// An implementation of a Segmenter for a multi-segment. Since this does not
|
||||||
|
/// use seeking, it does not matter if the underlying files support seeking.
|
||||||
|
class MultiSegmentSegmenter : public Segmenter {
|
||||||
|
public:
|
||||||
|
explicit MultiSegmentSegmenter(const MuxerOptions& options);
|
||||||
|
~MultiSegmentSegmenter() override;
|
||||||
|
|
||||||
|
/// @name Segmenter implementation overrides.
|
||||||
|
/// @{
|
||||||
|
bool GetInitRangeStartAndEnd(uint32_t* start, uint32_t* end) override;
|
||||||
|
bool GetIndexRangeStartAndEnd(uint32_t* start, uint32_t* end) override;
|
||||||
|
/// @}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Segmenter implementation overrides.
|
||||||
|
Status DoInitialize(scoped_ptr<MkvWriter> writer) override;
|
||||||
|
Status DoFinalize() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Segmenter implementation overrides.
|
||||||
|
Status NewSubsegment(uint64_t start_timescale) override;
|
||||||
|
Status NewSegment(uint64_t start_timescale) override;
|
||||||
|
|
||||||
|
Status FinalizeSegment();
|
||||||
|
|
||||||
|
scoped_ptr<MkvWriter> writer_;
|
||||||
|
uint32_t num_segment_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(MultiSegmentSegmenter);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace webm
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
||||||
|
|
||||||
|
#endif // MEDIA_FORMATS_WEBM_MULTI_SEGMENT_SEGMENTER_H_
|
|
@ -0,0 +1,123 @@
|
||||||
|
// 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/seek_head.h"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include "packager/third_party/libwebm/src/mkvmuxerutil.hpp"
|
||||||
|
#include "packager/third_party/libwebm/src/webmids.hpp"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
namespace {
|
||||||
|
const mkvmuxer::uint64 kElementIds[] = {mkvmuxer::kMkvCluster,
|
||||||
|
mkvmuxer::kMkvCues, mkvmuxer::kMkvInfo,
|
||||||
|
mkvmuxer::kMkvTracks};
|
||||||
|
const int kElementIdCount = arraysize(kElementIds);
|
||||||
|
|
||||||
|
uint64_t MaxSeekEntrySize() {
|
||||||
|
const uint64_t max_entry_payload_size =
|
||||||
|
EbmlElementSize(
|
||||||
|
mkvmuxer::kMkvSeekID,
|
||||||
|
static_cast<mkvmuxer::uint64>(std::numeric_limits<uint32_t>::max())) +
|
||||||
|
EbmlElementSize(mkvmuxer::kMkvSeekPosition,
|
||||||
|
std::numeric_limits<mkvmuxer::uint64>::max());
|
||||||
|
const uint64_t max_entry_size =
|
||||||
|
EbmlMasterElementSize(mkvmuxer::kMkvSeek, max_entry_payload_size) +
|
||||||
|
max_entry_payload_size;
|
||||||
|
|
||||||
|
return max_entry_size;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
SeekHead::SeekHead()
|
||||||
|
: cluster_pos_(-1),
|
||||||
|
cues_pos_(-1),
|
||||||
|
info_pos_(-1),
|
||||||
|
tracks_pos_(-1),
|
||||||
|
wrote_void_(false) {}
|
||||||
|
|
||||||
|
SeekHead::~SeekHead() {}
|
||||||
|
|
||||||
|
bool SeekHead::Write(mkvmuxer::IMkvWriter* writer) {
|
||||||
|
std::vector<uint64_t> element_sizes;
|
||||||
|
const uint64_t payload_size = GetPayloadSize(&element_sizes);
|
||||||
|
|
||||||
|
if (payload_size == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int64_t start_pos = writer->Position();
|
||||||
|
if (!WriteEbmlMasterElement(writer, mkvmuxer::kMkvSeekHead, payload_size))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const int64_t positions[] = {cluster_pos_, cues_pos_, info_pos_, tracks_pos_};
|
||||||
|
for (int i = 0; i < kElementIdCount; ++i) {
|
||||||
|
if (element_sizes[i] == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const mkvmuxer::uint64 position =
|
||||||
|
static_cast<mkvmuxer::uint64>(positions[i]);
|
||||||
|
if (!WriteEbmlMasterElement(writer, mkvmuxer::kMkvSeek, element_sizes[i]) ||
|
||||||
|
!WriteEbmlElement(writer, mkvmuxer::kMkvSeekID, kElementIds[i]) ||
|
||||||
|
!WriteEbmlElement(writer, mkvmuxer::kMkvSeekPosition, position))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we wrote void before, then fill in the extra with void.
|
||||||
|
if (wrote_void_) {
|
||||||
|
const uint64_t max_payload_size = kElementIdCount * MaxSeekEntrySize();
|
||||||
|
const uint64_t total_void_size =
|
||||||
|
EbmlMasterElementSize(mkvmuxer::kMkvSeekHead, max_payload_size) +
|
||||||
|
max_payload_size;
|
||||||
|
|
||||||
|
const uint64_t extra_void =
|
||||||
|
total_void_size - (writer->Position() - start_pos);
|
||||||
|
if (!WriteVoidElement(writer, extra_void))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SeekHead::WriteVoid(mkvmuxer::IMkvWriter* writer) {
|
||||||
|
const uint64_t payload_size = kElementIdCount * MaxSeekEntrySize();
|
||||||
|
const uint64_t total_size =
|
||||||
|
EbmlMasterElementSize(mkvmuxer::kMkvSeekHead, payload_size) +
|
||||||
|
payload_size;
|
||||||
|
|
||||||
|
wrote_void_ = true;
|
||||||
|
const uint64_t written = WriteVoidElement(writer, total_size);
|
||||||
|
if (!written)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t SeekHead::GetPayloadSize(std::vector<uint64_t>* data) {
|
||||||
|
const int64_t positions[] = {cluster_pos_, cues_pos_, info_pos_, tracks_pos_};
|
||||||
|
uint64_t total_payload_size = 0;
|
||||||
|
data->resize(kElementIdCount);
|
||||||
|
for (int i = 0; i < kElementIdCount; ++i) {
|
||||||
|
if (positions[i] < 0) {
|
||||||
|
(*data)[i] = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mkvmuxer::uint64 position =
|
||||||
|
static_cast<mkvmuxer::uint64>(positions[i]);
|
||||||
|
(*data)[i] = EbmlElementSize(mkvmuxer::kMkvSeekID, kElementIds[i]) +
|
||||||
|
EbmlElementSize(mkvmuxer::kMkvSeekPosition, position);
|
||||||
|
total_payload_size +=
|
||||||
|
data->at(i) + EbmlMasterElementSize(mkvmuxer::kMkvSeek, data->at(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return total_payload_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
|
@ -0,0 +1,52 @@
|
||||||
|
// 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_SEEK_HEAD_H_
|
||||||
|
#define MEDIA_FORMATS_WEBM_SEEK_HEAD_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "base/macros.h"
|
||||||
|
#include "packager/third_party/libwebm/src/mkvmuxer.hpp"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
|
||||||
|
/// Used to write the SeekHead to the output stream. This supports non-seekable
|
||||||
|
/// files and setting the values before write; this also supports updating.
|
||||||
|
class SeekHead {
|
||||||
|
public:
|
||||||
|
SeekHead();
|
||||||
|
~SeekHead();
|
||||||
|
|
||||||
|
/// Writes the seek head to the given writer. This should only be called
|
||||||
|
/// once. For seekable files, use WriteVoid first, then call this method.
|
||||||
|
bool Write(mkvmuxer::IMkvWriter* writer);
|
||||||
|
/// Writes a void element large enough to fit the SeekHead.
|
||||||
|
bool WriteVoid(mkvmuxer::IMkvWriter* writer);
|
||||||
|
|
||||||
|
void set_cluster_pos(uint64_t pos) { cluster_pos_ = pos; }
|
||||||
|
void set_cues_pos(uint64_t pos) { cues_pos_ = pos; }
|
||||||
|
void set_info_pos(uint64_t pos) { info_pos_ = pos; }
|
||||||
|
void set_tracks_pos(uint64_t pos) { tracks_pos_ = pos; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint64_t GetPayloadSize(std::vector<uint64_t>* data);
|
||||||
|
|
||||||
|
int64_t cluster_pos_;
|
||||||
|
int64_t cues_pos_;
|
||||||
|
int64_t info_pos_;
|
||||||
|
int64_t tracks_pos_;
|
||||||
|
bool wrote_void_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(SeekHead);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
||||||
|
|
||||||
|
#endif // MEDIA_FORMATS_WEBM_SEEK_HEAD_H_
|
|
@ -0,0 +1,275 @@
|
||||||
|
// 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.h"
|
||||||
|
|
||||||
|
#include "packager/base/time/time.h"
|
||||||
|
#include "packager/media/base/audio_stream_info.h"
|
||||||
|
#include "packager/media/base/media_sample.h"
|
||||||
|
#include "packager/media/base/media_stream.h"
|
||||||
|
#include "packager/media/base/muxer_options.h"
|
||||||
|
#include "packager/media/base/stream_info.h"
|
||||||
|
#include "packager/media/base/video_stream_info.h"
|
||||||
|
#include "packager/media/event/muxer_listener.h"
|
||||||
|
#include "packager/media/event/progress_listener.h"
|
||||||
|
#include "packager/third_party/libwebm/src/mkvmuxerutil.hpp"
|
||||||
|
#include "packager/third_party/libwebm/src/webmids.hpp"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
namespace webm {
|
||||||
|
namespace {
|
||||||
|
int64_t kTimecodeScale = 1000000;
|
||||||
|
int64_t kSecondsToNs = 1000000000L;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Segmenter::Segmenter(const MuxerOptions& options)
|
||||||
|
: options_(options),
|
||||||
|
stream_(NULL),
|
||||||
|
muxer_listener_(NULL),
|
||||||
|
progress_listener_(NULL),
|
||||||
|
progress_target_(0),
|
||||||
|
accumulated_progress_(0),
|
||||||
|
total_duration_(0),
|
||||||
|
sample_duration_(0),
|
||||||
|
segment_payload_pos_(0),
|
||||||
|
cluster_length_sec_(0),
|
||||||
|
segment_length_sec_(0),
|
||||||
|
track_id_(0) {}
|
||||||
|
|
||||||
|
Segmenter::~Segmenter() {}
|
||||||
|
|
||||||
|
Status Segmenter::Initialize(scoped_ptr<MkvWriter> writer,
|
||||||
|
MediaStream* streams,
|
||||||
|
ProgressListener* progress_listener,
|
||||||
|
MuxerListener* muxer_listener,
|
||||||
|
KeySource* encryption_key_source) {
|
||||||
|
muxer_listener_ = muxer_listener;
|
||||||
|
stream_ = streams;
|
||||||
|
|
||||||
|
// Use media duration as progress target.
|
||||||
|
progress_target_ = stream_->info()->duration();
|
||||||
|
progress_listener_ = progress_listener;
|
||||||
|
|
||||||
|
segment_info_.Init();
|
||||||
|
segment_info_.set_timecode_scale(kTimecodeScale);
|
||||||
|
if (options().single_segment) {
|
||||||
|
// Set an initial duration so the duration element is written; will be
|
||||||
|
// overwritten at the end.
|
||||||
|
segment_info_.set_duration(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the track info.
|
||||||
|
Status status;
|
||||||
|
switch (stream_->info()->stream_type()) {
|
||||||
|
case kStreamVideo:
|
||||||
|
status = CreateVideoTrack(
|
||||||
|
static_cast<VideoStreamInfo*>(stream_->info().get()));
|
||||||
|
break;
|
||||||
|
case kStreamAudio:
|
||||||
|
status = CreateAudioTrack(
|
||||||
|
static_cast<AudioStreamInfo*>(stream_->info().get()));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
NOTIMPLEMENTED() << "Not implemented for stream type: "
|
||||||
|
<< stream_->info()->stream_type();
|
||||||
|
status = Status(error::UNIMPLEMENTED, "Not implemented for stream type");
|
||||||
|
}
|
||||||
|
if (!status.ok())
|
||||||
|
return status;
|
||||||
|
|
||||||
|
return DoInitialize(writer.Pass());
|
||||||
|
}
|
||||||
|
|
||||||
|
Status Segmenter::Finalize() {
|
||||||
|
segment_info_.set_duration(FromBMFFTimescale(total_duration_));
|
||||||
|
return DoFinalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
Status Segmenter::AddSample(scoped_refptr<MediaSample> sample) {
|
||||||
|
if (sample_duration_ == 0) {
|
||||||
|
sample_duration_ = sample->duration();
|
||||||
|
if (muxer_listener_)
|
||||||
|
muxer_listener_->OnSampleDurationReady(sample_duration_);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateProgress(sample->duration());
|
||||||
|
|
||||||
|
// Create a new cluster if needed.
|
||||||
|
Status status;
|
||||||
|
if (!cluster_) {
|
||||||
|
status = NewSegment(sample->pts());
|
||||||
|
} else if (segment_length_sec_ >= options_.segment_duration) {
|
||||||
|
if (sample->is_key_frame() || !options_.segment_sap_aligned) {
|
||||||
|
status = NewSegment(sample->pts());
|
||||||
|
segment_length_sec_ = 0;
|
||||||
|
cluster_length_sec_ = 0;
|
||||||
|
}
|
||||||
|
} else if (cluster_length_sec_ >= options_.fragment_duration) {
|
||||||
|
if (sample->is_key_frame() || !options_.fragment_sap_aligned) {
|
||||||
|
status = NewSubsegment(sample->pts());
|
||||||
|
cluster_length_sec_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!status.ok())
|
||||||
|
return status;
|
||||||
|
|
||||||
|
const int64_t time_ns =
|
||||||
|
sample->pts() * kSecondsToNs / stream_->info()->time_scale();
|
||||||
|
if (!cluster_->AddFrame(sample->data(), sample->data_size(), track_id_,
|
||||||
|
time_ns, sample->is_key_frame())) {
|
||||||
|
LOG(ERROR) << "Error adding sample to segment.";
|
||||||
|
return Status(error::FILE_FAILURE, "Error adding sample to segment.");
|
||||||
|
}
|
||||||
|
const double duration_sec =
|
||||||
|
static_cast<double>(sample->duration()) / stream_->info()->time_scale();
|
||||||
|
cluster_length_sec_ += duration_sec;
|
||||||
|
segment_length_sec_ += duration_sec;
|
||||||
|
total_duration_ += sample->duration();
|
||||||
|
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Segmenter::GetDuration() const {
|
||||||
|
return static_cast<float>(segment_info_.duration()) *
|
||||||
|
segment_info_.timecode_scale() / kSecondsToNs;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status Segmenter::WriteSegmentHeader(uint64_t file_size, MkvWriter* writer) {
|
||||||
|
Status error_status(error::FILE_FAILURE, "Error writing segment header.");
|
||||||
|
|
||||||
|
if (!WriteEbmlHeader(writer))
|
||||||
|
return error_status;
|
||||||
|
|
||||||
|
if (WriteID(writer, mkvmuxer::kMkvSegment) != 0)
|
||||||
|
return error_status;
|
||||||
|
|
||||||
|
const uint64_t segment_size_size = 8;
|
||||||
|
segment_payload_pos_ = writer->Position() + segment_size_size;
|
||||||
|
if (file_size > 0) {
|
||||||
|
// We want the size of the segment element, so subtract the header.
|
||||||
|
if (WriteUIntSize(writer, file_size - segment_payload_pos_,
|
||||||
|
segment_size_size) != 0)
|
||||||
|
return error_status;
|
||||||
|
if (!seek_head_.Write(writer))
|
||||||
|
return error_status;
|
||||||
|
} else {
|
||||||
|
if (SerializeInt(writer, mkvmuxer::kEbmlUnknownValue, segment_size_size) !=
|
||||||
|
0)
|
||||||
|
return error_status;
|
||||||
|
// We don't know the header size, so write a placeholder.
|
||||||
|
if (!seek_head_.WriteVoid(writer))
|
||||||
|
return error_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!segment_info_.Write(writer))
|
||||||
|
return error_status;
|
||||||
|
|
||||||
|
if (!tracks_.Write(writer))
|
||||||
|
return error_status;
|
||||||
|
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status Segmenter::SetCluster(uint64_t start_webm_timecode,
|
||||||
|
uint64_t position,
|
||||||
|
MkvWriter* writer) {
|
||||||
|
const uint64_t scale = segment_info_.timecode_scale();
|
||||||
|
cluster_.reset(new mkvmuxer::Cluster(start_webm_timecode, position, scale));
|
||||||
|
cluster_->Init(writer);
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Segmenter::UpdateProgress(uint64_t progress) {
|
||||||
|
accumulated_progress_ += progress;
|
||||||
|
if (!progress_listener_ || progress_target_ == 0)
|
||||||
|
return;
|
||||||
|
// It might happen that accumulated progress exceeds progress_target due to
|
||||||
|
// computation errors, e.g. rounding error. Cap it so it never reports > 100%
|
||||||
|
// progress.
|
||||||
|
if (accumulated_progress_ >= progress_target_) {
|
||||||
|
progress_listener_->OnProgress(1.0);
|
||||||
|
} else {
|
||||||
|
progress_listener_->OnProgress(static_cast<double>(accumulated_progress_) /
|
||||||
|
progress_target_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Status Segmenter::CreateVideoTrack(VideoStreamInfo* info) {
|
||||||
|
// The seed is only used to create a UID which we overwrite later.
|
||||||
|
unsigned int seed = 0;
|
||||||
|
mkvmuxer::VideoTrack* track = new mkvmuxer::VideoTrack(&seed);
|
||||||
|
if (!track)
|
||||||
|
return Status(error::INTERNAL_ERROR, "Failed to create video track.");
|
||||||
|
|
||||||
|
if (info->codec() == kCodecVP8) {
|
||||||
|
track->set_codec_id(mkvmuxer::Tracks::kVp8CodecId);
|
||||||
|
} else if (info->codec() == kCodecVP9) {
|
||||||
|
track->set_codec_id(mkvmuxer::Tracks::kVp9CodecId);
|
||||||
|
} else {
|
||||||
|
LOG(ERROR) << "Only VP8 and VP9 video codec is supported.";
|
||||||
|
return Status(error::UNIMPLEMENTED,
|
||||||
|
"Only VP8 and VP9 video codecs are supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
track->set_uid(info->track_id());
|
||||||
|
track->set_type(mkvmuxer::Tracks::kVideo);
|
||||||
|
track->set_width(info->width());
|
||||||
|
track->set_height(info->height());
|
||||||
|
track->set_display_height(info->height());
|
||||||
|
track->set_display_width(info->width() * info->pixel_width() /
|
||||||
|
info->pixel_height());
|
||||||
|
track->set_language(info->language().c_str());
|
||||||
|
|
||||||
|
tracks_.AddTrack(track, info->track_id());
|
||||||
|
track_id_ = track->number();
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status Segmenter::CreateAudioTrack(AudioStreamInfo* info) {
|
||||||
|
// The seed is only used to create a UID which we overwrite later.
|
||||||
|
unsigned int seed = 0;
|
||||||
|
mkvmuxer::AudioTrack* track = new mkvmuxer::AudioTrack(&seed);
|
||||||
|
if (!track)
|
||||||
|
return Status(error::INTERNAL_ERROR, "Failed to create audio track.");
|
||||||
|
|
||||||
|
if (info->codec() == kCodecOpus) {
|
||||||
|
track->set_codec_id(mkvmuxer::Tracks::kOpusCodecId);
|
||||||
|
} else if (info->codec() == kCodecVorbis) {
|
||||||
|
track->set_codec_id(mkvmuxer::Tracks::kVorbisCodecId);
|
||||||
|
} else {
|
||||||
|
LOG(ERROR) << "Only Vorbis and Opus audio codec is supported.";
|
||||||
|
return Status(error::UNIMPLEMENTED,
|
||||||
|
"Only Vorbis and Opus audio codecs are supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
track->set_uid(info->track_id());
|
||||||
|
track->set_language(info->language().c_str());
|
||||||
|
track->set_type(mkvmuxer::Tracks::kAudio);
|
||||||
|
track->set_sample_rate(info->sampling_frequency());
|
||||||
|
track->set_channels(info->num_channels());
|
||||||
|
|
||||||
|
tracks_.AddTrack(track, info->track_id());
|
||||||
|
track_id_ = track->number();
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace webm
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
|
@ -0,0 +1,154 @@
|
||||||
|
// 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_H_
|
||||||
|
#define MEDIA_FORMATS_WEBM_SEGMENTER_H_
|
||||||
|
|
||||||
|
#include "packager/base/memory/ref_counted.h"
|
||||||
|
#include "packager/base/memory/scoped_ptr.h"
|
||||||
|
#include "packager/media/base/status.h"
|
||||||
|
#include "packager/media/formats/webm/mkv_writer.h"
|
||||||
|
#include "packager/media/formats/webm/seek_head.h"
|
||||||
|
#include "packager/third_party/libwebm/src/mkvmuxer.hpp"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
|
||||||
|
struct MuxerOptions;
|
||||||
|
|
||||||
|
class AudioStreamInfo;
|
||||||
|
class KeySource;
|
||||||
|
class MediaSample;
|
||||||
|
class MediaStream;
|
||||||
|
class MuxerListener;
|
||||||
|
class ProgressListener;
|
||||||
|
class StreamInfo;
|
||||||
|
class VideoStreamInfo;
|
||||||
|
|
||||||
|
namespace webm {
|
||||||
|
|
||||||
|
class Segmenter {
|
||||||
|
public:
|
||||||
|
explicit Segmenter(const MuxerOptions& options);
|
||||||
|
virtual ~Segmenter();
|
||||||
|
|
||||||
|
/// Initialize the 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 muxer_listener receives muxer events. Can be NULL.
|
||||||
|
/// @param encryption_key_source points to the key source which contains
|
||||||
|
/// the encryption keys. It can be NULL to indicate that no encryption
|
||||||
|
/// is required.
|
||||||
|
/// @return OK on success, an error status otherwise.
|
||||||
|
Status Initialize(scoped_ptr<MkvWriter> writer,
|
||||||
|
MediaStream* streams,
|
||||||
|
ProgressListener* progress_listener,
|
||||||
|
MuxerListener* muxer_listener,
|
||||||
|
KeySource* encryption_key_source);
|
||||||
|
|
||||||
|
/// Finalize the segmenter.
|
||||||
|
/// @return OK on success, an error status otherwise.
|
||||||
|
Status Finalize();
|
||||||
|
|
||||||
|
/// Add sample to the indicated stream.
|
||||||
|
/// @param sample points to the sample to be added.
|
||||||
|
/// @return OK on success, an error status otherwise.
|
||||||
|
Status AddSample(scoped_refptr<MediaSample> sample);
|
||||||
|
|
||||||
|
// TODO(modmaker): Add key source support.
|
||||||
|
|
||||||
|
/// @return true if there is an initialization range, while setting @a start
|
||||||
|
/// and @a end; or false if initialization range does not apply.
|
||||||
|
virtual bool GetInitRangeStartAndEnd(uint32_t* start, uint32_t* end) = 0;
|
||||||
|
|
||||||
|
/// @return true if there is an index byte range, while setting @a start
|
||||||
|
/// and @a end; or false if index byte range does not apply.
|
||||||
|
virtual bool GetIndexRangeStartAndEnd(uint32_t* start, uint32_t* end) = 0;
|
||||||
|
|
||||||
|
/// @return The total length, in seconds, of segmented media files.
|
||||||
|
float GetDuration() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Converts the given time in ISO BMFF timescale to the current WebM
|
||||||
|
/// timecode.
|
||||||
|
uint64_t FromBMFFTimescale(uint64_t time_timescale);
|
||||||
|
/// Converts the given time in WebM timecode to ISO BMFF timescale.
|
||||||
|
uint64_t FromWebMTimecode(uint64_t time_webm_timecode);
|
||||||
|
/// Writes the Segment header to @a writer.
|
||||||
|
Status WriteSegmentHeader(uint64_t file_size, MkvWriter* writer);
|
||||||
|
/// Creates a Cluster object with the given parameters.
|
||||||
|
Status SetCluster(uint64_t start_webm_timecode,
|
||||||
|
uint64_t position,
|
||||||
|
MkvWriter* writer);
|
||||||
|
|
||||||
|
/// Update segmentation progress using ProgressListener.
|
||||||
|
void UpdateProgress(uint64_t progress);
|
||||||
|
void set_progress_target(uint64_t target) { progress_target_ = target; }
|
||||||
|
|
||||||
|
const MuxerOptions& options() const { return options_; }
|
||||||
|
mkvmuxer::Cluster* cluster() { return cluster_.get(); }
|
||||||
|
mkvmuxer::Cues* cues() { return &cues_; }
|
||||||
|
MuxerListener* muxer_listener() { return muxer_listener_; }
|
||||||
|
MediaStream* stream() { return stream_; }
|
||||||
|
SeekHead* seek_head() { return &seek_head_; }
|
||||||
|
|
||||||
|
int track_id() const { return track_id_; }
|
||||||
|
uint64_t segment_payload_pos() const { return segment_payload_pos_; }
|
||||||
|
double cluster_length_sec() const { return cluster_length_sec_; }
|
||||||
|
|
||||||
|
virtual Status DoInitialize(scoped_ptr<MkvWriter> writer) = 0;
|
||||||
|
virtual Status DoFinalize() = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Status CreateVideoTrack(VideoStreamInfo* info);
|
||||||
|
Status CreateAudioTrack(AudioStreamInfo* info);
|
||||||
|
|
||||||
|
// This is called when there needs to be a new subsegment. This does nothing
|
||||||
|
// in single-segment mode. In multi-segment mode this creates a new Cluster
|
||||||
|
// element.
|
||||||
|
virtual Status NewSubsegment(uint64_t start_timescale) = 0;
|
||||||
|
// This is called when there needs to be a new segment. In single-segment
|
||||||
|
// mode, this creates a new Cluster element. In multi-segment mode this
|
||||||
|
// creates a new output file.
|
||||||
|
virtual Status NewSegment(uint64_t start_timescale) = 0;
|
||||||
|
// This is called when a segment ends. This is called right before a call
|
||||||
|
// to NewSegment and at the start of Finalize.
|
||||||
|
Status FinalizeSegment(uint64_t end_timescale);
|
||||||
|
|
||||||
|
const MuxerOptions& options_;
|
||||||
|
|
||||||
|
scoped_ptr<mkvmuxer::Cluster> cluster_;
|
||||||
|
mkvmuxer::Cues cues_;
|
||||||
|
SeekHead seek_head_;
|
||||||
|
mkvmuxer::SegmentInfo segment_info_;
|
||||||
|
mkvmuxer::Tracks tracks_;
|
||||||
|
|
||||||
|
MediaStream* stream_;
|
||||||
|
MuxerListener* muxer_listener_;
|
||||||
|
ProgressListener* progress_listener_;
|
||||||
|
uint64_t progress_target_;
|
||||||
|
uint64_t accumulated_progress_;
|
||||||
|
uint64_t total_duration_;
|
||||||
|
uint64_t sample_duration_;
|
||||||
|
// The position (in bytes) of the start of the Segment payload in the init
|
||||||
|
// file. This is also the size of the header before the SeekHead.
|
||||||
|
uint64_t segment_payload_pos_;
|
||||||
|
|
||||||
|
double cluster_length_sec_;
|
||||||
|
double segment_length_sec_;
|
||||||
|
|
||||||
|
int track_id_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(Segmenter);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace webm
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
||||||
|
|
||||||
|
#endif // MEDIA_FORMATS_WEBM_SEGMENTER_H_
|
|
@ -0,0 +1,88 @@
|
||||||
|
// 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/single_segment_segmenter.h"
|
||||||
|
|
||||||
|
#include "packager/media/base/muxer_options.h"
|
||||||
|
#include "packager/third_party/libwebm/src/mkvmuxer.hpp"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
namespace webm {
|
||||||
|
|
||||||
|
SingleSegmentSegmenter::SingleSegmentSegmenter(const MuxerOptions& options)
|
||||||
|
: Segmenter(options), init_end_(0), index_start_(0) {}
|
||||||
|
|
||||||
|
SingleSegmentSegmenter::~SingleSegmentSegmenter() {}
|
||||||
|
|
||||||
|
Status SingleSegmentSegmenter::DoInitialize(scoped_ptr<MkvWriter> writer) {
|
||||||
|
writer_ = writer.Pass();
|
||||||
|
Status ret = WriteSegmentHeader(0, writer_.get());
|
||||||
|
init_end_ = writer_->Position() - 1;
|
||||||
|
seek_head()->set_cluster_pos(init_end_ + 1);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status SingleSegmentSegmenter::DoFinalize() {
|
||||||
|
if (!cluster()->Finalize())
|
||||||
|
return Status(error::FILE_FAILURE, "Error finalizing cluster.");
|
||||||
|
|
||||||
|
// Write the Cues to the end of the file.
|
||||||
|
index_start_ = writer_->Position();
|
||||||
|
if (!cues()->Write(writer_.get()))
|
||||||
|
return Status(error::FILE_FAILURE, "Error writing Cues data.");
|
||||||
|
|
||||||
|
uint64_t file_size = writer_->Position();
|
||||||
|
writer_->Position(0);
|
||||||
|
|
||||||
|
Status status = WriteSegmentHeader(file_size, writer_.get());
|
||||||
|
writer_->Position(file_size);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status SingleSegmentSegmenter::NewSubsegment(uint64_t start_timescale) {
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status SingleSegmentSegmenter::NewSegment(uint64_t start_timescale) {
|
||||||
|
if (cluster() && !cluster()->Finalize())
|
||||||
|
return Status(error::FILE_FAILURE, "Error finalizing cluster.");
|
||||||
|
|
||||||
|
// Create a new Cue point.
|
||||||
|
uint64_t position = writer_->Position();
|
||||||
|
uint64_t start_webm_timecode = FromBMFFTimescale(start_timescale);
|
||||||
|
|
||||||
|
mkvmuxer::CuePoint* cue_point = new mkvmuxer::CuePoint;
|
||||||
|
cue_point->set_time(start_webm_timecode);
|
||||||
|
cue_point->set_track(track_id());
|
||||||
|
cue_point->set_cluster_pos(position - segment_payload_pos());
|
||||||
|
if (!cues()->AddCue(cue_point))
|
||||||
|
return Status(error::INTERNAL_ERROR, "Error adding CuePoint.");
|
||||||
|
|
||||||
|
return SetCluster(start_webm_timecode, position, writer_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleSegmentSegmenter::GetInitRangeStartAndEnd(uint32_t* start,
|
||||||
|
uint32_t* end) {
|
||||||
|
// The init range is the header, from the start of the file to the size of
|
||||||
|
// the header.
|
||||||
|
*start = 0;
|
||||||
|
*end = init_end_;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleSegmentSegmenter::GetIndexRangeStartAndEnd(uint32_t* start,
|
||||||
|
uint32_t* end) {
|
||||||
|
// The index is the Cues element, which is always placed at the end of the
|
||||||
|
// file.
|
||||||
|
*start = index_start_;
|
||||||
|
*end = writer_->file()->Size();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace webm
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
|
@ -0,0 +1,63 @@
|
||||||
|
// 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_SINGLE_SEGMENT_SEGMENTER_H_
|
||||||
|
#define MEDIA_FORMATS_WEBM_SINGLE_SEGMENT_SEGMENTER_H_
|
||||||
|
|
||||||
|
#include "packager/media/formats/webm/segmenter.h"
|
||||||
|
|
||||||
|
#include "packager/base/memory/scoped_ptr.h"
|
||||||
|
#include "packager/media/base/status.h"
|
||||||
|
#include "packager/media/formats/webm/mkv_writer.h"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
|
||||||
|
struct MuxerOptions;
|
||||||
|
|
||||||
|
namespace webm {
|
||||||
|
|
||||||
|
/// An implementation of a Segmenter for a single-segment. This assumes that
|
||||||
|
/// the output file is seekable. For non-seekable files, use
|
||||||
|
/// TwoPassSingleSegmentSegmenter.
|
||||||
|
class SingleSegmentSegmenter : public Segmenter {
|
||||||
|
public:
|
||||||
|
explicit SingleSegmentSegmenter(const MuxerOptions& options);
|
||||||
|
~SingleSegmentSegmenter() override;
|
||||||
|
|
||||||
|
/// @name Segmenter implementation overrides.
|
||||||
|
/// @{
|
||||||
|
bool GetInitRangeStartAndEnd(uint32_t* start, uint32_t* end) override;
|
||||||
|
bool GetIndexRangeStartAndEnd(uint32_t* start, uint32_t* end) override;
|
||||||
|
/// @}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
MkvWriter* writer() { return writer_.get(); }
|
||||||
|
void set_init_end(uint64_t end) { init_end_ = end; }
|
||||||
|
void set_index_start(uint64_t start) { index_start_ = start; }
|
||||||
|
void set_writer(scoped_ptr<MkvWriter> writer) { writer_ = writer.Pass(); }
|
||||||
|
|
||||||
|
// Segmenter implementation overrides.
|
||||||
|
Status DoInitialize(scoped_ptr<MkvWriter> writer) override;
|
||||||
|
Status DoFinalize() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Segmenter implementation overrides.
|
||||||
|
Status NewSubsegment(uint64_t start_timescale) override;
|
||||||
|
Status NewSegment(uint64_t start_timescale) override;
|
||||||
|
|
||||||
|
scoped_ptr<MkvWriter> writer_;
|
||||||
|
uint32_t init_end_;
|
||||||
|
uint32_t index_start_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(SingleSegmentSegmenter);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace webm
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
||||||
|
|
||||||
|
#endif // MEDIA_FORMATS_WEBM_SINGLE_SEGMENT_SEGMENTER_H_
|
|
@ -0,0 +1,184 @@
|
||||||
|
// 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/two_pass_single_segment_segmenter.h"
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/rand.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "packager/base/files/file_path.h"
|
||||||
|
#include "packager/base/strings/stringprintf.h"
|
||||||
|
#include "packager/base/threading/platform_thread.h"
|
||||||
|
#include "packager/base/time/time.h"
|
||||||
|
#include "packager/media/base/media_sample.h"
|
||||||
|
#include "packager/media/base/media_stream.h"
|
||||||
|
#include "packager/media/base/muxer_options.h"
|
||||||
|
#include "packager/media/base/stream_info.h"
|
||||||
|
#include "packager/third_party/libwebm/src/mkvmuxer.hpp"
|
||||||
|
#include "packager/third_party/libwebm/src/mkvmuxerutil.hpp"
|
||||||
|
#include "packager/third_party/libwebm/src/webmids.hpp"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
namespace webm {
|
||||||
|
namespace {
|
||||||
|
/// Create the temp file name using process/thread id and current time.
|
||||||
|
std::string TempFileName(const MuxerOptions& options) {
|
||||||
|
// TODO: Move to a common util function and remove other uses.
|
||||||
|
int32_t tid = static_cast<int32_t>(base::PlatformThread::CurrentId());
|
||||||
|
int64_t rand = 0;
|
||||||
|
if (RAND_bytes(reinterpret_cast<uint8_t*>(&rand), sizeof(rand)) != 1) {
|
||||||
|
LOG(WARNING) << "RAND_bytes failed with error: "
|
||||||
|
<< ERR_error_string(ERR_get_error(), NULL);
|
||||||
|
rand = base::Time::Now().ToInternalValue();
|
||||||
|
}
|
||||||
|
std::string file_prefix =
|
||||||
|
base::StringPrintf("packager-tempfile-%x-%" PRIx64 ".tmp", tid, rand);
|
||||||
|
return base::FilePath(options.temp_dir).Append(file_prefix).value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skips a given number of bytes in a file by reading. This allows
|
||||||
|
// forward-seeking in non-seekable files.
|
||||||
|
bool ReadSkip(File* file, int64_t byte_count) {
|
||||||
|
const int64_t kBufferSize = 0x40000; // 256KB.
|
||||||
|
scoped_ptr<char[]> buffer(new char[kBufferSize]);
|
||||||
|
int64_t bytes_read = 0;
|
||||||
|
while (bytes_read < byte_count) {
|
||||||
|
int64_t size = std::min(kBufferSize, byte_count - bytes_read);
|
||||||
|
int64_t result = file->Read(buffer.get(), size);
|
||||||
|
// Only give success if there are no errors, not at EOF, and read exactly
|
||||||
|
// byte_count bytes.
|
||||||
|
if (result <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bytes_read += result;
|
||||||
|
}
|
||||||
|
|
||||||
|
DCHECK_EQ(bytes_read, byte_count);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TwoPassSingleSegmentSegmenter::TwoPassSingleSegmentSegmenter(
|
||||||
|
const MuxerOptions& options)
|
||||||
|
: SingleSegmentSegmenter(options) {}
|
||||||
|
|
||||||
|
TwoPassSingleSegmentSegmenter::~TwoPassSingleSegmentSegmenter() {}
|
||||||
|
|
||||||
|
Status
|
||||||
|
TwoPassSingleSegmentSegmenter::DoInitialize(scoped_ptr<MkvWriter> writer) {
|
||||||
|
// Assume the amount of time to copy the temp file as the same amount
|
||||||
|
// of time as to make it.
|
||||||
|
set_progress_target(stream()->info()->duration() * 2);
|
||||||
|
|
||||||
|
real_writer_ = writer.Pass();
|
||||||
|
|
||||||
|
std::string temp_name = TempFileName(options());
|
||||||
|
scoped_ptr<MkvWriter> temp(new MkvWriter);
|
||||||
|
Status status = temp->Open(temp_name);
|
||||||
|
if (!status.ok())
|
||||||
|
return status;
|
||||||
|
|
||||||
|
return SingleSegmentSegmenter::DoInitialize(temp.Pass());
|
||||||
|
}
|
||||||
|
|
||||||
|
Status TwoPassSingleSegmentSegmenter::DoFinalize() {
|
||||||
|
if (!cluster()->Finalize())
|
||||||
|
return Status(error::FILE_FAILURE, "Error finalizing cluster.");
|
||||||
|
|
||||||
|
// Write the Cues to the end of the temp file.
|
||||||
|
uint64_t cues_pos = writer()->Position();
|
||||||
|
set_index_start(cues_pos);
|
||||||
|
seek_head()->set_cues_pos(cues_pos);
|
||||||
|
if (!cues()->Write(writer()))
|
||||||
|
return Status(error::FILE_FAILURE, "Error writing Cues data.");
|
||||||
|
|
||||||
|
// Write the header to the real output file.
|
||||||
|
Status temp = WriteSegmentHeader(writer()->Position(), real_writer_.get());
|
||||||
|
if (!temp.ok())
|
||||||
|
return temp;
|
||||||
|
|
||||||
|
// Close the temp file and open it for reading.
|
||||||
|
std::string temp_name = writer()->file()->file_name();
|
||||||
|
set_writer(scoped_ptr<MkvWriter>());
|
||||||
|
scoped_ptr<File, FileCloser> temp_reader(File::Open(temp_name.c_str(), "r"));
|
||||||
|
if (!temp_reader)
|
||||||
|
return Status(error::FILE_FAILURE, "Error opening temp file.");
|
||||||
|
|
||||||
|
// Skip the header that has already been written.
|
||||||
|
uint64_t header_size = real_writer_->Position();
|
||||||
|
if (!ReadSkip(temp_reader.get(), header_size))
|
||||||
|
return Status(error::FILE_FAILURE, "Error reading temp file.");
|
||||||
|
|
||||||
|
// Copy the rest of the data over.
|
||||||
|
if (!CopyFileWithClusterRewrite(temp_reader.get(), real_writer_.get(),
|
||||||
|
cluster()->Size()))
|
||||||
|
return Status(error::FILE_FAILURE, "Error copying temp file.");
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the writer back to the real file so GetIndexRangeStartAndEnd works.
|
||||||
|
set_writer(real_writer_.Pass());
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TwoPassSingleSegmentSegmenter::CopyFileWithClusterRewrite(
|
||||||
|
File* source,
|
||||||
|
MkvWriter* dest,
|
||||||
|
uint64_t last_size) {
|
||||||
|
const int cluster_id_size = mkvmuxer::GetUIntSize(mkvmuxer::kMkvCluster);
|
||||||
|
const int cluster_size_size = 8; // The size of the Cluster size integer.
|
||||||
|
const int cluster_header_size = cluster_id_size + cluster_size_size;
|
||||||
|
|
||||||
|
// We are at the start of a cluster, so copy the ID.
|
||||||
|
if (dest->WriteFromFile(source, cluster_id_size) != cluster_id_size)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (int i = 0; i < cues()->cue_entries_size() - 1; ++i) {
|
||||||
|
// Write the size of the cluster.
|
||||||
|
const mkvmuxer::CuePoint* cue = cues()->GetCueByIndex(i);
|
||||||
|
const mkvmuxer::CuePoint* next_cue = cues()->GetCueByIndex(i + 1);
|
||||||
|
const int64_t cluster_payload_size =
|
||||||
|
next_cue->cluster_pos() - cue->cluster_pos() - cluster_header_size;
|
||||||
|
if (mkvmuxer::WriteUIntSize(dest, cluster_payload_size, cluster_size_size))
|
||||||
|
return false;
|
||||||
|
if (!ReadSkip(source, cluster_size_size))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Copy the cluster and the next cluster's ID.
|
||||||
|
int64_t to_copy = cluster_payload_size + cluster_id_size;
|
||||||
|
if (dest->WriteFromFile(source, to_copy) != to_copy)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Update the progress; need to convert from WebM timecode to ISO BMFF.
|
||||||
|
const uint64_t webm_delta_time = next_cue->time() - cue->time();
|
||||||
|
const uint64_t delta_time = FromWebMTimecode(webm_delta_time);
|
||||||
|
UpdateProgress(delta_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The last cluster takes up until the cues.
|
||||||
|
const uint64_t last_cluster_payload_size = last_size - cluster_header_size;
|
||||||
|
if (mkvmuxer::WriteUIntSize(dest, last_cluster_payload_size,
|
||||||
|
cluster_size_size))
|
||||||
|
return false;
|
||||||
|
if (!ReadSkip(source, cluster_size_size))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Copy the remaining data (i.e. Cues data).
|
||||||
|
return dest->WriteFromFile(source) ==
|
||||||
|
static_cast<int64_t>(last_cluster_payload_size + cues()->Size());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace webm
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
|
@ -0,0 +1,52 @@
|
||||||
|
// 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_TWO_PASS_SINGLE_SEGMENT_SEGMENTER_H_
|
||||||
|
#define MEDIA_FORMATS_WEBM_TWO_PASS_SINGLE_SEGMENT_SEGMENTER_H_
|
||||||
|
|
||||||
|
#include "packager/media/formats/webm/single_segment_segmenter.h"
|
||||||
|
|
||||||
|
#include "packager/base/memory/scoped_ptr.h"
|
||||||
|
#include "packager/media/base/status.h"
|
||||||
|
#include "packager/media/formats/webm/mkv_writer.h"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
|
||||||
|
struct MuxerOptions;
|
||||||
|
|
||||||
|
namespace webm {
|
||||||
|
|
||||||
|
/// An implementation of a Segmenter for a single-segment that performs two
|
||||||
|
/// passes. This does not use seeking and is used for non-seekable files.
|
||||||
|
class TwoPassSingleSegmentSegmenter : public SingleSegmentSegmenter {
|
||||||
|
public:
|
||||||
|
explicit TwoPassSingleSegmentSegmenter(const MuxerOptions& options);
|
||||||
|
~TwoPassSingleSegmentSegmenter() override;
|
||||||
|
|
||||||
|
// Segmenter implementation overrides.
|
||||||
|
Status DoInitialize(scoped_ptr<MkvWriter> writer) override;
|
||||||
|
Status DoFinalize() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Copies the data from source to destination while rewriting the Cluster
|
||||||
|
/// sizes to the correct values. This assumes that both @a source and
|
||||||
|
/// @a dest are at the same position and that the headers have already
|
||||||
|
/// been copied.
|
||||||
|
bool CopyFileWithClusterRewrite(File* source,
|
||||||
|
MkvWriter* dest,
|
||||||
|
uint64_t last_size);
|
||||||
|
|
||||||
|
scoped_ptr<MkvWriter> real_writer_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(TwoPassSingleSegmentSegmenter);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace webm
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
||||||
|
|
||||||
|
#endif // MEDIA_FORMATS_WEBM_TWO_PASS_SINGLE_SEGMENT_SEGMENTER_H_
|
|
@ -13,6 +13,18 @@
|
||||||
'target_name': 'webm',
|
'target_name': 'webm',
|
||||||
'type': '<(component)',
|
'type': '<(component)',
|
||||||
'sources': [
|
'sources': [
|
||||||
|
'mkv_writer.cc',
|
||||||
|
'mkv_writer.h',
|
||||||
|
'multi_segment_segmenter.cc',
|
||||||
|
'multi_segment_segmenter.h',
|
||||||
|
'seek_head.cc',
|
||||||
|
'seek_head.h',
|
||||||
|
'segmenter.cc',
|
||||||
|
'segmenter.h',
|
||||||
|
'single_segment_segmenter.cc',
|
||||||
|
'single_segment_segmenter.h',
|
||||||
|
'two_pass_single_segment_segmenter.cc',
|
||||||
|
'two_pass_single_segment_segmenter.h',
|
||||||
'webm_audio_client.cc',
|
'webm_audio_client.cc',
|
||||||
'webm_audio_client.h',
|
'webm_audio_client.h',
|
||||||
'webm_cluster_parser.cc',
|
'webm_cluster_parser.cc',
|
||||||
|
@ -31,6 +43,8 @@
|
||||||
'webm_parser.h',
|
'webm_parser.h',
|
||||||
'webm_media_parser.cc',
|
'webm_media_parser.cc',
|
||||||
'webm_media_parser.h',
|
'webm_media_parser.h',
|
||||||
|
'webm_muxer.cc',
|
||||||
|
'webm_muxer.h',
|
||||||
'webm_tracks_parser.cc',
|
'webm_tracks_parser.cc',
|
||||||
'webm_tracks_parser.h',
|
'webm_tracks_parser.h',
|
||||||
'webm_video_client.cc',
|
'webm_video_client.cc',
|
||||||
|
@ -39,6 +53,7 @@
|
||||||
'webm_webvtt_parser.h'
|
'webm_webvtt_parser.h'
|
||||||
],
|
],
|
||||||
'dependencies': [
|
'dependencies': [
|
||||||
|
'../../../third_party/boringssl/boringssl.gyp:boringssl',
|
||||||
'../../../third_party/libwebm/libwebm.gyp:libwebm',
|
'../../../third_party/libwebm/libwebm.gyp:libwebm',
|
||||||
'../../base/media_base.gyp:base',
|
'../../base/media_base.gyp:base',
|
||||||
'../../filters/filters.gyp:filters'
|
'../../filters/filters.gyp:filters'
|
||||||
|
|
|
@ -182,17 +182,25 @@ int WebMMediaParser::ParseInfoAndTracks(const uint8_t* data, int size) {
|
||||||
int64_t duration_in_us = info_parser.duration() * timecode_scale_in_us;
|
int64_t duration_in_us = info_parser.duration() * timecode_scale_in_us;
|
||||||
|
|
||||||
std::vector<scoped_refptr<StreamInfo>> streams;
|
std::vector<scoped_refptr<StreamInfo>> streams;
|
||||||
scoped_refptr<AudioStreamInfo> audio_stream_info =
|
AudioCodec audio_codec = kCodecOpus;
|
||||||
tracks_parser.audio_stream_info();
|
if (tracks_parser.audio_stream_info()) {
|
||||||
streams.push_back(tracks_parser.audio_stream_info());
|
streams.push_back(tracks_parser.audio_stream_info());
|
||||||
streams.back()->set_duration(duration_in_us);
|
streams.back()->set_duration(duration_in_us);
|
||||||
if (streams.back()->is_encrypted())
|
if (streams.back()->is_encrypted())
|
||||||
OnEncryptedMediaInitData(tracks_parser.audio_encryption_key_id());
|
OnEncryptedMediaInitData(tracks_parser.audio_encryption_key_id());
|
||||||
|
audio_codec = tracks_parser.audio_stream_info()->codec();
|
||||||
|
} else {
|
||||||
|
VLOG(1) << "No audio track info found.";
|
||||||
|
}
|
||||||
|
|
||||||
streams.push_back(tracks_parser.video_stream_info());
|
if (tracks_parser.video_stream_info()) {
|
||||||
streams.back()->set_duration(duration_in_us);
|
streams.push_back(tracks_parser.video_stream_info());
|
||||||
if (streams.back()->is_encrypted())
|
streams.back()->set_duration(duration_in_us);
|
||||||
OnEncryptedMediaInitData(tracks_parser.video_encryption_key_id());
|
if (streams.back()->is_encrypted())
|
||||||
|
OnEncryptedMediaInitData(tracks_parser.video_encryption_key_id());
|
||||||
|
} else {
|
||||||
|
VLOG(1) << "No video track info found.";
|
||||||
|
}
|
||||||
|
|
||||||
init_cb_.Run(streams);
|
init_cb_.Run(streams);
|
||||||
|
|
||||||
|
@ -203,8 +211,7 @@ int WebMMediaParser::ParseInfoAndTracks(const uint8_t* data, int size) {
|
||||||
tracks_parser.GetVideoDefaultDuration(timecode_scale_in_us),
|
tracks_parser.GetVideoDefaultDuration(timecode_scale_in_us),
|
||||||
tracks_parser.text_tracks(), tracks_parser.ignored_tracks(),
|
tracks_parser.text_tracks(), tracks_parser.ignored_tracks(),
|
||||||
tracks_parser.audio_encryption_key_id(),
|
tracks_parser.audio_encryption_key_id(),
|
||||||
tracks_parser.video_encryption_key_id(), audio_stream_info->codec(),
|
tracks_parser.video_encryption_key_id(), audio_codec, new_sample_cb_));
|
||||||
new_sample_cb_));
|
|
||||||
|
|
||||||
return bytes_parsed;
|
return bytes_parsed;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
// 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/webm_muxer.h"
|
||||||
|
|
||||||
|
#include "packager/media/base/media_sample.h"
|
||||||
|
#include "packager/media/base/media_stream.h"
|
||||||
|
#include "packager/media/base/stream_info.h"
|
||||||
|
#include "packager/media/formats/webm/mkv_writer.h"
|
||||||
|
#include "packager/media/formats/webm/multi_segment_segmenter.h"
|
||||||
|
#include "packager/media/formats/webm/single_segment_segmenter.h"
|
||||||
|
#include "packager/media/formats/webm/two_pass_single_segment_segmenter.h"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
namespace webm {
|
||||||
|
|
||||||
|
WebMMuxer::WebMMuxer(const MuxerOptions& options) : Muxer(options) {}
|
||||||
|
WebMMuxer::~WebMMuxer() {}
|
||||||
|
|
||||||
|
Status WebMMuxer::Initialize() {
|
||||||
|
CHECK_EQ(streams().size(), 1U);
|
||||||
|
|
||||||
|
scoped_ptr<MkvWriter> writer(new MkvWriter);
|
||||||
|
Status status = writer->Open(options().output_file_name);
|
||||||
|
if (!status.ok())
|
||||||
|
return status;
|
||||||
|
|
||||||
|
if (!options().single_segment) {
|
||||||
|
segmenter_.reset(new MultiSegmentSegmenter(options()));
|
||||||
|
} else if (writer->Seekable()) {
|
||||||
|
segmenter_.reset(new SingleSegmentSegmenter(options()));
|
||||||
|
} else {
|
||||||
|
segmenter_.reset(new TwoPassSingleSegmentSegmenter(options()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Status initialized =
|
||||||
|
segmenter_->Initialize(writer.Pass(), streams()[0], progress_listener(),
|
||||||
|
muxer_listener(), encryption_key_source());
|
||||||
|
|
||||||
|
if (!initialized.ok())
|
||||||
|
return initialized;
|
||||||
|
|
||||||
|
FireOnMediaStartEvent();
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status WebMMuxer::Finalize() {
|
||||||
|
DCHECK(segmenter_);
|
||||||
|
Status segmenter_finalized = segmenter_->Finalize();
|
||||||
|
|
||||||
|
if (!segmenter_finalized.ok())
|
||||||
|
return segmenter_finalized;
|
||||||
|
|
||||||
|
FireOnMediaEndEvent();
|
||||||
|
LOG(INFO) << "WEBM file '" << options().output_file_name << "' finalized.";
|
||||||
|
return Status::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Status WebMMuxer::DoAddSample(const MediaStream* stream,
|
||||||
|
scoped_refptr<MediaSample> sample) {
|
||||||
|
DCHECK(segmenter_);
|
||||||
|
DCHECK(stream == streams()[0]);
|
||||||
|
return segmenter_->AddSample(sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebMMuxer::FireOnMediaStartEvent() {
|
||||||
|
if (!muxer_listener())
|
||||||
|
return;
|
||||||
|
|
||||||
|
DCHECK(!streams().empty()) << "Media started without a stream.";
|
||||||
|
|
||||||
|
const uint32_t timescale = streams().front()->info()->time_scale();
|
||||||
|
muxer_listener()->OnMediaStart(options(), *streams().front()->info(),
|
||||||
|
timescale, MuxerListener::kContainerWebM);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebMMuxer::FireOnMediaEndEvent() {
|
||||||
|
if (!muxer_listener())
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint32_t init_range_start = 0;
|
||||||
|
uint32_t init_range_end = 0;
|
||||||
|
const bool has_init_range =
|
||||||
|
segmenter_->GetInitRangeStartAndEnd(&init_range_start, &init_range_end);
|
||||||
|
|
||||||
|
uint32_t index_range_start = 0;
|
||||||
|
uint32_t index_range_end = 0;
|
||||||
|
const bool has_index_range = segmenter_->GetIndexRangeStartAndEnd(
|
||||||
|
&index_range_start, &index_range_end);
|
||||||
|
|
||||||
|
const float duration_seconds = segmenter_->GetDuration();
|
||||||
|
|
||||||
|
const int64_t file_size =
|
||||||
|
File::GetFileSize(options().output_file_name.c_str());
|
||||||
|
if (file_size <= 0) {
|
||||||
|
LOG(ERROR) << "Invalid file size: " << file_size;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
muxer_listener()->OnMediaEnd(has_init_range, init_range_start, init_range_end,
|
||||||
|
has_index_range, index_range_start,
|
||||||
|
index_range_end, duration_seconds, file_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace webm
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
|
@ -0,0 +1,44 @@
|
||||||
|
// 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_WEBM_MUXER_H_
|
||||||
|
#define MEDIA_FORMATS_WEBM_WEBM_MUXER_H_
|
||||||
|
|
||||||
|
#include "packager/media/base/muxer.h"
|
||||||
|
|
||||||
|
namespace edash_packager {
|
||||||
|
namespace media {
|
||||||
|
namespace webm {
|
||||||
|
|
||||||
|
class Segmenter;
|
||||||
|
|
||||||
|
/// Implements WebM Muxer.
|
||||||
|
class WebMMuxer : public Muxer {
|
||||||
|
public:
|
||||||
|
/// Create a WebMMuxer object from MuxerOptions.
|
||||||
|
explicit WebMMuxer(const MuxerOptions& options);
|
||||||
|
~WebMMuxer() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Muxer implementation overrides.
|
||||||
|
Status Initialize() override;
|
||||||
|
Status Finalize() override;
|
||||||
|
Status DoAddSample(const MediaStream* stream,
|
||||||
|
scoped_refptr<MediaSample> sample) override;
|
||||||
|
|
||||||
|
void FireOnMediaStartEvent();
|
||||||
|
void FireOnMediaEndEvent();
|
||||||
|
|
||||||
|
scoped_ptr<Segmenter> segmenter_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(WebMMuxer);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace webm
|
||||||
|
} // namespace media
|
||||||
|
} // namespace edash_packager
|
||||||
|
|
||||||
|
#endif // MEDIA_FORMATS_WEBM_WEBM_MUXER_H_
|
Loading…
Reference in New Issue