blob: 335edb4d4ef0057261e6e38ba6947ce9271cd90d [file]
// Copyright (c) 2010 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.
#pragma warning(disable:4505) //unreferenced local function has been removed
#include "webmvorbisencoderfilter.hpp"
#include "webmvorbisencoderoutpin.hpp"
#include "mediatypeutil.hpp"
#include "graphutil.hpp"
#include "webmtypes.hpp"
#include "vorbistypes.hpp"
#include "vorbis/vorbisenc.h"
#include "vorbis/codec.h" //in libvorbis
#include <vfwmsgs.h>
#include <uuids.h>
#include <mmreg.h>
#include <cassert>
//#include <amvideo.h>
//#include <evcode.h>
#ifdef _DEBUG
#include <iomanip>
#include "odbgstream.hpp"
using std::endl;
using std::hex;
using std::dec;
using std::fixed;
using std::setprecision;
#endif
namespace WebmVorbisEncoderLib
{
Inpin::Inpin(Filter* pFilter) :
Pin(PINDIR_INPUT, L"input"),
m_pFilter(pFilter),
m_bEndOfStream(false),
m_bFlush(false),
m_bDone(false),
m_bStopped(true),
m_first_reftime(-1),
m_start_reftime(-1),
m_start_samples(-1)
{
AM_MEDIA_TYPE mt;
//This is essentially a "partial media type". That's all we
//can say right now without being connected.
mt.majortype = MEDIATYPE_Audio;
mt.subtype = MEDIASUBTYPE_IEEE_FLOAT;
mt.bFixedSizeSamples = FALSE;
mt.bTemporalCompression = FALSE;
mt.lSampleSize = 0;
mt.formattype = GUID_NULL;
mt.pUnk = 0;
mt.cbFormat = 0;
mt.pbFormat = 0;
m_preferred_mtv.Add(mt);
m_info.channels = 0; //means "not initialized"
m_hSamples = CreateEvent(0, 0, 0, 0);
assert(m_hSamples);
}
Inpin::~Inpin()
{
const BOOL b = CloseHandle(m_hSamples);
assert(b);
}
HRESULT Inpin::QueryInterface(const IID& iid, void** ppv)
{
if (ppv == 0)
return E_POINTER;
IUnknown*& pUnk = reinterpret_cast<IUnknown*&>(*ppv);
if (iid == __uuidof(IUnknown))
pUnk = static_cast<IPin*>(this);
else if (iid == __uuidof(IPin))
pUnk = static_cast<IPin*>(this);
else if (iid == __uuidof(IMemInputPin))
pUnk = static_cast<IMemInputPin*>(this);
else
{
pUnk = 0;
return E_NOINTERFACE;
}
pUnk->AddRef();
return S_OK;
}
ULONG Inpin::AddRef()
{
return m_pFilter->AddRef();
}
ULONG Inpin::Release()
{
return m_pFilter->Release();
}
HRESULT Inpin::Connect(IPin*, const AM_MEDIA_TYPE*)
{
return E_UNEXPECTED; //for output pins only
}
HRESULT Inpin::QueryInternalConnections(
IPin** pa,
ULONG* pn)
{
if (pn == 0)
return E_POINTER;
Filter::Lock lock;
HRESULT hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
const ULONG m = 1; //number of output pins
ULONG& n = *pn;
if (n == 0)
{
if (pa == 0) //query for required number
{
n = m;
return S_OK;
}
return S_FALSE; //means "insufficient number of array elements"
}
if (n < m)
{
n = 0;
return S_FALSE; //means "insufficient number of array elements"
}
if (pa == 0)
{
n = 0;
return E_POINTER;
}
IPin*& pin = pa[0];
pin = &m_pFilter->m_outpin;
pin->AddRef();
n = m;
return S_OK;
}
HRESULT Inpin::ReceiveConnection(
IPin* pin,
const AM_MEDIA_TYPE* pmt)
{
if (pin == 0)
return E_INVALIDARG;
if (pmt == 0)
return E_INVALIDARG;
Filter::Lock lock;
HRESULT hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
if (m_pFilter->m_state != State_Stopped)
return VFW_E_NOT_STOPPED;
if (bool(m_pPinConnection))
return VFW_E_ALREADY_CONNECTED;
m_connection_mtv.Clear();
hr = QueryAccept(pmt);
if (hr != S_OK)
return VFW_E_TYPE_NOT_ACCEPTED;
const AM_MEDIA_TYPE& mt = *pmt;
assert(mt.formattype == FORMAT_WaveFormatEx);
assert(mt.cbFormat >= sizeof(WAVEFORMATEX));
assert(mt.pbFormat);
hr = m_connection_mtv.Add(mt);
if (FAILED(hr))
return hr;
const WAVEFORMATEX& wfx = (WAVEFORMATEX&)(*mt.pbFormat);
assert(wfx.nChannels > 0);
assert(wfx.nChannels <= 2); //TODO: proper channel mapping
assert(wfx.nSamplesPerSec > 0);
//Initialize vorbis encoder library, in order to generate
//the 3 header packets, and thus the output media type.
assert(m_info.channels <= 0);
vorbis_info_init(&m_info);
int result = vorbis_encode_init_vbr(
&m_info,
wfx.nChannels,
wfx.nSamplesPerSec,
1.0); //?
if (result != 0) //error
return E_FAIL;
assert(m_info.channels > 0);
assert(m_info.channels == wfx.nChannels);
result = vorbis_analysis_init(&m_dsp_state, &m_info);
assert(result == 0);
vorbis_comment comment_hdr;
vorbis_comment_init(&comment_hdr);
ogg_packet ident, comment_pkt, code;
result = vorbis_analysis_headerout(
&m_dsp_state,
&comment_hdr,
&ident,
&comment_pkt,
&code);
if (result != 0)
{
vorbis_info_clear(&m_info);
m_info.channels = 0;
return E_FAIL;
}
result = vorbis_block_init(&m_dsp_state, &m_block);
assert(result == 0);
m_pPinConnection = pin;
OnConnect(wfx, ident, comment_pkt, code);
//Encoding workflow:
//http://xiph.org/vorbis/doc/libvorbis/overview.html
//http://xiph.org/vorbis/doc/vorbisenc/overview.html
//http://xiph.org/vorbis/doc/vorbisenc/examples.html
return S_OK;
}
void Inpin::OnConnect(
const WAVEFORMATEX& wfx,
const ogg_packet& ident,
const ogg_packet& comment,
const ogg_packet& code)
{
typedef VorbisTypes::VORBISFORMAT2 FMT;
size_t cb = sizeof(FMT);
assert(ident.packetno == 0);
assert(ident.packet);
assert(ident.bytes >= 7);
assert(memcmp(ident.packet, "\x01vorbis", 7) == 0);
cb += ident.bytes;
assert(comment.packetno == 1);
assert(comment.packet);
assert(comment.bytes >= 7);
assert(memcmp(comment.packet, "\x03vorbis", 7) == 0);
cb += comment.bytes;
assert(code.packetno == 2);
assert(code.packet);
assert(code.bytes >= 7);
assert(memcmp(code.packet, "\x05vorbis", 7) == 0);
cb += code.bytes;
AM_MEDIA_TYPE mt;
mt.majortype = MEDIATYPE_Audio;
mt.subtype = VorbisTypes::MEDIASUBTYPE_Vorbis2;
mt.bFixedSizeSamples = FALSE;
mt.bTemporalCompression = FALSE;
mt.lSampleSize = 0;
mt.formattype = VorbisTypes::FORMAT_Vorbis2;
mt.pUnk = 0;
mt.cbFormat = cb;
mt.pbFormat = (BYTE*)_malloca(cb);
BYTE* pb = mt.pbFormat;
FMT& fmt = (FMT&)(*pb);
fmt.channels = wfx.nChannels;
fmt.samplesPerSec = wfx.nSamplesPerSec;
fmt.bitsPerSample = 0; //TODO
fmt.headerSize[0] = ident.bytes;
fmt.headerSize[1] = comment.bytes;
fmt.headerSize[2] = code.bytes;
pb += sizeof(FMT);
memcpy(pb, ident.packet, ident.bytes);
pb += ident.bytes;
memcpy(pb, comment.packet, comment.bytes);
pb += comment.bytes;
memcpy(pb, code.packet, code.bytes);
pb += code.bytes;
assert((pb - mt.pbFormat) == ptrdiff_t(mt.cbFormat));
m_pFilter->m_outpin.OnInpinConnect(mt);
}
HRESULT Inpin::EndOfStream()
{
Filter::Lock lock;
HRESULT hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
if (!bool(m_pPinConnection))
return VFW_E_NOT_CONNECTED;
#ifdef _DEBUG
odbgstream os;
os << "webmvorbisencoder::inpin::EOS" << endl;
#endif
if (m_bFlush)
return S_FALSE; //?
m_bEndOfStream = true;
const int status = vorbis_analysis_wrote(&m_dsp_state, 0); //means "EOS"
status;
assert(status == 0);
hr = lock.Release();
assert(SUCCEEDED(hr));
if (FAILED(hr))
return hr;
return PopulateSamples();
}
HRESULT Inpin::BeginFlush()
{
#ifdef _DEBUG
odbgstream os;
os << "webmvorbisencoder::inpin::beginflush(begin)" << endl;
#endif
Filter::Lock lock;
HRESULT hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
if (!bool(m_pPinConnection))
return VFW_E_NOT_CONNECTED;
//IPin::BeginFlush
//In a flush operation, a filter discards whatever data it was processing.
//It rejects new data until the flush is completed. The flush is
//completed when the upstream pin calls the IPin::EndFlush method.
//Flushing enables the filter graph to be more responsive when events
//alter the normal data flow. For example, flushing occurs during a seek.
//
//When BeginFlush is called, the filter performs the following steps:
//
//(1) Passes the IPin::BeginFlush call downstream.
//
//(2) Sets an internal flag that causes all data-streaming methods to
// fail, such as IMemInputPin::Receive.
//
//(3) Returns from any blocked calls to the Receive method.
//
//
//When the BeginFlush notification reaches a renderer filter,
//the renderer frees any samples that it holds.
//
//After BeginFlush is called, the pin rejects all samples from upstream,
//with a return value of S_FALSE, until the IPin::EndFlush method is
//called.
//IPin::EndOfStream
//The IPin::BeginFlush method flushes any queued end-of-stream
//notifications. This is intended for input pins only.
m_bFlush = true;
Outpin& outpin = m_pFilter->m_outpin;
if (IPin* const pPin = outpin.m_pPinConnection)
{
#ifdef _DEBUG
os << "webmvorbisencoder::inpin::beginflush:"
<< " flushing downstream filter"
<< endl;
#endif
hr = pPin->BeginFlush(); //safe to do this here, while holding lock?
#ifdef _DEBUG
os << "webmvorbisencoder::inpin::beginflush:"
<< " called BeginFlush: hr=0x" << hex << hr << dec
<< "\nwebmvorbisdecoder::inpin::beginflush: waiting for thread"
<< "to terminate"
<< endl;
#endif
const BOOL b = SetEvent(m_hSamples); //to terminate thread
assert(b);
hr = lock.Release();
assert(SUCCEEDED(hr));
//The thread might not exist yet, if we have been connected but not
//started. In which case, attempting to stop the thread is benign.
outpin.StopThread();
}
#ifdef _DEBUG
os << "webmvorbisencoder::inpin::beginflush(end #2): thread terminated"
<< endl;
#endif
return S_OK;
}
HRESULT Inpin::EndFlush()
{
Filter::Lock lock;
HRESULT hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
if (!bool(m_pPinConnection))
return VFW_E_NOT_CONNECTED;
#ifdef _DEBUG
odbgstream os;
os << "webmvorbisencoder::inpin::endflush(begin)" << endl;
#endif
//IPin::EndFlush
//When this method is called, the filter performs the following actions:
//
//(1) Waits for all queued samples to be discarded.
//
//(2) Frees any buffered data, including any pending end-of-stream
//notifications.
//
//(3) Clears any pending EC_COMPLETE notifications.
//
//(4) Calls EndFlush downstream.
//
//When the method returns, the pin can accept new samples.
m_bFlush = false;
m_bEndOfStream = false;
m_first_reftime = -1;
m_bDone = false;
while (!m_buffers.empty())
{
IMediaSample* const pSample = m_buffers.front();
m_buffers.pop_front();
if (pSample)
pSample->Release();
}
Outpin& outpin = m_pFilter->m_outpin;
if (IPin* const pPin = outpin.m_pPinConnection)
{
#ifdef _DEBUG
os << "webmvorbisencoder::inpin::endflush: calling endflush on"
<< " downstream filter"
<< endl;
#endif
hr = pPin->EndFlush(); //safe to call this without releasing lock?
if (m_pFilter->m_state != State_Stopped)
{
#ifdef _DEBUG
os << "webmvorbisencoder::inpin::endflush: state != stopped,"
<< " so starting thread"
<< endl;
#endif
outpin.StartThread();
#ifdef _DEBUG
os << "webmvorbisencoder::inpin::endflush: started thread"
<< endl;
#endif
}
}
#ifdef _DEBUG
os << "webmvorbisencoder::inpin::endflush(end)" << endl;
#endif
return S_OK;
}
void Inpin::OnCompletion()
{
Filter::Lock lock;
HRESULT hr = lock.Seize(m_pFilter);
assert(SUCCEEDED(hr));
while (!m_buffers.empty())
{
IMediaSample* const pSample = m_buffers.front();
if (pSample == 0) //EOS notification
break; //don't throw EOS notification away
m_buffers.pop_front();
pSample->Release();
}
m_bDone = true;
}
HRESULT Inpin::NewSegment(
REFERENCE_TIME st,
REFERENCE_TIME sp,
double r)
{
Filter::Lock lock;
HRESULT hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
if (!bool(m_pPinConnection))
return VFW_E_NOT_CONNECTED;
if (IPin* pPin = m_pFilter->m_outpin.m_pPinConnection)
{
lock.Release();
const HRESULT hr = pPin->NewSegment(st, sp, r);
return hr;
}
return S_OK;
}
HRESULT Inpin::QueryAccept(const AM_MEDIA_TYPE* pmt)
{
if (pmt == 0)
return E_INVALIDARG;
const AM_MEDIA_TYPE& mt = *pmt;
if (mt.majortype != MEDIATYPE_Audio)
return S_FALSE;
if (mt.subtype != MEDIASUBTYPE_IEEE_FLOAT)
return S_FALSE;
if (mt.formattype != FORMAT_WaveFormatEx)
return S_FALSE;
if (mt.pbFormat == 0)
return S_FALSE;
if (mt.cbFormat < 18) //WAVEFORMATEX
return S_FALSE;
const WAVEFORMATEX& wfx = (WAVEFORMATEX&)(*mt.pbFormat);
if (wfx.wFormatTag != WAVE_FORMAT_IEEE_FLOAT)
return S_FALSE;
if (wfx.cbSize > 0) //weird
return S_FALSE;
if (wfx.nChannels == 0)
return S_FALSE;
if (wfx.nChannels > 2) //TODO
return S_FALSE;
if (wfx.nSamplesPerSec == 0)
return S_FALSE;
return S_OK;
}
HRESULT Inpin::GetAllocator(IMemAllocator** p)
{
if (p)
*p = 0;
return VFW_E_NO_ALLOCATOR;
}
HRESULT Inpin::NotifyAllocator(
IMemAllocator* pAllocator,
BOOL)
{
if (pAllocator == 0)
return E_INVALIDARG;
ALLOCATOR_PROPERTIES props;
const HRESULT hr = pAllocator->GetProperties(&props);
hr;
assert(SUCCEEDED(hr));
#ifdef _DEBUG
wodbgstream os;
os << "webmvorbisencoder::inpin::NotifyAllocator: props.cBuffers="
<< props.cBuffers
<< " cbBuffer="
<< props.cbBuffer
<< " cbAlign="
<< props.cbAlign
<< " cbPrefix="
<< props.cbPrefix
<< endl;
#endif
return S_OK;
}
HRESULT Inpin::GetAllocatorRequirements(ALLOCATOR_PROPERTIES* pp)
{
if (pp == 0)
return E_POINTER;
ALLOCATOR_PROPERTIES& p = *pp;
p;
return S_OK;
}
HRESULT Inpin::Receive(IMediaSample* pInSample)
{
if (pInSample == 0)
return E_INVALIDARG;
HRESULT hr;
//#define DEBUG_RECEIVE
#undef DEBUG_RECEIVE
#ifdef DEBUG_RECEIVE
__int64 start_reftime_, stop_reftime_;
hr = pInSample->GetTime(&start_reftime_, &stop_reftime_);
#endif
Filter::Lock lock;
hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
//#ifdef DEBUG_RECEIVE
// wodbgstream os;
// os << L"vp8dec::inpin::Receive: THREAD=0x"
// << std::hex << GetCurrentThreadId() << std::dec
// << endl;
//#endif
if (!bool(m_pPinConnection))
return VFW_E_NOT_CONNECTED;
Outpin& outpin = m_pFilter->m_outpin;
if (!bool(outpin.m_pPinConnection))
return S_FALSE;
if (!bool(outpin.m_pAllocator)) //should never happen
return VFW_E_NO_ALLOCATOR;
if (m_pFilter->m_state == State_Stopped)
return VFW_E_NOT_RUNNING;
if (m_bStopped) //weird
return VFW_E_WRONG_STATE;
if (m_bEndOfStream)
return VFW_E_SAMPLE_REJECTED_EOS;
if (m_bFlush)
return S_FALSE;
if (m_bDone)
return S_FALSE;
if (m_first_reftime < 0)
{
LONGLONG sp;
hr = pInSample->GetTime(&m_first_reftime, &sp);
if (FAILED(hr))
return S_OK;
if (m_first_reftime < 0) //TODO: Vorbis can handle this case
return S_OK;
m_start_reftime = m_first_reftime;
m_start_samples = 0;
#ifdef DEBUG_RECEIVE
odbgstream os;
os << std::fixed << std::setprecision(3);
os << "\nwebmvorbisdec::Inpin::Receive: RESET FIRST REFTIME;"
<< " st=" << m_start_reftime
<< " st[sec]=" << (double(m_start_reftime) / 10000000)
<< endl;
#endif
#if 0 //TODO
const int status = vorbis_synthesis_restart(&m_dsp_state);
status;
assert(status == 0); //success
#endif
m_bDiscontinuity = true;
}
#ifdef DEBUG_RECEIVE
{
odbgstream os;
os << "webmvorbisdec::inpin::receive: ";
os << std::fixed << std::setprecision(3);
if (hr == S_OK)
os << "start[sec]="
<< double(start_reftime_) / 10000000
<< "; stop[sec]="
<< double(stop_reftime_) / 10000000
<< "; dt[ms]="
<< double(stop_reftime_ - start_reftime_) / 10000;
else if (hr == VFW_S_NO_STOP_TIME)
os << "start[sec]=" << double(start_reftime_) / 10000000;
os << endl;
}
#endif
Encode(pInSample);
hr = lock.Release();
assert(SUCCEEDED(hr));
if (FAILED(hr))
return hr;
return PopulateSamples();
}
void Inpin::Encode(IMediaSample* s)
{
assert(s);
const long len = s->GetActualDataLength();
if (len <= 0)
return;
const int channels = m_info.channels;
assert(channels > 0);
const long block_align = sizeof(float) * channels;
assert(len % block_align == 0);
const long block_count = len / block_align;
assert(block_count > 0); //distinguished value 0 means "end of stream"
const long sample_count = len / sizeof(float);
assert(sample_count > 0);
BYTE* buf;
HRESULT hr = s->GetPointer(&buf);
assert(SUCCEEDED(hr));
assert(buf);
const float* const src_begin = reinterpret_cast<float*>(buf);
const float* src = src_begin;
const float* const src_end = src_begin + sample_count;
src_end;
float** dst = vorbis_analysis_buffer(&m_dsp_state, block_count);
assert(dst);
//TODO: verify left-right channel order issue
//TODO: extend to channels > 2
for (int block = 0; block < block_count; ++block)
for (int channel = 0; channel < channels; ++channel)
{
memcpy(dst[channel] + block, src, sizeof(float));
++src;
}
assert(src == src_end);
assert((src_end - src_begin) / channels == block_count);
const int status = vorbis_analysis_wrote(&m_dsp_state, block_count);
status;
assert(status == 0);
}
HRESULT Inpin::PopulateSamples()
{
//Filter is NOT locked
const Outpin& outpin = m_pFilter->m_outpin;
for (;;)
{
GraphUtil::IMediaSamplePtr pOutSample;
HRESULT hr = outpin.m_pAllocator->GetBuffer(&pOutSample, 0, 0, 0);
if (FAILED(hr))
return hr;
Filter::Lock lock;
hr = lock.Seize(m_pFilter);
assert(SUCCEEDED(hr));
if (FAILED(hr))
return hr;
if (m_pFilter->m_state == State_Stopped)
return VFW_E_NOT_RUNNING;
if (m_bStopped) //weird
return VFW_E_WRONG_STATE;
//if (m_bEndOfStream)
// return VFW_SAMPLE_REJECTED_EOS;
if (m_bFlush)
return S_FALSE;
if (m_bDone)
return S_FALSE;
if (!bool(outpin.m_pPinConnection)) //weird
return S_FALSE;
if (!bool(outpin.m_pInputPin)) //weird
return S_FALSE;
//if (!bool(outpin.m_pAllocator)) //weird
// return S_FALSE;
int status = vorbis_analysis_blockout(&m_dsp_state, &m_block);
if (status < 0) //error
return E_FAIL;
if (status == 0) //no block available
{
#if 0 //see test below
//NOTE: commenting out this branch assumes
//that if we request EOS, then the encoder pushes
//out at least one packet, with the EOS indication
//specified.
if (m_bEndOfStream)
{
m_buffers.push_back(0);
const BOOL b = SetEvent(m_hSamples);
b;
assert(b);
}
#endif
return S_OK;
}
//status=1 means "success, more blocks available"
ogg_packet pkt;
status = vorbis_analysis(&m_block, &pkt);
assert(status == 0);
//TODO: vet seq no.
PopulateSample(pOutSample, pkt);
m_buffers.push_back(pOutSample.Detach());
if (pkt.e_o_s)
m_buffers.push_back(0);
const BOOL b = SetEvent(m_hSamples);
b;
assert(b);
if (pkt.e_o_s)
return S_OK;
}
}
void Inpin::PopulateSample(
IMediaSample* pOutSample,
const ogg_packet& pkt)
{
assert(m_start_samples >= 0);
assert(m_start_reftime >= 0);
assert(pOutSample);
assert(pkt.packet);
assert(pkt.bytes > 0);
assert(pkt.granulepos >= 0);
assert(pkt.granulepos >= m_start_samples);
const long size = pOutSample->GetSize();
assert(size >= 0);
assert(size >= pkt.bytes);
BYTE* dst;
HRESULT hr = pOutSample->GetPointer(&dst);
assert(SUCCEEDED(hr));
assert(dst);
memcpy(dst, pkt.packet, pkt.bytes);
hr = pOutSample->SetActualDataLength(pkt.bytes);
assert(SUCCEEDED(hr));
const double stop_samples = static_cast<double>(pkt.granulepos);
const double samples_per_sec = m_info.rate;
const double stop_secs = stop_samples / samples_per_sec;
const double stop_ticks_ = stop_secs * 10000000;
const LONGLONG stop_ticks = static_cast<LONGLONG>(stop_ticks_);
LONGLONG stop_reftime = m_first_reftime + stop_ticks;
assert(stop_reftime >= m_start_reftime);
#if 0 //TODO: determine whether we need this
if (stop_reftime <= m_start_reftime)
{
#ifdef _DEBUG
odbgstream os;
os << "WebmVorbisEnc::Inpin::PopulateSample: start=stop"
<< " pkt.granulepos=" << pkt.granulepos
<< " pkt.pkt_no=" << pkt.packetno
<< " st=" << m_start_reftime
<< " sp=" << stop_reftime
<< " sp-st=" << (stop_reftime - m_start_reftime)
<< " stop_secs=" << fixed << setprecision(3) << stop_secs
<< endl;
#endif
stop_reftime = m_start_reftime + 1; //use 1 tick or 1 ms?
}
#endif
hr = pOutSample->SetTime(&m_start_reftime, &stop_reftime);
assert(SUCCEEDED(hr));
m_start_samples = pkt.granulepos;
m_start_reftime = stop_reftime;
hr = pOutSample->SetSyncPoint(TRUE); //?
assert(SUCCEEDED(hr));
hr = pOutSample->SetPreroll(FALSE);
assert(SUCCEEDED(hr));
hr = pOutSample->SetDiscontinuity(m_bDiscontinuity ? TRUE : FALSE);
assert(SUCCEEDED(hr));
m_bDiscontinuity = false;
hr = pOutSample->SetMediaTime(0, 0);
assert(SUCCEEDED(hr));
}
int Inpin::GetSample(IMediaSample** ppSample)
{
assert(ppSample);
IMediaSample*& pSample = *ppSample;
if (m_bFlush)
{
pSample = 0;
return -2; //terminate
}
if (m_buffers.empty())
{
pSample = 0;
if (m_bStopped)
return -2; //terminate
assert(m_pFilter->m_state != State_Stopped);
return -1; //wait
}
pSample = m_buffers.front();
m_buffers.pop_front();
if (pSample)
return 1;
assert(m_buffers.empty());
return 0; //EOS
}
HRESULT Inpin::ReceiveMultiple(
IMediaSample** pSamples,
long n, //in
long* pm) //out
{
if (pm == 0)
return E_POINTER;
long& m = *pm; //out
m = 0;
if (n <= 0)
return S_OK; //weird
if (pSamples == 0)
return E_INVALIDARG;
for (long i = 0; i < n; ++i)
{
IMediaSample* const pSample = pSamples[i];
assert(pSample);
const HRESULT hr = Receive(pSample);
if (hr != S_OK)
return hr;
++m;
}
return S_OK;
}
HRESULT Inpin::ReceiveCanBlock()
{
return S_OK; //because we wait for output sample
}
HRESULT Inpin::OnDisconnect()
{
m_pFilter->m_outpin.OnInpinDisconnect();
if (m_info.channels > 0)
{
vorbis_block_clear(&m_block);
vorbis_dsp_clear(&m_dsp_state);
vorbis_info_clear(&m_info);
m_info.channels = 0; //means "not initialized"
}
return S_OK;
}
HRESULT Inpin::GetName(PIN_INFO& info) const
{
const wchar_t name[] = L"IEEE Float";
#if _MSC_VER >= 1400
enum { namelen = sizeof(info.achName) / sizeof(WCHAR) };
const errno_t e = wcscpy_s(info.achName, namelen, name);
e;
assert(e == 0);
#else
wcscpy(info.achName, name);
#endif
return S_OK;
}
HRESULT Inpin::Seize(CLockable::Lock& lock)
{
return lock.Seize(m_pFilter);
}
FILTER_STATE Inpin::GetState()
{
return m_pFilter->m_state;
}
IBaseFilter* Inpin::GetFilter() //no addref here
{
return m_pFilter; //no addref here!
}
void Inpin::Start()
{
assert(m_buffers.empty());
m_bEndOfStream = false;
m_bFlush = false;
m_bDone = false;
m_bStopped = false;
m_first_reftime = -1;
m_start_reftime = -1;
m_start_samples = -1;
if (m_info.channels > 0) //connected
{
int result = vorbis_block_clear(&m_block);
assert(result == 0);
vorbis_dsp_clear(&m_dsp_state);
result = vorbis_analysis_init(&m_dsp_state, &m_info);
assert(result == 0);
result = vorbis_block_init(&m_dsp_state, &m_block);
assert(result == 0);
}
}
void Inpin::Stop()
{
while (!m_buffers.empty())
{
IMediaSample* const pSample = m_buffers.front();
m_buffers.pop_front();
if (pSample)
pSample->Release();
}
const BOOL b = SetEvent(m_hSamples); //tell thread to terminate
assert(b);
m_bStopped = true;
//TODO: we really should wait here for the output pin thread
//to terminate, in order to ensure that we transition to the
//bDone state immediately.
}
} //end namespace WebmVorbisEncoderLib