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:
Jacob Trimble 2015-10-28 10:23:08 -07:00 committed by Gerrit Code Review
parent 1cdce29343
commit 07378e806c
19 changed files with 1566 additions and 13 deletions

View File

@ -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) {

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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'

View File

@ -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();
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());
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.";
}
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());
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;
}

View File

@ -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

View File

@ -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_