blob: 70a3836dbb0c7d50e6bb97937aa751b26d3b4c00 [file] [log] [blame]
// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found
// in the file PATENTS. All contributing project authors may
// be found in the AUTHORS file in the root of the source tree.
#include "encoder/webm_mux.h"
#include <new>
#include <vector>
#include "glog/logging.h"
#include "libwebm/mkvmuxer.hpp"
#include "libwebm/webmids.hpp"
namespace {
const int kAutoAssignTrackNum = 0;
} // namespace
namespace webmlive {
template <typename T>
T milliseconds_to_timecode_ticks(T milliseconds) {
return milliseconds * LiveWebmMuxer::kTimecodeScale;
}
// Buffer object implementing libwebm's IMkvWriter interface.
class WebmMuxWriter : public mkvmuxer::IMkvWriter {
public:
enum {
kNotImplemented = -200,
kNotInitialized = -2,
kInvalidArg = -1,
kSuccess = 0,
};
WebmMuxWriter();
virtual ~WebmMuxWriter();
// Stores |ptr_buffer| and returns |kSuccess|.
int32 Init(LiveWebmMuxer::WriteBuffer* ptr_write_buffer,
const std::string& id);
// Accessors.
int64 bytes_written() const { return bytes_written_; }
int64 chunk_end() const { return chunk_end_; }
// Erases chunk from |ptr_write_buffer_|, resets |chunk_end_| to 0, and
// updates |bytes_buffered_|.
void EraseChunk();
// mkvmuxer::IMkvWriter methods
// Returns total bytes of data passed to |Write|.
virtual int64 Position() const { return bytes_written_; }
// Not seekable, return |kNotImplemented| on seek attempts.
virtual int32 Position(int64) { return kNotImplemented; } // NOLINT
// Always returns false: |WebmMuxWriter| is never seekable. Written data
// goes into a vector, and data is buffered only until a chunk is completed.
virtual bool Seekable() const { return false; }
// Writes |ptr_buffer| contents to |ptr_write_buffer_|.
virtual int32 Write(const void* ptr_buffer, uint32 buffer_length);
// Called by libwebm, and notifies writer of element start position.
virtual void ElementStartNotify(uint64 element_id, int64 position);
private:
int64 bytes_buffered_;
int64 bytes_written_;
int64 chunk_end_;
LiveWebmMuxer::WriteBuffer* ptr_write_buffer_;
std::string id_;
WEBMLIVE_DISALLOW_COPY_AND_ASSIGN(WebmMuxWriter);
};
WebmMuxWriter::WebmMuxWriter()
: bytes_buffered_(0),
bytes_written_(0),
chunk_end_(0),
ptr_write_buffer_(NULL) {
}
WebmMuxWriter::~WebmMuxWriter() {
}
int32 WebmMuxWriter::Init(LiveWebmMuxer::WriteBuffer* ptr_write_buffer,
const std::string& id) {
if (!ptr_write_buffer) {
LOG(ERROR) << "Cannot Init, NULL write buffer.";
return kInvalidArg;
}
ptr_write_buffer_ = ptr_write_buffer;
id_ = id;
return kSuccess;
}
void WebmMuxWriter::EraseChunk() {
if (ptr_write_buffer_) {
LiveWebmMuxer::WriteBuffer::iterator erase_end_pos =
ptr_write_buffer_->begin() + static_cast<int32>(chunk_end_);
ptr_write_buffer_->erase(ptr_write_buffer_->begin(), erase_end_pos);
bytes_buffered_ = ptr_write_buffer_->size();
chunk_end_ = 0;
}
}
int32 WebmMuxWriter::Write(const void* ptr_buffer, uint32 buffer_length) {
if (!ptr_write_buffer_) {
LOG(ERROR) << "Cannot Write, not Initialized.";
return kNotInitialized;
}
if (!ptr_buffer || !buffer_length) {
LOG(ERROR) << "returning kInvalidArg to libwebm: NULL/0 length buffer.";
return kInvalidArg;
}
const uint8* ptr_data = reinterpret_cast<const uint8*>(ptr_buffer);
ptr_write_buffer_->insert(ptr_write_buffer_->end(),
ptr_data,
ptr_data + buffer_length);
bytes_written_ += buffer_length;
bytes_buffered_ = ptr_write_buffer_->size();
return kSuccess;
}
void WebmMuxWriter::ElementStartNotify(uint64 element_id, int64 position) {
if (element_id == mkvmuxer::kMkvCluster) {
chunk_end_ = bytes_buffered_;
if (id_ == "video") {
LOG(INFO) << "video chunk_end_=" << chunk_end_<< " position=" << position;
}
}
}
///////////////////////////////////////////////////////////////////////////////
// LiveWebmMuxer
//
LiveWebmMuxer::LiveWebmMuxer()
: audio_track_num_(0),
video_track_num_(0),
muxer_time_(0),
chunks_read_(0) {
}
LiveWebmMuxer::~LiveWebmMuxer() {
}
int LiveWebmMuxer::Init(int32 cluster_duration_milliseconds,
const std::string& muxer_id) {
muxer_id_ = muxer_id;
// Construct and Init |WebmMuxWriter|-- it handles writes coming from libwebm.
ptr_writer_.reset(new (std::nothrow) WebmMuxWriter()); // NOLINT
if (!ptr_writer_) {
LOG(ERROR) << "cannot construct WebmWriteBuffer.";
return kNoMemory;
}
if (ptr_writer_->Init(&buffer_, muxer_id)) {
LOG(ERROR) << "cannot Init WebmWriteBuffer.";
return kMuxerError;
}
// Construct and Init |ptr_segment_|, then enable live mode.
ptr_segment_.reset(new (std::nothrow) mkvmuxer::Segment()); // NOLINT
if (!ptr_segment_) {
LOG(ERROR) << "cannot construct Segment.";
return kNoMemory;
}
if (!ptr_segment_->Init(ptr_writer_.get())) {
LOG(ERROR) << "cannot Init Segment.";
return kMuxerError;
}
ptr_segment_->set_mode(mkvmuxer::Segment::kLive);
if (cluster_duration_milliseconds > 0) {
const uint64 max_cluster_duration =
milliseconds_to_timecode_ticks(cluster_duration_milliseconds);
ptr_segment_->set_max_cluster_duration(max_cluster_duration);
}
// Set segment info fields.
using mkvmuxer::SegmentInfo;
SegmentInfo* const ptr_segment_info = ptr_segment_->GetSegmentInfo();
if (!ptr_segment_info) {
LOG(ERROR) << "Segment has no SegmentInfo.";
return kNoMemory;
}
ptr_segment_info->set_timecode_scale(kTimecodeScale);
// Set writing application name.
std::string app_name = kEncoderName;
app_name += " v";
app_name += kEncoderVersion;
ptr_segment_info->set_writing_app(app_name.c_str());
return kSuccess;
}
int LiveWebmMuxer::AddTrack(const AudioConfig& audio_config,
const VorbisCodecPrivate& codec_private) {
if (audio_track_num_ != 0) {
LOG(ERROR) << "Cannot add audio track: it already exists.";
return kAudioTrackAlreadyExists;
}
const VorbisCodecPrivate& vcp = codec_private;
// Perform minimal private data validation.
if (!vcp.ptr_ident || !vcp.ptr_comments || !vcp.ptr_setup) {
LOG(ERROR) << "Cannot add audio track: NULL private data contents.";
return kAudioPrivateDataInvalid;
}
if (vcp.ident_length > 255 || vcp.comments_length > 255) {
LOG(ERROR) << "Cannot add audio track: over maximum ident/comment length.";
return kAudioPrivateDataInvalid;
}
const int data_length =
vcp.ident_length + vcp.comments_length + vcp.setup_length;
// Calculate total bytes of storage required for the private data chunk.
// 1 byte to store header count (total headers - 1 = 2).
// 1 byte each for ident and comment length values.
// The length of setup data is implied by the total length.
const int header_length = 1 + 1 + 1 + data_length;
std::unique_ptr<uint8[]> private_data;
private_data.reset(new (std::nothrow) uint8[header_length]);
if (!private_data) {
LOG(ERROR) << "Cannot allocate private data block";
return kNoMemory;
}
uint8* ptr_private_data = private_data.get();
// Write header count. As above, number of headers - 1.
*ptr_private_data++ = 2;
// Write ident length, comment length.
*ptr_private_data++ = static_cast<uint8>(vcp.ident_length);
*ptr_private_data++ = static_cast<uint8>(vcp.comments_length);
// Write the data blocks.
memcpy(ptr_private_data, vcp.ptr_ident, vcp.ident_length);
ptr_private_data += vcp.ident_length;
memcpy(ptr_private_data, vcp.ptr_comments, vcp.comments_length);
ptr_private_data += vcp.comments_length;
memcpy(ptr_private_data, vcp.ptr_setup, vcp.setup_length);
audio_track_num_ = ptr_segment_->AddAudioTrack(audio_config.sample_rate,
audio_config.channels,
kAutoAssignTrackNum);
if (!audio_track_num_) {
LOG(ERROR) << "cannot AddAudioTrack on segment.";
return kVideoTrackError;
}
mkvmuxer::AudioTrack* const ptr_audio_track =
static_cast<mkvmuxer::AudioTrack*>(
ptr_segment_->GetTrackByNumber(audio_track_num_));
if (!ptr_audio_track) {
LOG(ERROR) << "Unable to access audio track.";
return kAudioTrackError;
}
if (!ptr_audio_track->SetCodecPrivate(private_data.get(), header_length)) {
LOG(ERROR) << "Unable to write audio track codec private data.";
return kAudioTrackError;
}
return kSuccess;
}
int LiveWebmMuxer::AddTrack(const VideoConfig& video_config) {
if (video_track_num_ != 0) {
LOG(ERROR) << "Cannot add video track: it already exists.";
return kVideoTrackAlreadyExists;
}
video_track_num_ = ptr_segment_->AddVideoTrack(video_config.width,
video_config.height,
kAutoAssignTrackNum);
if (!video_track_num_) {
LOG(ERROR) << "cannot AddVideoTrack on segment.";
return kVideoTrackError;
}
if (video_config.format != kVideoFormatVP8) {
mkvmuxer::VideoTrack* const video_track =
static_cast<mkvmuxer::VideoTrack*>(
ptr_segment_->GetTrackByNumber(video_track_num_));
if (!video_track) {
LOG(ERROR) << "cannot get video track to set codec.\n";
return kVideoTrackError;
}
video_track->set_codec_id(mkvmuxer::Tracks::kVp9CodecId);
}
return kSuccess;
}
int LiveWebmMuxer::Finalize() {
if (!ptr_segment_->Finalize()) {
LOG(ERROR) << "libwebm mkvmuxer Finalize failed.";
return kMuxerError;
}
if (buffer_.size() > 0) {
// When data is in |buffer_| after the |mkvmuxer::Segment::Finalize()|
// call, make the last chunk available to the user by forcing
// |ChunkReady()| to return true one final time. This last chunk will
// contain any data passed to |mkvmuxer::Segment::AddFrame()| since the
// last call to |WebmMuxWriter::ElementStartNotify()|.
ptr_writer_->ElementStartNotify(mkvmuxer::kMkvCluster,
ptr_writer_->bytes_written());
}
return kSuccess;
}
int LiveWebmMuxer::WriteVideoFrame(const VideoFrame& vpx_frame) {
if (video_track_num_ == 0) {
LOG(ERROR) << "Cannot WriteVideoFrame without a video track.";
return kNoVideoTrack;
}
if (!vpx_frame.buffer()) {
LOG(ERROR) << "cannot write empty frame.";
return kInvalidArg;
}
if (vpx_frame.format() != kVideoFormatVP8 &&
vpx_frame.format() != kVideoFormatVP9) {
LOG(ERROR) << "cannot write non-VPx frame.";
return kInvalidArg;
}
const int64 timecode = milliseconds_to_timecode_ticks(vpx_frame.timestamp());
if (!ptr_segment_->AddFrame(vpx_frame.buffer(),
vpx_frame.buffer_length(),
video_track_num_,
timecode,
vpx_frame.keyframe())) {
LOG(ERROR) << "AddFrame (video) failed.";
return kVideoWriteError;
}
muxer_time_ = vpx_frame.timestamp();
return kSuccess;
}
int LiveWebmMuxer::WriteAudioBuffer(const AudioBuffer& vorbis_buffer) {
if (audio_track_num_ == 0) {
LOG(ERROR) << "Cannot WriteAudioBuffer without an audio track.";
return kNoAudioTrack;
}
if (!vorbis_buffer.buffer()) {
LOG(ERROR) << "cannot write empty audio buffer.";
return kInvalidArg;
}
if (vorbis_buffer.config().format_tag != kAudioFormatVorbis) {
LOG(ERROR) << "cannot write non-Vorbis audio buffer.";
return kInvalidArg;
}
const int64 timecode =
milliseconds_to_timecode_ticks(vorbis_buffer.timestamp());
if (!ptr_segment_->AddFrame(vorbis_buffer.buffer(),
vorbis_buffer.buffer_length(),
audio_track_num_,
timecode,
true)) {
LOG(ERROR) << "AddFrame (audio) failed.";
return kAudioWriteError;
}
muxer_time_ = vorbis_buffer.timestamp();
return kSuccess;
}
// A chunk is ready when |WebmMuxWriter::chunk_length()| returns a value
// greater than 0.
bool LiveWebmMuxer::ChunkReady(int32* ptr_chunk_length) {
if (ptr_chunk_length) {
const int32 chunk_length = static_cast<int32>(ptr_writer_->chunk_end());
if (chunk_length > 0) {
*ptr_chunk_length = chunk_length;
return true;
}
}
return false;
}
// Copies the buffered chunk data into |ptr_buf|, erases it from |buffer_|, and
// calls |WebmMuxWriter::ResetChunkEnd()| to zero the chunk end position.
int LiveWebmMuxer::ReadChunk(int32 buffer_capacity, uint8* ptr_buf) {
if (!ptr_buf) {
LOG(ERROR) << "NULL buffer pointer.";
return kInvalidArg;
}
// Make sure there's a chunk ready.
int32 chunk_length = 0;
if (!ChunkReady(&chunk_length)) {
LOG(ERROR) << "No chunk ready.";
return kNoChunkReady;
}
// Confirm user buffer is of adequate size
if (buffer_capacity < chunk_length) {
LOG(ERROR) << "Not enough space for chunk.";
return kUserBufferTooSmall;
}
LOG(INFO) << "ReadChunk capacity=" << buffer_capacity
<< " length=" << chunk_length
<< " total buffered=" << buffer_.size();
// Copy chunk to user buffer, and erase it from |buffer_|.
memcpy(ptr_buf, &buffer_[0], chunk_length);
ptr_writer_->EraseChunk();
++chunks_read_;
return kSuccess;
}
} // namespace webmlive