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/stl_util.h"
|
||||
#include "packager/base/strings/string_split.h"
|
||||
#include "packager/base/strings/string_util.h"
|
||||
#include "packager/base/strings/stringprintf.h"
|
||||
#include "packager/base/threading/simple_thread.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/file/file.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/media_info.pb.h"
|
||||
#include "packager/mpd/base/mpd_builder.h"
|
||||
|
@ -181,6 +183,25 @@ bool StreamInfoToTextMediaInfo(const StreamDescriptor& stream_descriptor,
|
|||
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,
|
||||
const MuxerOptions& muxer_options,
|
||||
FakeClock* fake_clock,
|
||||
|
@ -261,7 +282,9 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
|||
}
|
||||
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 (key_source) {
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
#include "packager/media/file/file.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include "packager/base/logging.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;
|
||||
}
|
||||
|
||||
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 edash_packager
|
||||
|
|
|
@ -17,6 +17,7 @@ namespace edash_packager {
|
|||
namespace media {
|
||||
|
||||
extern const char* kLocalFilePrefix;
|
||||
const int64_t kWholeFile = -1;
|
||||
|
||||
/// Define an abstract file interface.
|
||||
class File {
|
||||
|
@ -110,6 +111,19 @@ class File {
|
|||
/// @return true on success, false otherwise.
|
||||
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:
|
||||
explicit File(const std::string& file_name) : file_name_(file_name) {}
|
||||
/// 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',
|
||||
'type': '<(component)',
|
||||
'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.h',
|
||||
'webm_cluster_parser.cc',
|
||||
|
@ -31,6 +43,8 @@
|
|||
'webm_parser.h',
|
||||
'webm_media_parser.cc',
|
||||
'webm_media_parser.h',
|
||||
'webm_muxer.cc',
|
||||
'webm_muxer.h',
|
||||
'webm_tracks_parser.cc',
|
||||
'webm_tracks_parser.h',
|
||||
'webm_video_client.cc',
|
||||
|
@ -39,6 +53,7 @@
|
|||
'webm_webvtt_parser.h'
|
||||
],
|
||||
'dependencies': [
|
||||
'../../../third_party/boringssl/boringssl.gyp:boringssl',
|
||||
'../../../third_party/libwebm/libwebm.gyp:libwebm',
|
||||
'../../base/media_base.gyp:base',
|
||||
'../../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;
|
||||
|
||||
std::vector<scoped_refptr<StreamInfo>> streams;
|
||||
scoped_refptr<AudioStreamInfo> audio_stream_info =
|
||||
tracks_parser.audio_stream_info();
|
||||
AudioCodec audio_codec = kCodecOpus;
|
||||
if (tracks_parser.audio_stream_info()) {
|
||||
streams.push_back(tracks_parser.audio_stream_info());
|
||||
streams.back()->set_duration(duration_in_us);
|
||||
if (streams.back()->is_encrypted())
|
||||
OnEncryptedMediaInitData(tracks_parser.audio_encryption_key_id());
|
||||
audio_codec = tracks_parser.audio_stream_info()->codec();
|
||||
} else {
|
||||
VLOG(1) << "No audio track info found.";
|
||||
}
|
||||
|
||||
if (tracks_parser.video_stream_info()) {
|
||||
streams.push_back(tracks_parser.video_stream_info());
|
||||
streams.back()->set_duration(duration_in_us);
|
||||
if (streams.back()->is_encrypted())
|
||||
OnEncryptedMediaInitData(tracks_parser.video_encryption_key_id());
|
||||
} else {
|
||||
VLOG(1) << "No video track info found.";
|
||||
}
|
||||
|
||||
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.text_tracks(), tracks_parser.ignored_tracks(),
|
||||
tracks_parser.audio_encryption_key_id(),
|
||||
tracks_parser.video_encryption_key_id(), audio_stream_info->codec(),
|
||||
new_sample_cb_));
|
||||
tracks_parser.video_encryption_key_id(), audio_codec, new_sample_cb_));
|
||||
|
||||
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