blob: 14e14d58f576316ba23bdcd35a100fcd98c1c81f [file]
// Copyright (c) 2011 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 <cassert>
#include <ctime>
#include <sstream>
#include <strmif.h>
#include <comdef.h>
#include "comreg.h"
#include "scratchbuf.h"
#include "versionhandling.h"
#include "webmconstants.h"
#include "webmmuxcontext.h"
#ifdef _DEBUG
#include "odbgstream.h"
using std::endl;
#endif
using std::wstring;
using std::wostringstream;
enum { kAudioClusterSizeInTimeMs = 5000 }; //TODO: parameterize this
namespace WebmMuxLib
{
extern HMODULE s_hModule;
Context::Context() :
m_bLiveMux(false),
m_bBufferData(false),
m_pVideo(0),
m_pAudio(0),
m_timecode_scale(1000000), //TODO
m_info_pos(0),
m_seekhead_pos(0),
m_segment_pos(0)
{
//Seed the random number generator, which is needed
//for creation of unique TrackUIDs.
const time_t time_ = time(0);
const unsigned seed = static_cast<unsigned>(time_);
srand(seed);
}
Context::~Context()
{
assert(m_pVideo == 0);
assert(m_pAudio == 0);
assert(m_file.GetStream() == 0);
}
void Context::SetVideoStream(StreamVideo* pVideo)
{
assert((pVideo == 0) || (m_pVideo == 0));
assert((pVideo == 0) || (pVideo->GetFrames().empty()));
assert((pVideo == 0) || (pVideo->GetKeyFrames().empty()));
assert(m_clusters.empty());
m_pVideo = pVideo;
}
void Context::SetAudioStream(StreamAudio* pAudio)
{
assert((pAudio == 0) || (m_pAudio == 0));
assert((pAudio == 0) || (pAudio->GetFrames().empty()));
assert(m_clusters.empty());
m_pAudio = pAudio; //TODO: generlize this to many audio streams
}
void Context::Open(IStream* pStream)
{
assert(m_file.GetStream() == 0);
assert((m_pVideo == 0) || (m_pVideo->GetFrames().empty()));
assert((m_pVideo == 0) || (m_pVideo->GetKeyFrames().empty()));
assert((m_pAudio == 0) || (m_pAudio->GetFrames().empty()));
assert(m_clusters.empty());
m_max_timecode = 0; //to keep track of duration
m_cEOS = 0;
m_bEOSVideo = false; //means we haven't seen EOS yet (from either
m_bEOSAudio = false; //the stream itself, or because of stop)
int tn = 0;
if (m_pVideo)
{
m_pVideo->SetTrackNumber(++tn);
++m_cEOS;
}
if (m_pAudio)
{
m_pAudio->SetTrackNumber(++tn);
++m_cEOS;
}
if (pStream)
{
m_file.SetStream(pStream);
#if 0 //TODO: parameterize this (with default of 0)
const __int64 One_GB = 1024i64 * 1024i64 * 1024i64;
__int64 size = 8 * One_GB;
while (size > 0)
{
const HRESULT hr = m_file.SetSize(size);
if (SUCCEEDED(hr))
break;
size /= 2;
}
#endif
ResetBuffer();
if (!m_bLiveMux)
m_file.SetPosition(0);
else
assert(m_file.GetPosition() == 0);
WriteEbmlHeader();
InitSegment();
}
}
void Context::Close()
{
if (m_pVideo)
NotifyVideoEOS(0);
if (m_pAudio)
NotifyAudioEOS(0);
Final();
}
void Context::Final()
{
if (m_file.GetStream())
{
if (m_pVideo)
m_pVideo->Final(); //grant last wishes
if (m_pAudio)
m_pAudio->Final(); //grant last wishes
FinalSegment();
m_file.SetStream(0);
}
assert(m_clusters.empty());
assert((m_pVideo == 0) || (m_pVideo->GetFrames().empty()));
assert((m_pVideo == 0) || (m_pVideo->GetKeyFrames().empty()));
assert((m_pAudio == 0) || (m_pAudio->GetFrames().empty()));
}
ULONG Context::GetTimecodeScale() const
{
return m_timecode_scale;
}
ULONG Context::GetTimecode() const
{
return m_max_timecode;
}
void Context::WriteEbmlHeader()
{
m_buf.WriteID4(WebmUtil::kEbmlID);
// store the header size offset for later correction
const uint64 offset_header_size = m_buf.GetBufferLength();
m_buf.Write1UInt(0xF); // temp size
// We must exclude |num_bytes_to_ignore| from the size we obtain from
// |m_buf|. The value from |m_buf| includes the bytes storing
// kEbmlID and the size written into the buffer-- using it would result in
// an invalid WebM file.
const uint64 num_bytes_to_ignore = 4 + sizeof(uint8);
// EBML Version
m_buf.WriteID2(WebmUtil::kEbmlVersionID);
m_buf.Write1UInt(1); // element size
m_buf.Serialize1UInt(1); // EBML Version = 1
// EBML Read Version
m_buf.WriteID2(WebmUtil::kEbmlReadVersionID);
m_buf.Write1UInt(1); // element size
m_buf.Serialize1UInt(1); // EBML Read Version = 1
// EBML Max ID Length
m_buf.WriteID2(WebmUtil::kEbmlMaxIDLengthID);
m_buf.Write1UInt(1); // element size
m_buf.Serialize1UInt(4); // EBML Max ID Length = 4
// EBML Max Size Length
m_buf.WriteID2(WebmUtil::kEbmlMaxSizeLengthID);
m_buf.Write1UInt(1); // element size
m_buf.Serialize1UInt(8); // EBML Max Size Length = 8
// Doc Type
m_buf.WriteID2(WebmUtil::kEbmlDocTypeID);
m_buf.Write1String("webm");
// Pad our doc type (so it can be changed to matroska easily)
m_buf.WriteID1(WebmUtil::kEbmlVoidID);
const int32 void_element_size = 9;
m_buf.Write1UInt(void_element_size);
for (int32 i = 0; i < void_element_size; ++i)
{
m_buf.Serialize1UInt(0);
}
// Doc Type Version
m_buf.WriteID2(WebmUtil::kEbmlDocTypeVersionID);
m_buf.Write1UInt(1); // element size
m_buf.Serialize1UInt(2); // Doc Type Version
// Doc Type Read Version
m_buf.WriteID2(WebmUtil::kEbmlDocTypeReadVersionID);
m_buf.Write1UInt(1); // element size
m_buf.Serialize1UInt(2); // Doc Type Read Version
// patch in the actual length of the EBML header
const uint64 actual_header_length =
m_buf.GetBufferLength() - num_bytes_to_ignore;
assert(actual_header_length < 255);
m_buf.RewriteUInt(offset_header_size, actual_header_length, 1);
// dump |m_buf| into the out file
m_file.Write(m_buf.GetBufferPtr(),
static_cast<ULONG>(m_buf.GetBufferLength()));
m_buf.Reset();
}
void Context::InitSegment()
{
m_file.WriteID4(WebmUtil::kEbmlSegmentID); //Segment ID
if (m_bLiveMux)
m_file.Serialize1UInt(0xFF);
else
{
m_segment_pos = m_file.GetPosition() - 4;
m_file.Serialize8UInt(0x01FFFFFFFFFFFFFFLL);
if (m_pVideo)
InitSeekHead(); //Meta Seek
}
InitInfo(); //Segment Info
WriteTrack();
}
void Context::FinalSegment()
{
if (!m_bLiveMux)
{
m_cues_pos = m_file.GetPosition(); //end of clusters
if (m_pVideo)
WriteCues();
const __int64 maxpos = m_file.GetPosition();
m_file.SetSize(maxpos);
const __int64 size = maxpos - m_segment_pos - 12;
assert(size >= 0);
m_file.SetPosition(m_segment_pos);
const ULONG id = m_file.ReadID4();
assert(id == WebmUtil::kEbmlSegmentID);
id;
m_file.Write8UInt(size); //total size of the segment
if (m_pVideo)
FinalSeekHead();
FinalInfo();
}
m_clusters.clear();
}
void Context::FinalInfo()
{
m_file.SetPosition(m_duration_pos);
m_file.WriteID2(WebmUtil::kEbmlDurationID); // Duration ID
m_file.Write1UInt(4); // payload size
const float duration = static_cast<float>(m_max_timecode);
m_file.Serialize4Float(duration);
}
void Context::InitSeekHead()
{
m_seekhead_pos = m_file.GetPosition();
// The SeekID is 2 + 1 + 4 = 7 bytes.
// The SeekPos is 2 + 1 + 8 = 11 bytes.
// Total payload for a seek entry is 7 + 11 = 18 bytes.
// The Seek entry is 2 + 1 + 18 = 21 bytes.
// What we write here:
// (1) SegmentInfo
// (2) Track
// (3) Cues
//
// Possible extra entries, by other tools:
// (4) XMP
// (5) MKV tags
// (6) another post-clusters SeekHead
//
// So just allocate space for 10 entries; that's 10*21 bytes for
// the payload that carries SeekHead entries.
//
// We also write a void element, inside the SeekHead element, because
// we're writing fewer entries than we have budgeted for. A Void element
// has a 1-byte ID and a 2-byte size field, so that's 3 additional bytes
// of payload for the SeekHead.
//
// The SeekHead ID is 4 bytes, with a 2-byte size field. So that's:
// 6 (4-byte ID + 2-byte size) +
// 10*21 (max 10 seek entries) +
// 3 (overhead for Void element, for unused payload)
// The total payload size during finalization will be 10*21 + 3 bytes.
//
// However, we only write the SeekHead ID when we finalize the container,
// after the muxing is complete. We write a Void element during
// initialization:
// 3 (1-byte ID + 2-byte size) +
// 3 (padding, to account for difference between ID sizes)
// 10*21 (as above)
// 3 (as above)
// The total payload size during initialization is 3 + 10*21 + 3 bytes.
const USHORT init_size = 3 + 10*21 + 3;
m_file.WriteID1(WebmUtil::kEbmlVoidID);
m_file.Write2UInt(init_size);
m_file.SetPosition(init_size, STREAM_SEEK_CUR);
}
void Context::FinalSeekHead()
{
const LONGLONG start_pos = m_file.SetPosition(m_seekhead_pos);
const USHORT final_size = 10*21 + 3; //see notes above
m_file.WriteID4(WebmUtil::kEbmlSeekHeadID);
m_file.Write2UInt(final_size);
WriteSeekEntry(WebmUtil::kEbmlSegmentInfoID, m_info_pos);
WriteSeekEntry(WebmUtil::kEbmlTracksID, m_track_pos);
WriteSeekEntry(WebmUtil::kEbmlCuesID, m_cues_pos);
const USHORT void_size = 7*21; //because we only wrote 3 actual entries
m_file.WriteID1(WebmUtil::kEbmlVoidID);
m_file.Write2UInt(void_size);
m_file.SetPosition(void_size, STREAM_SEEK_CUR);
assert((m_file.GetPosition() - start_pos) == (4 + 2 + final_size));
}
void Context::WriteSeekEntry(ULONG id, __int64 pos_)
{
assert(!m_bLiveMux);
//The SeekID is 2 + 1 + 4 = 7 bytes.
//The SeekPos is 2 + 1 + 8 = 11 bytes.
//Total payload for a seek entry is 7 + 11 = 18 bytes.
//The Seek entry is 2 + 1 + 18 = 21 bytes.
//Total payload for SeekHead is 20000 * 21 = 420000 bytes.
#ifdef _DEBUG
const __int64 start_pos = m_file.GetPosition();
#endif
m_file.WriteID2(WebmUtil::kEbmlSeekEntryID);
m_file.Write1UInt(18); // payload size
m_file.WriteID2(WebmUtil::kEbmlSeekIDID);
m_file.Write1UInt(4); // payload size is 4 bytes
m_file.WriteID4(id);
const __int64 pos = pos_ - m_segment_pos - 12;
assert(pos >= 0);
m_file.WriteID2(WebmUtil::kEbmlSeekPositionID);
m_file.Write1UInt(8); // payload size is 8 bytes
m_file.Serialize8UInt(pos); // payload
#ifdef _DEBUG
const __int64 stop_pos = m_file.GetPosition();
assert((stop_pos - start_pos) == 21);
#endif
}
void Context::InitInfo()
{
if (!m_bLiveMux)
m_info_pos = m_file.GetPosition();
m_buf.WriteID4(WebmUtil::kEbmlSegmentInfoID);
const uint64 size_pos = m_buf.GetBufferLength();
// pad with 0; we're going to patch the size into the buffer
m_buf.Serialize2UInt(0);
// We must exclude |num_bytes_to_ignore| from the size we obtain from
// |m_buf|. The value from |m_buf| includes the bytes storing
// kEbmlSegmentInfoID and the size written into the buffer-- using it
// would result in invalid segment info.
const uint64 num_bytes_to_ignore = 4 + sizeof(uint16);
m_buf.WriteID3(WebmUtil::kEbmlTimeCodeScaleID);
m_buf.Write1UInt(4); // payload size
m_buf.Serialize4UInt(m_timecode_scale);
if (!m_bLiveMux)
{
// remember where duration is
m_duration_pos = m_file.GetPosition() + m_buf.GetBufferLength();
m_buf.WriteID1(WebmUtil::kEbmlVoidID);
m_buf.Write1UInt(5); // (Duration ID - Void ID) + payload
// reserve space in the buffer
for (int32 i = 0; i < 5; ++i)
{
m_buf.Serialize1UInt(0xF);
}
}
// MuxingApp
m_buf.WriteID2(WebmUtil::kEbmlMuxingAppID);
wstring fname;
ComReg::ComRegGetModuleFileName(s_hModule, fname);
wostringstream os;
os << L"webmmux-";
VersionHandling::GetVersion(fname.c_str(), os);
m_buf.Write1UTF8(os.str().c_str()); // writes both EBML size and payload
// WritingApp
if (!m_writing_app.empty())
{
m_buf.WriteID2(WebmUtil::kEbmlWritingAppID);
m_buf.Write1UTF8(m_writing_app.c_str());
}
const uint64 actual_seginfo_len =
m_buf.GetBufferLength() - num_bytes_to_ignore;
m_buf.RewriteUInt(size_pos, actual_seginfo_len, sizeof(uint16));
m_file.Write(m_buf.GetBufferPtr(),
static_cast<ULONG>(m_buf.GetBufferLength()));
m_buf.Reset();
}
void Context::WriteTrack()
{
if (!m_bLiveMux)
m_track_pos = m_file.GetPosition();
assert(m_bBufferData == false);
m_buf.WriteID4(WebmUtil::kEbmlTracksID);
// store tracks element offset for patching later
const uint64 track_len_offset = m_buf.GetBufferLength();
// We must exclude |num_bytes_to_ignore| from the size we obtain from
// |m_buf|. The value from |m_buf| includes the bytes storing
// kEbmlTracksID and the size written into the buffer-- using it
// would result in invalid segment info.
const uint64 num_bytes_to_ignore = 4 + sizeof(uint16);
// reserve two bytes for the size of the tracks element
m_buf.Serialize2UInt(0);
int track_num = 0;
if (m_pVideo)
m_pVideo->WriteTrackEntry(++track_num);
if (m_pAudio) //TODO: allow for multiple audio streams
m_pAudio->WriteTrackEntry(++track_num);
if (m_bBufferData)
{
// Buffering was enabled by one of the tracks while writing the entry.
// Currently this should only happen with Vorbis audio from the
// Xiph.org directshow filter.
m_buf_element_info.byte_count = sizeof(uint16);
m_buf_element_info.num_bytes_to_ignore = num_bytes_to_ignore;
m_buf_element_info.offset = track_len_offset;
}
else
{
uint64 actual_tracks_len =
m_buf.GetBufferLength() - num_bytes_to_ignore;
m_buf.RewriteUInt(track_len_offset, actual_tracks_len, sizeof(uint16));
m_file.Write(m_buf.GetBufferPtr(),
static_cast<ULONG>(m_buf.GetBufferLength()));
m_buf.Reset();
}
}
#if 0
void Context::Cluster::Final(
Context& ctx,
ULONG& prev,
const Cluster& next)
{
const __int64 size_ = next.m_pos - m_pos;
assert(size_ >= 8);
assert(size_ <= ULONG_MAX);
const ULONG new_prev = static_cast<ULONG>(size_);
const ULONG size = new_prev - 8;
EbmlIO::File& f = ctx.m_file;
f.SetPosition(m_pos);
const ULONG id = f.ReadID4();
assert(id == 0x1F43B675);
id;
f.Write4UInt(size);
prev = new_prev;
}
#endif
void Context::WriteCuePoints(const Cluster& c)
{
//cue point container = 1 + size len(2) + payload len
// time = 1 + size len(1) + payload len(4)
// track posns container = 1 + size len + payload len
// track = 1 + size len + payload len (track number val)
// cluster pos = 1 + size len + payload len (pos val)
// block num = 2 + size len + payload len (block num val)
//TODO: for now just write video keyframes
//Do we even need audio here?
//We would need something, if this is an audio-only mux.
//TODO: write this at beginning of file
const Cluster::keyframes_t& kk = c.m_keyframes;
assert(kk.size() <= 255);
typedef Cluster::keyframes_t::const_iterator iter_t;
iter_t i = kk.begin();
const iter_t j = kk.end();
while (i != j)
{
const Keyframe& k = *i++;
WriteCuePoint(c, k);
}
}
Context::Keyframe::Keyframe() :
m_timecode(0),
m_block(0)
{
}
Context::Keyframe::Keyframe(
ULONG t,
ULONG n) :
m_timecode(t),
m_block(n)
{
}
void Context::WriteCuePoint(
const Cluster& c,
const Keyframe& k)
{
assert(m_pVideo);
EbmlIO::File& f = m_file;
f.WriteID1(0xBB); //CuePoint ID
f.Write1UInt(28); //payload size
#ifdef _DEBUG
const __int64 start_pos = f.GetPosition();
#endif
f.WriteID1(0xB3); //CueTime ID
f.Write1UInt(4); //payload len is 4
f.Serialize4UInt(k.m_timecode); //payload
f.WriteID1(0xB7); //CueTrackPositions
f.Write1UInt(20); //payload size
#ifdef _DEBUG
const __int64 start_track_pos = f.GetPosition();
#endif
const int tn_ = m_pVideo->GetTrackNumber();
assert(tn_ > 0);
assert(tn_ <= 255);
const BYTE tn = static_cast<BYTE>(tn_);
f.WriteID1(0xF7); //CueTrack ID
f.Write1UInt(1); //payload size is 1 byte
f.Serialize1UInt(tn); //payload
const __int64 off = c.m_pos - m_segment_pos - 12;
assert(off >= 0);
f.WriteID1(0xF1); //CueClusterPosition ID
f.Write1UInt(8); //payload size is 8 bytes
f.Serialize8UInt(off); //payload
//TODO: Keyframe::m_block_number is a 4-byte
//number, and we serialize all 4 bytes. However,
//it's unlikely we'll have block numbers that large
//(because we create a new cluster every second).
//Right now we always decide statically how many
//bytes to serialize (we're using 4 bytes of storage,
//so we serialize all 4 bytes, irrespective of the
//value at run-time), but in the future we
//could decide to check at run-time how large a value
//we have, and then only serialize the minimum number
//of bytes required for that value.
f.WriteID2(0x5378); //CueBlockNumber
f.Write1UInt(4); //payload size
f.Serialize4UInt(k.m_block); //payload //TODO: don't need 4 bytes
#ifdef _DEBUG
const __int64 stop_pos = f.GetPosition();
assert((stop_pos - start_track_pos) == 20);
assert((stop_pos - start_pos) == 28);
#endif
}
void Context::WriteCues()
{
m_file.WriteID4(0x1C53BB6B); //Cues ID
//allocate 4 bytes of storage for size of cues element
const __int64 start_pos = m_file.SetPosition(4, STREAM_SEEK_CUR);
typedef clusters_t::const_iterator iter_t;
iter_t i = m_clusters.begin();
const iter_t j = m_clusters.end();
while (i != j)
{
const Cluster& c = *i++;
WriteCuePoints(c);
}
const __int64 stop_pos = m_file.GetPosition();
const __int64 size_ = stop_pos - start_pos;
assert(size_ <= ULONG_MAX);
const ULONG size = static_cast<ULONG>(size_);
m_file.SetPosition(start_pos - 4);
m_file.Write4UInt(size);
m_file.SetPosition(stop_pos);
}
void Context::NotifyVideoFrame(
StreamVideo* pVideo,
StreamVideo::VideoFrame* pFrame)
{
pVideo;
assert(pVideo);
assert(pVideo == m_pVideo);
assert(pFrame);
assert(m_file.GetStream()); //TODO
StreamVideo::frames_t& vframes = pVideo->GetFrames();
assert(!vframes.empty());
assert(vframes.back() == pFrame);
const ULONG vt = pFrame->GetTimecode();
StreamVideo::frames_t& rframes = m_pVideo->GetKeyFrames();
if (rframes.empty())
{
rframes.push_back(pFrame);
return;
}
if (pFrame->IsKey())
rframes.push_back(pFrame);
else if (m_bLiveMux)
{
#if 0 //def _DEBUG
odbgstream os;
os << "["__FUNCTION__"] " << "rframes.size()=" << rframes.size()
<< " vframes.size()=" << vframes.size()<< endl;
#endif
return;
}
else
{
const StreamVideo::VideoFrame* const pvf0 = rframes.back();
assert(pvf0);
const ULONG vt0 = pvf0->GetTimecode();
assert(vt >= vt0);
const LONGLONG dt = LONGLONG(vt) - LONGLONG(vt0);
assert(dt >= 0);
const LONGLONG scale = GetTimecodeScale();
assert(scale >= 1);
const LONGLONG ns = scale * dt;
//TODO: allow this to be parameterized
if (ns <= 1000000000) //1 sec
return;
rframes.push_back(pFrame);
}
//At this point, we have at least 2 rframes, which means
//at least one cluster is potentially available to be written
//to the file. (Here the constraints that the video stream
//needs to satisfy have been satisified. We might still have
//to wait for the audio stream to satisfy its constraints.)
if ((m_pAudio == 0) || m_bEOSAudio)
{
CreateNewCluster(pFrame);
return;
}
const StreamAudio::frames_t& aframes = m_pAudio->GetFrames();
if (aframes.empty())
return;
const StreamAudio::AudioFrame* const paf = aframes.back();
assert(paf);
const ULONG at = paf->GetTimecode();
if (at < vt)
return;
CreateNewCluster(pFrame);
}
void Context::NotifyAudioFrame(
StreamAudio* pAudio,
StreamAudio::AudioFrame* pFrame)
{
pAudio;
assert(pAudio);
assert(pAudio == m_pAudio);
assert(pFrame);
assert(m_file.GetStream());
StreamAudio::frames_t& aframes = pAudio->GetFrames();
aframes.push_back(pFrame);
const ULONG at = pFrame->GetTimecode();
if ((m_pVideo == 0) || (m_pVideo->GetFrames().empty() && m_bEOSVideo))
{
const StreamAudio::AudioFrame* const paf = aframes.front();
assert(paf);
const ULONG at0 = paf->GetTimecode();
assert(at >= at0);
const LONG dt = LONG(at) - LONG(at0);
//TODO: THIS ASSUMES TIMECODE HAS MS RESOLUTION!
//THIS IS WRONG AND NEEDS TO BE FIXED
if (dt >= kAudioClusterSizeInTimeMs)
CreateNewClusterAudioOnly();
return;
}
StreamVideo::frames_t& vframes = m_pVideo->GetFrames();
if (vframes.empty())
return;
if (m_bEOSVideo)
{
const LONG vt_ = m_pVideo->GetLastTimecode();
assert(vt_ >= 0);
const ULONG vt = static_cast<ULONG>(vt_);
if (at >= vt)
CreateNewCluster(0); //NULL means deque all video
return; //not enough audio yet
}
StreamVideo::frames_t& rframes = m_pVideo->GetKeyFrames();
if (rframes.empty())
return;
typedef StreamVideo::frames_t::const_iterator iter_t;
iter_t i = rframes.begin();
const iter_t j = rframes.end();
const StreamVideo::VideoFrame* const pvf0 = *i++; //1st rframe
assert(pvf0);
const ULONG vt0 = pvf0->GetTimecode(); //1st rframe
vt0;
if (i == j) //only one rframe in queue
return;
const StreamVideo::VideoFrame* const pvf = *i; //2nd rframe
assert(pvf);
const ULONG vt = pvf->GetTimecode(); //2nd rframe
assert(vt >= vt0);
if (at < vt)
return; //not enough audio
CreateNewCluster(pvf); //2nd rframe
}
bool Context::WaitVideo() const
{
if (m_file.GetStream() == 0)
return false;
if (m_pVideo == 0)
return false;
if (m_bEOSVideo)
return false;
if (m_pAudio == 0)
return false;
if (m_bEOSAudio)
return false;
StreamVideo::frames_t& rframes = m_pVideo->GetKeyFrames();
if (rframes.size() <= 1)
return false;
const StreamVideo::VideoFrame* const pvf0 = rframes.front();
assert(pvf0);
const StreamVideo::VideoFrame* const pvf = rframes.back();
assert(pvf);
const ULONG vt0 = pvf0->GetTimecode();
const ULONG vt = pvf->GetTimecode();
const LONG dt = LONG(vt) - LONG(vt0);
assert(dt >= 0);
//TODO: THIS ASSUMES TIMECODE HAS MS RESOLUTION!
//THIS IS WRONG AND NEEDS TO BE FIXED
if (dt < 1000)
return false;
const StreamAudio::frames_t& aframes = m_pAudio->GetFrames();
if (aframes.empty())
return true;
const StreamAudio::AudioFrame* const paf = aframes.back();
assert(paf);
const ULONG at = paf->GetTimecode();
if (vt <= at)
return false;
//TODO: THIS ASSUMES TIMECODE HAS MS RESOLUTION!
//THIS IS WRONG AND NEEDS TO BE FIXED
if ((vt - at) <= 1000)
return false;
return true;
}
bool Context::WaitAudio() const
{
if (m_file.GetStream() == 0)
return false;
if (m_pAudio == 0)
return false;
if (m_bEOSAudio)
return false;
if (m_pVideo == 0)
return false;
if (m_bEOSVideo)
return false;
const StreamAudio::frames_t& aframes = m_pAudio->GetFrames();
if (aframes.empty())
return false;
const StreamVideo::frames_t& rframes = m_pVideo->GetKeyFrames();
if (rframes.empty())
return true; //wait for some video
const StreamAudio::AudioFrame* const paf = aframes.back();
assert(paf);
const ULONG at = paf->GetTimecode();
const StreamVideo::VideoFrame* const pvf = rframes.back();
assert(pvf);
const ULONG vt0 = pvf->GetTimecode();
if (at <= vt0)
return false;
const LONG vt_ = m_pVideo->GetLastTimecode();
assert(vt_ >= 0);
const ULONG vt = static_cast<ULONG>(vt_);
assert(vt >= vt0);
if (at <= vt)
return false;
//TODO: THIS ASSUMES TIMECODE HAS MS RESOLUTION!
//THIS IS WRONG AND NEEDS TO BE FIXED
if ((at - vt) <= 1000)
return false;
return true;
}
int Context::NotifyVideoEOS(StreamVideo* pSource)
{
if (m_bEOSVideo)
return 0;
#if 0
odbgstream os;
os << "mux::eosvideo" << endl;
#endif
m_bEOSVideo = true;
if (m_file.GetStream() == 0)
__noop;
else if ((m_pAudio == 0) || m_bEOSAudio)
{
for (;;)
{
if ((m_pVideo != 0) && !m_pVideo->GetFrames().empty())
CreateNewCluster(0);
else if ((m_pAudio != 0) && !m_pAudio->GetFrames().empty())
CreateNewClusterAudioOnly();
else
break;
}
}
return EOS(pSource);
}
int Context::NotifyAudioEOS(StreamAudio* pSource)
{
if (m_bEOSAudio)
return 0;
#if 0
odbgstream os;
os << "mux::eosaudio" << endl;
#endif
m_bEOSAudio = true;
if (m_file.GetStream() == 0)
__noop;
else if ((m_pVideo == 0) || m_bEOSVideo)
{
for (;;)
{
if ((m_pVideo != 0) && !m_pVideo->GetFrames().empty())
CreateNewCluster(0);
else if ((m_pAudio != 0) && !m_pAudio->GetFrames().empty())
CreateNewClusterAudioOnly();
else
break;
}
}
return EOS(pSource);
}
int Context::EOS(Stream*)
{
assert(m_cEOS > 0);
--m_cEOS;
if (m_cEOS > 0)
return 0;
Final();
return 1; //signal done
}
void Context::CreateNewCluster(const StreamVideo::VideoFrame* pvf_stop)
{
#if 0
odbgstream os;
os << "\nCreateNewCluster: pvf_stop=";
if (pvf_stop == 0)
os << "NULL";
else
os << pvf_stop->GetTimecode();
os << endl;
#endif
assert(m_bBufferData == false);
assert(m_pVideo);
const StreamVideo::frames_t& vframes = m_pVideo->GetFrames();
assert(!vframes.empty());
clusters_t& cc = m_clusters;
//const Cluster* const pPrevCluster = cc.empty() ? 0 : &cc.back();
cc.push_back(Cluster());
Cluster& c = cc.back();
c.m_pos = m_file.GetPosition();
{
const StreamVideo::VideoFrame* const pvf = vframes.front();
assert(pvf);
assert(pvf != pvf_stop);
const ULONG vt = pvf->GetTimecode();
if ((m_pAudio == 0) || m_pAudio->GetFrames().empty())
c.m_timecode = vt;
else
{
const StreamAudio::frames_t& aframes = m_pAudio->GetFrames();
const StreamAudio::AudioFrame* const paf = aframes.front();
const ULONG at = paf->GetTimecode();
c.m_timecode = (at <= vt) ? at : vt;
}
}
// Write cluster header
m_file.WriteID4(WebmUtil::kEbmlClusterID);
if (!m_bLiveMux)
{
// Use a 8-byte cluster header
m_file.Serialize4UInt(0x1FFFFFFF); // temp cluster size; rewritten
// below if |m_bLiveMux| is false
}
else
{
// Use a 5-byte cluster header
m_file.Serialize1UInt(0xFF);
}
m_file.WriteID1(WebmUtil::kEbmlTimeCodeID);
BYTE timecode_size;
if (!m_bLiveMux)
{
timecode_size = m_file.GetSerializeUIntSize(c.m_timecode);
assert(timecode_size <= 8);
}
else
{
// To facilitate easy rewriting of timecodes, always write 8 byte
// timecodes in live mux mode.
timecode_size = 8;
}
m_file.Write1UInt(timecode_size);
m_file.SerializeUInt(c.m_timecode, timecode_size);
const __int64 off = c.m_pos - m_segment_pos - 12;
assert(off >= 0);
#if 0
//TODO: disable until we're sure this is allowed per the Webm std
m_file.WriteID1(0xA7); //Position ID
m_file.Write1UInt(8); //payload size is 8 bytes
m_file.Serialize8UInt(off); //payload
if (pPrevCluster)
{
const __int64 size = c.m_pos - pPrevCluster->m_pos;
assert(size > 0);
m_file.WriteID1(0xAB); //PrevSize ID
m_file.Write1UInt(8); //payload size is 8 bytes
m_file.Serialize8UInt(size); //payload
}
#endif
ULONG cFrames = 0;
LONG vtc_prev = -1;
StreamVideo::frames_t& rframes = m_pVideo->GetKeyFrames();
while (!vframes.empty())
{
typedef StreamVideo::frames_t::const_iterator video_iter_t;
video_iter_t video_iter = vframes.begin();
const video_iter_t video_iter_end = vframes.end();
const StreamVideo::VideoFrame* const pvf = *video_iter++;
assert(pvf);
if (pvf == pvf_stop)
break;
const StreamVideo::VideoFrame* const pvf_next =
(video_iter == video_iter_end) ? 0 : *video_iter;
//const bool bLastVideo = (pvf_next == pvf_stop);
const ULONG vt = pvf->GetTimecode();
assert(vt >= c.m_timecode);
assert((pvf_stop == 0) || (vt < pvf_stop->GetTimecode()));
if ((m_pAudio == 0) || m_pAudio->GetFrames().empty())
{
if (!rframes.empty() && (pvf == rframes.front()))
rframes.pop_front();
const ULONG vtc = pvf->GetTimecode();
WriteVideoFrame(c, cFrames, pvf_stop, pvf_next, vtc_prev);
vtc_prev = vtc;
continue;
}
const StreamAudio::frames_t& aframes = m_pAudio->GetFrames();
typedef StreamAudio::frames_t::const_iterator audio_iter_t;
audio_iter_t i = aframes.begin();
const audio_iter_t j = aframes.end();
const StreamAudio::AudioFrame* const paf = *i++; //1st audio frame
assert(paf);
const ULONG at = paf->GetTimecode();
assert(at >= c.m_timecode);
if (vt < at)
{
if (!rframes.empty() && (pvf == rframes.front()))
rframes.pop_front();
const ULONG vtc = pvf->GetTimecode();
WriteVideoFrame(c, cFrames, pvf_stop, pvf_next, vtc_prev);
vtc_prev = vtc;
continue;
}
//At this point, we have (at least) one audio frame,
//and (at least) one video frame. They could have an
//equal timecode, or the audio might be smaller than
//the video. Our desire is that the largest audio
//frame less than the pvf_stop go on the next cluster,
//which means any video frames greater than the audio
//frame will also go on the next cluster.
if (pvf_stop == 0) //means write all extant frames
{
//We know that this audio frame is less or equal to
//the video frame, so write it now.
WriteAudioFrame(c, cFrames);
continue;
}
//At this point, we still have an audio frame and a
//video frame, neigther of which has been written yet.
const ULONG vt_stop = pvf_stop->GetTimecode();
if (at >= vt_stop) //weird
break;
if (i == j) //weird
break;
const StreamAudio::AudioFrame* const paf_stop = *i; //2nd audio frame
assert(paf_stop);
const ULONG at_stop = paf_stop->GetTimecode();
if (at_stop >= vt_stop)
break;
WriteAudioFrame(c, cFrames); //write 1st audio frame
}
if (m_bLiveMux == false)
{
// In default (not live) mode we must seek back to the
// cluster size pos, and replace the -1 placeholder with
// the correct value.
const __int64 pos = m_file.GetPosition();
const __int64 size_ = pos - c.m_pos - 8;
assert(size_ <= ULONG_MAX);
const ULONG size = static_cast<ULONG>(size_);
m_file.SetPosition(c.m_pos + 4);
m_file.Write4UInt(size);
m_file.SetPosition(pos);
}
}
void Context::CreateNewClusterAudioOnly()
{
assert(m_bBufferData == false);
assert(m_pAudio);
const StreamAudio::frames_t& aframes = m_pAudio->GetFrames();
assert(!aframes.empty());
const StreamAudio::AudioFrame* const paf_first = aframes.front();
assert(paf_first);
const StreamAudio::AudioFrame& af_first = *paf_first;
const ULONG af_first_time = af_first.GetTimecode();
clusters_t& cc = m_clusters;
assert(cc.empty() || (af_first_time > cc.back().m_timecode));
cc.push_back(Cluster());
Cluster& c = cc.back();
c.m_pos = m_file.GetPosition();
c.m_timecode = af_first_time;
// Write cluster header
m_file.WriteID4(WebmUtil::kEbmlClusterID);
if (!m_bLiveMux)
{
// Use a 7-byte cluster header
m_file.SerializeUInt(0x3FFFFF, 3); // temp cluster size; rewritten
// below if |m_bLiveMux| is false
}
else
{
// Use a 5-byte cluster header
m_file.Serialize1UInt(0xFF);
}
m_file.WriteID1(WebmUtil::kEbmlTimeCodeID);
BYTE timecode_size;
if (!m_bLiveMux)
{
timecode_size = m_file.GetSerializeUIntSize(c.m_timecode);
assert(timecode_size <= 8);
}
else
{
// To facilitate easy rewriting of timecodes, always write 8 byte
// timecodes in live mux mode.
timecode_size = 8;
}
m_file.Write1UInt(timecode_size);
m_file.SerializeUInt(c.m_timecode, timecode_size);
const __int64 off = c.m_pos - m_segment_pos - 12;
assert(off >= 0);
#if 0
//disable this until we're sure it's allowed per the WebM std
m_file.WriteID1(0xA7); //Position ID
m_file.Write1UInt(8); //payload size is 8 bytes
m_file.Serialize8UInt(off); //payload
if (pPrevCluster)
{
const __int64 size = c.m_pos - pPrevCluster->m_pos;
assert(size > 0);
m_file.WriteID1(0xAB); //PrevSize ID
m_file.Write1UInt(8); //payload size is 8 bytes
m_file.Serialize8UInt(size); //payload
}
#endif
ULONG cFrames = 0; //TODO: must write cues for audio
while (!aframes.empty())
{
const StreamAudio::AudioFrame* const paf = aframes.front();
assert(paf);
const ULONG t = paf->GetTimecode();
assert(t >= c.m_timecode);
const LONG dt = LONG(t) - LONG(c.m_timecode);
if (dt > kAudioClusterSizeInTimeMs)
break;
WriteAudioFrame(c, cFrames);
}
if (m_bLiveMux == false)
{
// In default (not live) mode we must seek back to the
// cluster size pos, and replace the -1 placeholder with
// the correct value.
const LONGLONG pos = m_file.GetPosition();
const LONGLONG size = pos - (c.m_pos + 7); //7 = ID (4) + size (3)
m_file.SetPosition(c.m_pos + 4);
m_file.WriteUInt(size, 3);
m_file.SetPosition(pos);
}
}
void Context::WriteVideoFrame(
Cluster& c,
ULONG& cFrames,
const StreamVideo::VideoFrame* stop,
const StreamVideo::VideoFrame* next,
LONG /* prev_timecode */ )
{
assert(m_pVideo);
StreamVideo& s = *m_pVideo;
StreamVideo::frames_t& vframes = s.GetFrames();
assert(!vframes.empty());
StreamVideo::VideoFrame* const pf = vframes.front();
assert(pf);
assert(pf != stop);
assert(pf != next);
StreamVideo::frames_t& rframes = m_pVideo->GetKeyFrames();
rframes; //already popped
assert(rframes.empty() || (pf != rframes.front()));
assert(cFrames < ULONG_MAX);
++cFrames;
const ULONG ft = pf->GetTimecode();
#if 1
pf->WriteSimpleBlock(s, c.m_timecode);
#else
if (next != stop)
pf->WriteSimpleBlock(s, c.m_timecode);
else
{
ULONG duration;
if (next == 0)
duration = pf->GetDuration();
else
{
const ULONG tc_curr = pf->GetTimecode();
const ULONG tc_next = next->GetTimecode();
if (tc_next <= tc_curr)
duration = 0;
else
duration = tc_next - tc_curr;
}
if (duration == 0)
pf->WriteSimpleBlock(s, c.m_timecode);
else if ((prev_timecode >= 0) && (ft > ULONG(prev_timecode)))
pf->WriteBlockGroup(s, c.m_timecode, prev_timecode, duration);
else if (pf->IsKey())
pf->WriteBlockGroup(s, c.m_timecode, -1, duration);
else
pf->WriteSimpleBlock(s, c.m_timecode);
}
#endif
if (pf->IsKey())
{
Cluster::keyframes_t& kk = c.m_keyframes;
const Keyframe k(ft, cFrames);
kk.push_back(k);
}
if (ft > m_max_timecode)
m_max_timecode = ft;
vframes.pop_front();
pf->Release();
#if 0
odbgstream os;
os << "mux::context::writevideoframe: t=" << ft
<< " vframes.size=" << vframes.size()
<< " rframes.size=" << rframes.size()
<< endl;
#endif
}
void Context::WriteAudioFrame(Cluster& c, ULONG& cFrames)
{
assert(m_pAudio);
StreamAudio& s = *m_pAudio;
StreamAudio::frames_t& aframes = s.GetFrames();
assert(!aframes.empty());
StreamAudio::AudioFrame* const pf = aframes.front();
assert(pf);
assert(cFrames < ULONG_MAX);
++cFrames;
pf->WriteSimpleBlock(s, c.m_timecode);
const ULONG ft = pf->GetTimecode();
if (ft > m_max_timecode)
m_max_timecode = ft;
aframes.pop_front();
pf->Release();
#if 0
odbgstream os;
os << "mux::context::writeaudioframe: t=" << ft
<< " aframes.size=" << aframes.size()
<< endl;
#endif
}
void Context::FlushVideo(StreamVideo* pVideo)
{
assert(pVideo);
assert(pVideo == m_pVideo);
StreamVideo::frames_t& vframes = pVideo->GetFrames();
if (m_file.GetStream() == 0)
{
assert(vframes.empty());
return;
}
while (!vframes.empty())
CreateNewCluster(0);
}
void Context::FlushAudio(StreamAudio* pAudio)
{
assert(pAudio);
assert(pAudio == m_pAudio);
const StreamAudio::frames_t& aframes = pAudio->GetFrames();
if (m_file.GetStream() == 0)
{
assert(aframes.empty());
return;
}
while (!aframes.empty())
{
if ((m_pVideo != 0) && !m_pVideo->GetFrames().empty())
CreateNewCluster(0);
else
CreateNewClusterAudioOnly();
}
}
bool Context::GetLiveMuxMode() const
{
return m_bLiveMux;
}
void Context::SetLiveMuxMode(bool is_live)
{
m_bLiveMux = is_live;
}
void Context::BufferData()
{
assert(m_bBufferData == false);
m_bBufferData = true;
}
void Context::FlushBufferedData()
{
assert(m_bBufferData == true);
const uint64 element_size =
m_buf.GetBufferLength() - m_buf_element_info.num_bytes_to_ignore;
m_buf.RewriteUInt(m_buf_element_info.offset, element_size,
m_buf_element_info.byte_count);
m_file.Write(m_buf.GetBufferPtr(),
static_cast<ULONG>(m_buf.GetBufferLength()));
m_buf.Reset();
m_bBufferData = false;
}
void Context::ResetBuffer()
{
m_bBufferData = false;
m_buf.Reset();
}
} // namespace WebmMuxLib