blob: 75c9c0e5c01de40d65b4e76e3a07b52a89b08782 [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 "vp8encoderfilter.hpp"
#include "vp8encoderoutpin.hpp"
#include "mediatypeutil.hpp"
#include "webmtypes.hpp"
#include "vpx/vp8cx.h"
#include <vfwmsgs.h>
#include <uuids.h>
#include <cassert>
#include <amvideo.h> //VideoInfoHeader
#include <dvdmedia.h> //VideoInfoHeader2
#ifdef _DEBUG
#include "odbgstream.hpp"
#include <iomanip>
using std::endl;
using std::hex;
using std::dec;
#endif
namespace VP8EncoderLib
{
Inpin::Inpin(Filter* p) :
Pin(p, PINDIR_INPUT, L"input"),
m_bEndOfStream(false),
m_bFlush(false),
m_bDiscontinuity(true),
m_buf(0),
m_buflen(0),
m_last_keyframe_time(0),
m_frames_received(0),
m_decimate_start_time(0)
{
AM_MEDIA_TYPE mt;
mt.majortype = MEDIATYPE_Video;
mt.subtype = MEDIASUBTYPE_YV12;
mt.bFixedSizeSamples = TRUE;
mt.bTemporalCompression = FALSE;
mt.lSampleSize = 0;
mt.formattype = GUID_NULL;
mt.pUnk = 0;
mt.cbFormat = 0;
mt.pbFormat = 0;
m_preferred_mtv.Add(mt);
mt.subtype = WebmTypes::MEDIASUBTYPE_I420;
m_preferred_mtv.Add(mt);
mt.subtype = MEDIASUBTYPE_YUY2;
m_preferred_mtv.Add(mt);
mt.subtype = MEDIASUBTYPE_YUYV;
m_preferred_mtv.Add(mt);
}
Inpin::~Inpin()
{
PurgePending();
delete[] m_buf;
}
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 = 2; //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;
}
pa[0] = &m_pFilter->m_outpin_video;
pa[0]->AddRef();
pa[1] = &m_pFilter->m_outpin_preview;
pa[1]->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;
hr = m_connection_mtv.Add(mt);
if (FAILED(hr))
return hr;
m_pPinConnection = pin;
m_pFilter->m_outpin_video.OnInpinConnect();
m_pFilter->m_outpin_preview.OnInpinConnect();
return S_OK;
}
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;
if (m_pFilter->m_state == State_Stopped)
return VFW_E_NOT_RUNNING;
if (m_bFlush)
return S_FALSE; //TODO: correct return value here?
if (m_bEndOfStream)
return S_FALSE;
m_bEndOfStream = true;
const vpx_codec_err_t err = vpx_codec_encode(&m_ctx, 0, 0, 0, 0, 0);
err;
assert(err == VPX_CODEC_OK); //TODO
const VP8PassMode m = m_pFilter->GetPassMode();
OutpinVideo& outpin = m_pFilter->m_outpin_video;
vpx_codec_iter_t iter = 0;
for (;;)
{
const vpx_codec_cx_pkt_t* const pkt =
vpx_codec_get_cx_data(&m_ctx, &iter);
if (pkt == 0)
break;
switch (pkt->kind)
{
case VPX_CODEC_CX_FRAME_PKT:
assert(m != kPassModeFirstPass);
AppendFrame(pkt);
break;
case VPX_CODEC_STATS_PKT:
assert(m == kPassModeFirstPass);
outpin.WriteStats(pkt);
break;
default:
assert(false);
return E_FAIL;
}
}
if (m != kPassModeFirstPass)
{
while (!m_pending.empty())
{
if (!bool(outpin.m_pAllocator))
break;
lock.Release();
GraphUtil::IMediaSamplePtr pOutSample;
const HRESULT hrGetBuffer =
outpin.m_pAllocator->GetBuffer(&pOutSample, 0, 0, 0);
hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
if (FAILED(hrGetBuffer))
break;
assert(bool(pOutSample));
PopulateSample(pOutSample); //consume pending frame
if (!bool(outpin.m_pInputPin))
break;
lock.Release();
const HRESULT hrReceive = outpin.m_pInputPin->Receive(pOutSample);
hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
if (hrReceive != S_OK)
break;
}
}
//We hold the lock.
if (IPin* pPin = m_pFilter->m_outpin_preview.m_pPinConnection)
{
lock.Release();
hr = pPin->EndOfStream();
hr = lock.Seize(m_pFilter);
assert(SUCCEEDED(hr)); //TODO
}
//We hold the lock.
if (IPin* pPin = outpin.m_pPinConnection)
{
lock.Release();
hr = pPin->EndOfStream();
}
return S_OK;
}
HRESULT Inpin::BeginFlush()
{
Filter::Lock lock;
HRESULT hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
if (!bool(m_pPinConnection))
return VFW_E_NOT_CONNECTED;
//TODO: need this?
//if (m_bFlush)
// return S_FALSE;
m_bFlush = true;
//We hold the lock
if (IPin* pPin = m_pFilter->m_outpin_preview.m_pPinConnection)
{
lock.Release();
hr = pPin->BeginFlush();
hr = lock.Seize(m_pFilter);
assert(SUCCEEDED(hr)); //TODO
}
//We hold the lock
if (IPin* pPin = m_pFilter->m_outpin_video.m_pPinConnection)
{
lock.Release();
hr = pPin->BeginFlush();
}
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;
m_bFlush = false;
//We hold the lock
if (IPin* pPin = m_pFilter->m_outpin_preview.m_pPinConnection)
{
lock.Release();
hr = pPin->EndFlush();
hr = lock.Seize(m_pFilter);
assert(SUCCEEDED(hr)); //TODO
}
//We hold the lock
if (IPin* pPin = m_pFilter->m_outpin_video.m_pPinConnection)
{
lock.Release();
hr = pPin->EndFlush();
}
return S_OK;
}
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;
//We hold the lock
if (IPin* pPin = m_pFilter->m_outpin_preview.m_pPinConnection)
{
lock.Release();
hr = pPin->NewSegment(st, sp, r);
hr = lock.Seize(m_pFilter);
assert(SUCCEEDED(hr)); //TODO
}
//We hold the lock
if (IPin* pPin = m_pFilter->m_outpin_video.m_pPinConnection)
{
lock.Release();
hr = pPin->NewSegment(st, sp, r);
}
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_Video)
return S_FALSE;
if (mt.subtype == MEDIASUBTYPE_YV12)
__noop;
else if (mt.subtype == WebmTypes::MEDIASUBTYPE_I420)
__noop;
else if (mt.subtype == MEDIASUBTYPE_YUY2)
__noop;
else if (mt.subtype == MEDIASUBTYPE_YUYV)
__noop;
else
return S_FALSE;
if (mt.pbFormat == 0)
return S_FALSE;
const BITMAPINFOHEADER* pbmih;
if (mt.formattype == FORMAT_VideoInfo)
{
if (mt.cbFormat < sizeof(VIDEOINFOHEADER))
return S_FALSE;
const VIDEOINFOHEADER& vih = (VIDEOINFOHEADER&)(*mt.pbFormat);
if (vih.AvgTimePerFrame <= 0)
return S_FALSE;
pbmih = &vih.bmiHeader;
}
else if (mt.formattype == FORMAT_VideoInfo2)
{
if (mt.cbFormat < sizeof(VIDEOINFOHEADER2))
return S_FALSE;
const VIDEOINFOHEADER2& vih = (VIDEOINFOHEADER2&)(*mt.pbFormat);
if (vih.AvgTimePerFrame <= 0)
return S_FALSE;
pbmih = &vih.bmiHeader;
}
else
return S_FALSE;
assert(pbmih);
const BITMAPINFOHEADER& bmih = *pbmih;
if (bmih.biSize != sizeof(BITMAPINFOHEADER)) //TODO: liberalize
return S_FALSE;
if (bmih.biWidth <= 0)
return S_FALSE;
if (bmih.biWidth % 2) //TODO
return S_FALSE;
if (bmih.biHeight == 0)
return S_FALSE;
if (bmih.biHeight % 2) //TODO
return S_FALSE;
if (bmih.biCompression != mt.subtype.Data1)
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 << "vp8enc::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;
if (pInSample->IsPreroll() == S_OK) //bogus for encode
return S_OK;
Filter::Lock lock;
HRESULT hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
if (!bool(m_pPinConnection))
return VFW_E_NOT_CONNECTED;
if (m_pFilter->m_state == State_Stopped)
return VFW_E_NOT_RUNNING;
if (m_bEndOfStream)
return VFW_E_SAMPLE_REJECTED_EOS;
if (m_bFlush)
return S_FALSE;
const BITMAPINFOHEADER& bmih = GetBMIH();
const LONG w = bmih.biWidth;
assert(w > 0);
assert((w % 2) == 0); //TODO
const LONG h = labs(bmih.biHeight);
assert(h > 0);
assert((h % 2) == 0); //TODO
const long len = pInSample->GetActualDataLength();
len;
assert(len >= 0);
img_fmt_t fmt;
const AM_MEDIA_TYPE& mt = m_connection_mtv[0];
if (mt.subtype == MEDIASUBTYPE_YV12)
fmt = IMG_FMT_YV12;
else if (mt.subtype == WebmTypes::MEDIASUBTYPE_I420)
fmt = IMG_FMT_I420;
else if ((mt.subtype == MEDIASUBTYPE_YUY2) ||
(mt.subtype == MEDIASUBTYPE_YUYV))
{
fmt = IMG_FMT_YUY2;
}
else
{
assert(false);
return E_FAIL;
}
BYTE* inbuf;
hr = pInSample->GetPointer(&inbuf);
assert(SUCCEEDED(hr));
assert(inbuf);
BYTE* imgbuf;
switch (fmt)
{
case IMG_FMT_YV12:
case IMG_FMT_I420:
{
assert(len == (w*h + 2*((w+1)/2)*((h+1)/2)));
imgbuf = inbuf;
assert(imgbuf);
break;
}
case IMG_FMT_YUY2:
{
assert(len == ((2*w) * h));
fmt = IMG_FMT_YV12;
imgbuf = ConvertYUY2ToYV12(inbuf, w, h);
assert(imgbuf);
break;
}
default:
assert(false);
return E_FAIL;
}
__int64 st, sp;
hr = pInSample->GetTime(&st, &sp);
if (FAILED(hr))
return hr;
if (m_pFilter->m_decimate > 1)
{
if (m_frames_received++ % m_pFilter->m_decimate)
return S_OK;
}
vpx_image_t img_;
vpx_image_t* const img = vpx_img_wrap(&img_, fmt, w, h, 1, imgbuf);
assert(img);
assert(img == &img_);
//TODO: set this based on vih.rcSource
const int status = vpx_img_set_rect(img, 0, 0, w, h);
status;
assert(status == 0);
m_pFilter->m_outpin_preview.Render(lock, img);
OutpinVideo& outpin = m_pFilter->m_outpin_video;
if (!bool(outpin.m_pPinConnection))
return S_OK;
if (st < 0) //?
{
#ifdef _DEBUG
odbgstream os;
os << "Vp8Encoder::Receive: st[reftime="
<< st
<< " st[sec]="
<< (double(st) / 10000000)
<< " sp[reftime]="
<< sp
<< " sp[sec]="
<< (double(sp) / 10000000)
<< " start_reftime="
<< m_start_reftime
<< "; START TIME LESS THAN 0"
<< endl;
#endif
return S_OK;
}
const bool bFirst = (m_start_reftime < 0);
if (bFirst)
{
#ifdef _DEBUG
odbgstream os;
os << "Vp8Encoder::Receive: st[reftime]="
<< st
<< " st[sec]="
<< (double(st) / 10000000)
<< " sp[reftime]="
<< sp
<< " sp[sec]="
<< (double(sp) / 10000000)
//<< " start_reftime="
//<< m_start_reftime
//<< " start_reftime[sec]="
//<< (double(m_start_reftime) / 10000000)
<< "; FIRST SAMPLE DETECTED"
<< endl;
#endif
m_start_reftime = 0;
}
else if (st <= m_start_reftime) //?
{
#ifdef _DEBUG
odbgstream os;
os << "Vp8Encoder::Receive: st[reftime]="
<< st
<< " st[sec]="
<< (double(st) / 10000000)
<< " sp[reftime]="
<< sp
<< " sp[sec]="
<< (double(sp) / 10000000)
<< " start_reftime="
<< m_start_reftime
<< " start_reftime[sec]="
<< (double(m_start_reftime) / 10000000)
<< "; START TIME LESS THAN PREV SAMPLE"
<< endl;
#endif
return S_OK;
}
else
{
#if 0 //def _DEBUG
odbgstream os;
os << "Vp8Encoder::Receive: st[reftime]="
<< st
<< " st[sec]="
<< (double(st) / 10000000)
<< " sp[reftime]="
<< sp
<< " sp[sec]="
<< (double(sp) / 10000000)
<< " start_reftime="
<< m_start_reftime
<< " start_reftime[sec]="
<< (double(m_start_reftime) / 10000000)
<< "; POST-FIRST SAMPLE DETECTED"
<< endl;
#endif
m_start_reftime = st;
}
__int64 duration_ = GetAvgTimePerFrame();
if (m_pFilter->m_decimate > 1)
duration_ *= m_pFilter->m_decimate;
assert(duration_ > 0);
const unsigned long d = static_cast<unsigned long>(duration_);
hr = pInSample->IsDiscontinuity();
const bool bDiscontinuity = (hr == S_OK);
vpx_enc_frame_flags_t f = 0;
if (m_pFilter->m_bForceKeyframe || bDiscontinuity || bFirst)
{
f |= VPX_EFLAG_FORCE_KF;
m_pFilter->m_bForceKeyframe = false;
}
else if (m_cfg.kf_mode == kKeyframeModeDisabled &&
m_pFilter->m_keyframe_interval > 0)
{
const __int64 reftime_since_last_kf = st - m_last_keyframe_time;
if (reftime_since_last_kf >= m_pFilter->m_keyframe_interval)
f |= VPX_EFLAG_FORCE_KF;
}
const Filter::Config::int32_t deadline_ = m_pFilter->m_cfg.deadline;
const ULONG dl = (deadline_ >= 0) ? deadline_ : kDeadlineGoodQuality;
const __int64 st2 = m_start_reftime / 10000; // scale to ms
const unsigned long d2 = (d + 9999) / 10000; // scale to ms
const vpx_codec_err_t err = vpx_codec_encode(&m_ctx, img, st2, d2, f, dl);
err;
assert(err == VPX_CODEC_OK); //TODO
const VP8PassMode m = m_pFilter->GetPassMode();
vpx_codec_iter_t iter = 0;
for (;;)
{
const vpx_codec_cx_pkt_t* const pkt =
vpx_codec_get_cx_data(&m_ctx, &iter);
if (pkt == 0)
break;
switch (pkt->kind)
{
case VPX_CODEC_CX_FRAME_PKT:
assert(m != kPassModeFirstPass);
AppendFrame(pkt);
break;
case VPX_CODEC_STATS_PKT:
assert(m == kPassModeFirstPass);
outpin.WriteStats(pkt);
break;
default:
assert(false);
return E_FAIL;
}
}
if (m == kPassModeFirstPass)
return S_OK; //nothing else to do
while (!m_pending.empty())
{
if (!bool(outpin.m_pAllocator))
return VFW_E_NO_ALLOCATOR;
lock.Release();
GraphUtil::IMediaSamplePtr pOutSample;
hr = outpin.m_pAllocator->GetBuffer(&pOutSample, 0, 0, 0);
if (FAILED(hr))
return hr;
assert(bool(pOutSample));
hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
PopulateSample(pOutSample); //consume pending frame
if (!bool(outpin.m_pInputPin))
return S_FALSE;
lock.Release();
hr = outpin.m_pInputPin->Receive(pOutSample);
if (hr != S_OK)
return hr;
hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return hr;
}
return S_OK;
}
void Inpin::PopulateSample(IMediaSample* p)
{
assert(p);
assert(!m_pending.empty());
#ifdef _DEBUG
{
AM_MEDIA_TYPE* pmt;
const HRESULT hr = p->GetMediaType(&pmt);
assert(FAILED(hr) || (pmt == 0));
}
#endif
_COM_SMARTPTR_TYPEDEF(IVP8Sample, __uuidof(IVP8Sample));
const IVP8SamplePtr pSample(p);
assert(bool(pSample));
IVP8Sample::Frame& f = pSample->GetFrame();
assert(f.buf == 0); //should have already been reclaimed
f = m_pending.front();
assert(f.buf);
m_pending.pop_front();
HRESULT hr = p->SetPreroll(FALSE);
assert(SUCCEEDED(hr));
hr = p->SetDiscontinuity(m_bDiscontinuity ? TRUE : FALSE);
assert(SUCCEEDED(hr));
m_bDiscontinuity = false;
}
void Inpin::AppendFrame(const vpx_codec_cx_pkt_t* pkt)
{
assert(pkt);
assert(pkt->kind == VPX_CODEC_CX_FRAME_PKT);
IVP8Sample::Frame f;
const HRESULT hr = m_pFilter->m_outpin_video.GetFrame(f);
hr;
assert(SUCCEEDED(hr));
assert(f.buf);
const size_t len_ = pkt->data.frame.sz;
const long len = static_cast<long>(len_);
const long size = f.buflen - f.off;
size;
assert(size >= len);
BYTE* tgt = f.buf + f.off;
//assert(intptr_t(tgt - props.cbPrefix) % props.cbAlign == 0);
memcpy(tgt, pkt->data.frame.buf, len);
f.len = len;
f.start = pkt->data.frame.pts * 10000; // scale to 100 ns ticks
assert(f.start >= 0);
#if 0
f.stop = f.start + pkt->data.frame.duration;
assert(f.stop > f.start);
#else
f.stop = -1; //don't send stop time
#endif
const uint32_t bKey = pkt->data.frame.flags & VPX_FRAME_IS_KEY;
#if 0 //def _DEBUG
odbgstream os;
os << "["__FUNCTION__"] " << "time=" << f.start / 10000000.0
<< " bKey=" << bKey << endl;
#endif
f.key = bKey ? true : false;
if (f.key)
m_last_keyframe_time = f.start;
m_pending.push_back(f);
#if 0 //def _DEBUG
odbgstream os;
os << "vp8encoder::inpin::appendframe: pending.size="
<< m_pending.size()
<< endl;
#endif
}
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()
{
Filter::Lock lock;
const HRESULT hr = lock.Seize(m_pFilter);
if (FAILED(hr))
return S_OK; //?
if (IMemInputPin* pPin = m_pFilter->m_outpin_video.m_pInputPin)
{
lock.Release();
return pPin->ReceiveCanBlock();
}
return S_FALSE;
}
HRESULT Inpin::OnDisconnect()
{
HRESULT hr = m_pFilter->m_outpin_preview.OnInpinDisconnect();
assert(SUCCEEDED(hr));
hr = m_pFilter->m_outpin_video.OnInpinDisconnect();
assert(SUCCEEDED(hr));
return S_OK;
}
std::wstring Inpin::GetName() const
{
return L"YUV";
}
void Inpin::PurgePending()
{
while (!m_pending.empty())
{
IVP8Sample::Frame& f = m_pending.front();
assert(f.buf);
delete[] f.buf;
m_pending.pop_front();
}
}
HRESULT Inpin::Start()
{
m_bDiscontinuity = true;
m_bEndOfStream = false;
m_bFlush = false;
m_start_reftime = -1; //first-time flag
PurgePending();
const BITMAPINFOHEADER& bmih = GetBMIH();
const LONG w = bmih.biWidth;
assert(w > 0);
assert((w % 2) == 0); //TODO
const LONG h = labs(bmih.biHeight);
assert(h > 0);
assert((h % 2) == 0); //TODO
vpx_codec_iface_t& vp8 = vpx_codec_vp8_cx_algo;
vpx_codec_enc_cfg_t& tgt = m_cfg;
vpx_codec_err_t err = vpx_codec_enc_config_default(&vp8, &tgt, 0);
if (err != VPX_CODEC_OK)
return E_FAIL;
tgt.g_w = w;
tgt.g_h = h;
tgt.g_timebase.num = 1;
tgt.g_timebase.den = 1000; // millisecond ticks
// The filter scales to milliseconds because the encoder will generate
// altref frames that is one timestamp tick different than another frame.
// The default downstream filter has a resolution of milliseconds so set
// the encoder timebase to milliseconds.
SetConfig();
err = vpx_codec_enc_init(&m_ctx, &vp8, &tgt, 0);
if (err != VPX_CODEC_OK)
{
#ifdef _DEBUG
const char* str = vpx_codec_error_detail(&m_ctx);
str;
#endif
return E_FAIL;
}
err = SetTokenPartitions();
if (err != VPX_CODEC_OK)
{
const vpx_codec_err_t err = vpx_codec_destroy(&m_ctx);
err;
assert(err == VPX_CODEC_OK);
return E_FAIL;
}
err = SetAutoAltRef();
if (err != VPX_CODEC_OK)
{
const vpx_codec_err_t err = vpx_codec_destroy(&m_ctx);
err;
assert(err == VPX_CODEC_OK);
return E_FAIL;
}
err = SetARNRMaxFrames();
if (err != VPX_CODEC_OK)
{
const vpx_codec_err_t err = vpx_codec_destroy(&m_ctx);
err;
assert(err == VPX_CODEC_OK);
return E_FAIL;
}
err = SetARNRStrength();
if (err != VPX_CODEC_OK)
{
const vpx_codec_err_t err = vpx_codec_destroy(&m_ctx);
err;
assert(err == VPX_CODEC_OK);
return E_FAIL;
}
err = SetARNRType();
if (err != VPX_CODEC_OK)
{
const vpx_codec_err_t err = vpx_codec_destroy(&m_ctx);
err;
assert(err == VPX_CODEC_OK);
return E_FAIL;
}
err = SetCPUUsed();
if (err != VPX_CODEC_OK)
{
const vpx_codec_err_t err = vpx_codec_destroy(&m_ctx);
err;
assert(err == VPX_CODEC_OK);
return E_FAIL;
}
err = SetStaticThreshold();
if (err != VPX_CODEC_OK)
{
const vpx_codec_err_t err = vpx_codec_destroy(&m_ctx);
err;
assert(err == VPX_CODEC_OK);
return E_FAIL;
}
return S_OK;
}
void Inpin::Stop()
{
const vpx_codec_err_t err = vpx_codec_destroy(&m_ctx);
err;
assert(err == VPX_CODEC_OK);
memset(&m_ctx, 0, sizeof m_ctx);
}
HRESULT Inpin::OnApplySettings(std::wstring& msg)
{
SetConfig();
const vpx_codec_err_t err = vpx_codec_enc_config_set(&m_ctx, &m_cfg);
if (err == VPX_CODEC_OK)
{
msg.clear();
return S_OK;
}
const char* const str = vpx_codec_error_detail(&m_ctx);
const int cch = MultiByteToWideChar(
CP_UTF8,
0, //TODO: MB_ERR_INVALID_CHARS
str,
-1, //include NUL terminator in result
0,
0); //request length
assert(cch > 0);
const size_t cb = cch * sizeof(wchar_t);
wchar_t* const wstr = (wchar_t*)_alloca(cb);
const int cch2 = MultiByteToWideChar(
CP_UTF8,
0, //TODO: MB_ERR_INVALID_CHARS
str,
-1,
wstr,
cch);
cch2;
assert(cch2 > 0);
assert(cch2 == cch);
msg.assign(wstr);
return E_FAIL;
}
void Inpin::SetConfig()
{
const Filter::Config& src = m_pFilter->m_cfg;
vpx_codec_enc_cfg_t& tgt = m_cfg;
if (src.threads >= 0)
tgt.g_threads = src.threads;
if (src.error_resilient >= 0)
tgt.g_error_resilient = src.error_resilient;
switch (src.pass_mode)
{
default:
break;
case kPassModeOnePass:
tgt.g_pass = VPX_RC_ONE_PASS;
break;
case kPassModeFirstPass:
tgt.g_pass = VPX_RC_FIRST_PASS;
break;
case kPassModeLastPass:
tgt.g_pass = VPX_RC_LAST_PASS;
if (src.two_pass_stats_buflen >= 0)
{
vpx_fixed_buf& stats = tgt.rc_twopass_stats_in;
stats.buf = const_cast<BYTE*>(src.two_pass_stats_buf);
stats.sz = static_cast<size_t>(src.two_pass_stats_buflen);
}
break;
}
if (src.two_pass_vbr_bias_pct >= 0)
tgt.rc_2pass_vbr_bias_pct = src.two_pass_vbr_bias_pct;
if (src.two_pass_vbr_minsection_pct >= 0)
tgt.rc_2pass_vbr_minsection_pct = src.two_pass_vbr_minsection_pct;
if (src.two_pass_vbr_maxsection_pct >= 0)
tgt.rc_2pass_vbr_maxsection_pct = src.two_pass_vbr_maxsection_pct;
if (src.lag_in_frames >= 0)
tgt.g_lag_in_frames = src.lag_in_frames;
if (src.dropframe_thresh < 0)
tgt.rc_dropframe_thresh = 0; //disable by default
else
tgt.rc_dropframe_thresh = src.dropframe_thresh;
if (src.resize_allowed >= 0)
tgt.rc_resize_allowed = src.resize_allowed;
if (src.resize_up_thresh >= 0)
tgt.rc_resize_up_thresh = src.resize_up_thresh;
if (src.resize_down_thresh >= 0)
tgt.rc_resize_down_thresh = src.resize_down_thresh;
switch (src.end_usage)
{
default:
break;
case kEndUsageVBR:
tgt.rc_end_usage = VPX_VBR;
break;
case kEndUsageCBR:
tgt.rc_end_usage = VPX_CBR;
break;
}
if (src.target_bitrate >= 0)
tgt.rc_target_bitrate = src.target_bitrate;
if (src.min_quantizer >= 0)
tgt.rc_min_quantizer = src.min_quantizer;
if (src.max_quantizer >= 0)
tgt.rc_max_quantizer = src.max_quantizer;
if (src.undershoot_pct >= 0)
tgt.rc_undershoot_pct = src.undershoot_pct;
if (src.overshoot_pct >= 0)
tgt.rc_overshoot_pct = src.overshoot_pct;
if (src.decoder_buffer_size >= 0)
tgt.rc_buf_sz = src.decoder_buffer_size;
if (src.decoder_buffer_initial_size >= 0)
tgt.rc_buf_initial_sz = src.decoder_buffer_initial_size;
if (src.decoder_buffer_optimal_size >= 0)
tgt.rc_buf_optimal_sz = src.decoder_buffer_optimal_size;
switch (src.keyframe_mode)
{
default:
break;
case kKeyframeModeDisabled:
tgt.kf_mode = VPX_KF_DISABLED;
break;
case kKeyframeModeAuto:
tgt.kf_mode = VPX_KF_AUTO;
break;
}
if (src.keyframe_min_interval >= 0)
tgt.kf_min_dist = src.keyframe_min_interval;
if (src.keyframe_max_interval >= 0)
tgt.kf_max_dist = src.keyframe_max_interval;
}
vpx_codec_err_t Inpin::SetTokenPartitions()
{
const Filter::Config& src = m_pFilter->m_cfg;
if (src.token_partitions < 0)
return VPX_CODEC_OK;
const vp8e_token_partitions token_partitions =
static_cast<vp8e_token_partitions>(src.token_partitions);
return vpx_codec_control(
&m_ctx, VP8E_SET_TOKEN_PARTITIONS, token_partitions);
}
vpx_codec_err_t Inpin::SetAutoAltRef()
{
const Filter::Config& src = m_pFilter->m_cfg;
if (src.auto_alt_ref < 0)
return VPX_CODEC_OK;
return vpx_codec_control(
&m_ctx, VP8E_SET_ENABLEAUTOALTREF, src.auto_alt_ref);
}
vpx_codec_err_t Inpin::SetARNRMaxFrames()
{
const Filter::Config& src = m_pFilter->m_cfg;
if (src.arnr_max_frames < 0)
return VPX_CODEC_OK;
return vpx_codec_control(
&m_ctx, VP8E_SET_ARNR_MAXFRAMES, src.arnr_max_frames);
}
vpx_codec_err_t Inpin::SetARNRStrength()
{
const Filter::Config& src = m_pFilter->m_cfg;
if (src.arnr_strength < 0)
return VPX_CODEC_OK;
return vpx_codec_control(
&m_ctx, VP8E_SET_ARNR_STRENGTH, src.arnr_strength);
}
vpx_codec_err_t Inpin::SetARNRType()
{
const Filter::Config& src = m_pFilter->m_cfg;
if (src.arnr_type < 1)
return VPX_CODEC_OK;
return vpx_codec_control(
&m_ctx, VP8E_SET_ARNR_TYPE, src.arnr_type);
}
vpx_codec_err_t Inpin::SetCPUUsed()
{
const Filter::Config& src = m_pFilter->m_cfg;
if (src.cpu_used < -16 || src.cpu_used > 16)
return VPX_CODEC_OK;
return vpx_codec_control(
&m_ctx, VP8E_SET_CPUUSED, src.cpu_used);
}
vpx_codec_err_t Inpin::SetStaticThreshold()
{
const Filter::Config& src = m_pFilter->m_cfg;
if (src.static_threshold < 0)
return VPX_CODEC_OK;
return vpx_codec_control(
&m_ctx, VP8E_SET_STATIC_THRESHOLD, src.static_threshold);
}
BYTE* Inpin::ConvertYUY2ToYV12(
const BYTE* srcbuf,
ULONG w,
ULONG h)
{
assert(srcbuf);
assert((w % 2) == 0); //TODO
assert((h % 2) == 0); //TODO
const ULONG len = w*h + 2*((w+1)/2)*((h+1)/2);
if (m_buflen < len)
{
delete[] m_buf;
m_buf = 0;
m_buflen = 0;
m_buf = new (std::nothrow) BYTE[len];
assert(m_buf); //TODO
m_buflen = len;
}
BYTE* dst_y = m_buf;
BYTE* dst_v = dst_y + w*h;
BYTE* dst_u = dst_v + ((w/2)*(h/2));
const ULONG src_stride = 2*w;
const ULONG dst_y_stride = w;
const ULONG dst_uv_stride = w/2;
const BYTE* src = srcbuf;
for (ULONG i = 0; i < h; ++i)
{
const BYTE* src0_y = src;
src = src0_y + src_stride;
BYTE* dst0_y = dst_y;
dst_y = dst0_y + dst_y_stride;
for (ULONG j = 0; j < dst_y_stride; ++j)
{
*dst0_y = *src0_y;
src0_y += 2;
++dst0_y;
}
}
src = srcbuf;
for (ULONG i = 0; i < h; i += 2)
{
const BYTE* src0_u = src + 1;
const BYTE* src1_u = src0_u + src_stride;
const BYTE* src0_v = src + 3;
const BYTE* src1_v = src0_v + src_stride;
src += 2 * src_stride;
BYTE* dst0_u = dst_u;
dst_u += dst_uv_stride;
BYTE* dst0_v = dst_v;
dst_v += dst_uv_stride;
for (ULONG j = 0; j < dst_uv_stride; ++j)
{
const UINT u0 = *src0_u;
const UINT u1 = *src1_u;
const UINT src_u = (u0 + u1) / 2; //?
const UINT v0 = *src0_v;
const UINT v1 = *src1_v;
const UINT src_v = (v0 + v1) / 2; //?
*dst0_u = static_cast<BYTE>(src_u);
*dst0_v = static_cast<BYTE>(src_v);
src0_u += 4;
src1_u += 4;
src0_v += 4;
src1_v += 4;
++dst0_u;
++dst0_v;
}
}
return m_buf;
}
} //end namespace VP8EncoderLib