/*
* (C) 2003-2006 Gabest
* (C) 2006-2013 see Authors.txt
*
* This file is part of MPC-HC.
*
* MPC-HC is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* MPC-HC is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include "stdafx.h"
#include "OggSplitter.h"
#include "../../../DSUtil/DSUtil.h"
#include "../../../DSUtil/GolombBuffer.h"
#ifdef STANDALONE_FILTER
#include
#endif
#include "moreuuids.h"
# define TH_VERSION_CHECK(version_major,version_minor,version_subminor,_maj,_min,_sub) \
(version_major>(_maj)||version_major==(_maj)&& \
(version_minor>(_min)||version_minor==(_min)&& \
version_subminor>=(_sub)))
#ifdef STANDALONE_FILTER
const AMOVIESETUP_MEDIATYPE sudPinTypesIn[] = {
{&MEDIATYPE_Stream, &MEDIASUBTYPE_NULL},
};
const AMOVIESETUP_PIN sudpPins[] = {
{L"Input", FALSE, FALSE, FALSE, FALSE, &CLSID_NULL, nullptr, _countof(sudPinTypesIn), sudPinTypesIn},
{L"Output", FALSE, TRUE, FALSE, FALSE, &CLSID_NULL, nullptr, 0, nullptr}
};
const AMOVIESETUP_FILTER sudFilter[] = {
{&__uuidof(COggSplitterFilter), OggSplitterName, MERIT_NORMAL + 1, _countof(sudpPins), sudpPins, CLSID_LegacyAmFilterCategory},
{&__uuidof(COggSourceFilter), OggSourceName, MERIT_NORMAL + 1, 0, nullptr, CLSID_LegacyAmFilterCategory},
};
CFactoryTemplate g_Templates[] = {
{sudFilter[0].strName, sudFilter[0].clsID, CreateInstance, nullptr, &sudFilter[0]},
{sudFilter[1].strName, sudFilter[1].clsID, CreateInstance, nullptr, &sudFilter[1]},
};
int g_cTemplates = _countof(g_Templates);
STDAPI DllRegisterServer()
{
RegisterSourceFilter(
CLSID_AsyncReader,
MEDIASUBTYPE_Ogg,
_T("0,4,,4F676753"), // OggS
_T(".ogg"), _T(".ogm"), nullptr);
return AMovieDllRegisterServer2(TRUE);
}
STDAPI DllUnregisterServer()
{
UnRegisterSourceFilter(MEDIASUBTYPE_Ogg);
return AMovieDllRegisterServer2(FALSE);
}
#include "../../FilterApp.h"
CFilterApp theApp;
#endif
//
// bitstream
//
class bitstream
{
BYTE* m_p;
int m_len, m_pos;
public:
bitstream(BYTE* p, int len, bool rev = false) : m_p(p), m_len(len * 8) {
m_pos = !rev ? 0 : len * 8;
}
bool hasbits(int cnt) {
int pos = m_pos + cnt;
return (pos >= 0 && pos < m_len);
}
unsigned int showbits(int cnt) { // a bit unclean, but works and can read backwards too! :P
if (!hasbits(cnt)) {
ASSERT(0);
return 0;
}
unsigned int ret = 0, off = 0;
BYTE* p = m_p;
if (cnt < 0) {
p += (m_pos + cnt) >> 3;
off = (m_pos + cnt) & 7;
cnt = abs(cnt);
ret = (*p++ & (~0 << off)) >> off;
off = 8 - off;
cnt -= off;
} else {
p += m_pos >> 3;
off = m_pos & 7;
ret = (*p++ >> off) & ((1 << min(cnt, 8)) - 1);
off = 0;
cnt -= 8 - off;
}
while (cnt > 0) {
ret |= (*p++ & ((1 << min(cnt, 8)) - 1)) << off;
off += 8;
cnt -= 8;
}
return ret;
}
unsigned int getbits(int cnt) {
unsigned int ret = showbits(cnt);
m_pos += cnt;
return ret;
}
};
//
// COggSplitterFilter
//
COggSplitterFilter::COggSplitterFilter(LPUNKNOWN pUnk, HRESULT* phr)
: CBaseSplitterFilter(NAME("COggSplitterFilter"), pUnk, phr, __uuidof(this))
{
}
COggSplitterFilter::~COggSplitterFilter()
{
}
STDMETHODIMP COggSplitterFilter::QueryFilterInfo(FILTER_INFO* pInfo)
{
CheckPointer(pInfo, E_POINTER);
ValidateReadWritePtr(pInfo, sizeof(FILTER_INFO));
if (m_pName && m_pName[0] == L'M' && m_pName[1] == L'P' && m_pName[2] == L'C') {
(void)StringCchCopyW(pInfo->achName, NUMELMS(pInfo->achName), m_pName);
} else {
wcscpy_s(pInfo->achName, OggSourceName);
}
pInfo->pGraph = m_pGraph;
if (m_pGraph) {
m_pGraph->AddRef();
}
return S_OK;
}
HRESULT COggSplitterFilter::CreateOutputs(IAsyncReader* pAsyncReader)
{
CheckPointer(pAsyncReader, E_POINTER);
HRESULT hr = E_FAIL;
m_pFile.Free();
m_pFile.Attach(DEBUG_NEW COggFile(pAsyncReader, hr));
if (!m_pFile) {
return E_OUTOFMEMORY;
}
if (FAILED(hr)) {
m_pFile.Free();
return hr;
}
m_rtNewStart = m_rtCurrent = 0;
m_rtNewStop = m_rtStop = 0;
m_rtDuration = 0;
m_pFile->Seek(0);
OggPage page;
for (int i = 0, nWaitForMore = 0; m_pFile->Read(page), i < 100; i++) {
BYTE* p = page.GetData();
if (!p) {
break;
}
if (!(page.m_hdr.header_type_flag & OggPageHeader::continued)) {
if (!memcmp(p, "fishead", 7) || !memcmp(p, "fisbone", 7)) {
continue;
}
BYTE type = *p++;
CStringW name;
name.Format(L"Stream %d", i);
HRESULT hr2;
if (type >= 0x80 && type <= 0x82 && !memcmp(p, "theora", 6)) {
if (type == 0x80) {
name.Format(L"Theora %d", i);
CAutoPtr pPinOut;
pPinOut.Attach(DEBUG_NEW COggTheoraOutputPin(page.GetData(), name, this, this, &hr2));
AddOutputPin(page.m_hdr.bitstream_serial_number, pPinOut);
nWaitForMore++;
}
} else if (type == 1 && (page.m_hdr.header_type_flag & OggPageHeader::first)) {
CAutoPtr pPinOut;
if (!memcmp(p, "vorbis", 6)) {
if ((*(OggVorbisIdHeader*)(p + 6)).audio_sample_rate == 0) {
return E_FAIL; // fix crash on broken files
}
name.Format(L"Vorbis %d", i);
pPinOut.Attach(DEBUG_NEW COggVorbisOutputPin((OggVorbisIdHeader*)(p + 6), name, this, this, &hr2));
nWaitForMore++;
} else if (!memcmp(p, "video", 5)) {
name.Format(L"Video %d", i);
pPinOut.Attach(DEBUG_NEW COggVideoOutputPin((OggStreamHeader*)p, name, this, this, &hr2));
} else if (!memcmp(p, "audio", 5)) {
name.Format(L"Audio %d", i);
pPinOut.Attach(DEBUG_NEW COggAudioOutputPin((OggStreamHeader*)p, name, this, this, &hr2));
} else if (!memcmp(p, "text", 4)) {
name.Format(L"Text %d", i);
pPinOut.Attach(DEBUG_NEW COggTextOutputPin((OggStreamHeader*)p, name, this, this, &hr2));
} else if (!memcmp(p, "Direct Show Samples embedded in Ogg", 35)) {
name.Format(L"DirectShow %d", i);
pPinOut.Attach(DEBUG_NEW COggDirectShowOutputPin((AM_MEDIA_TYPE*)(p + 35 + sizeof(GUID)), name, this, this, &hr2));
}
AddOutputPin(page.m_hdr.bitstream_serial_number, pPinOut);
} else if (type == 3 && !memcmp(p, "vorbis", 6)) {
if (COggSplitterOutputPin* pOggPin =
dynamic_cast(GetOutputPin(page.m_hdr.bitstream_serial_number))) {
pOggPin->AddComment(p + 6, (int)page.GetCount() - 6 - 1);
}
} else if (type == 0x7F && page.GetCount() > 12 && *(long*)(p + 8) == 0x43614C66) { // Flac
// Ogg Flac : method 1
CAutoPtr pPinOut;
name.Format(L"FLAC %d", i);
pPinOut.Attach(DEBUG_NEW COggFlacOutputPin(p + 12, (int)page.GetCount() - 14, name, this, this, &hr2));
AddOutputPin(page.m_hdr.bitstream_serial_number, pPinOut);
} else if (*(long*)(p - 1) == 0x43614C66) {
//bFlac = true;
//nWaitForMore++;
if (m_pFile->Read(page)) {
CAutoPtr pPinOut;
name.Format(L"FLAC %d", i);
p = page.GetData();
pPinOut.Attach(DEBUG_NEW COggFlacOutputPin(p, (int)page.GetCount(), name, this, this, &hr2));
AddOutputPin(page.m_hdr.bitstream_serial_number, pPinOut);
}
} else if (!(type & 1) && nWaitForMore == 0) {
break;
}
}
if (COggTheoraOutputPin* ptr = dynamic_cast(GetOutputPin(page.m_hdr.bitstream_serial_number))) {
ptr->UnpackInitPage(page);
if (ptr->IsInitialized()) {
nWaitForMore--;
}
}
if (COggVorbisOutputPin* ptr = dynamic_cast(GetOutputPin(page.m_hdr.bitstream_serial_number))) {
ptr->UnpackInitPage(page);
if (ptr->IsInitialized()) {
nWaitForMore--;
}
}
}
if (m_pOutputs.IsEmpty()) {
return E_FAIL;
}
if (m_pFile->IsRandomAccess()) {
m_pFile->Seek(max(m_pFile->GetLength() - MAX_PROBE_SIZE, 0));
OggPage ppage;
while (m_pFile->Read(ppage)) {
COggSplitterOutputPin* pOggPin = dynamic_cast(GetOutputPin(ppage.m_hdr.bitstream_serial_number));
if (!pOggPin || ppage.m_hdr.granule_position == -1) {
continue;
}
REFERENCE_TIME rt = pOggPin->GetRefTime(ppage.m_hdr.granule_position);
m_rtDuration = max(rt, m_rtDuration);
}
}
m_rtNewStart = m_rtCurrent = 0;
m_rtNewStop = m_rtStop = m_rtDuration;
// comments
{
CAtlMap> tagmap;
tagmap[L"TITLE"] = L"TITL";
tagmap[L"ARTIST"] = L"AUTH"; // not quite
tagmap[L"COPYRIGHT"] = L"CPYR";
tagmap[L"DESCRIPTION"] = L"DESC";
POSITION pos2 = tagmap.GetStartPosition();
while (pos2) {
CStringW oggtag, dsmtag;
tagmap.GetNextAssoc(pos2, oggtag, dsmtag);
POSITION pos = m_pOutputs.GetHeadPosition();
while (pos) {
COggSplitterOutputPin* pOggPin = dynamic_cast((CBaseOutputPin*)m_pOutputs.GetNext(pos));
if (!pOggPin) {
continue;
}
CStringW value = pOggPin->GetComment(oggtag);
if (!value.IsEmpty()) {
SetProperty(dsmtag, value);
break;
}
}
}
POSITION pos = m_pOutputs.GetHeadPosition();
while (pos && !ChapGetCount()) {
COggSplitterOutputPin* pOggPin = dynamic_cast((CBaseOutputPin*)m_pOutputs.GetNext(pos));
if (!pOggPin) {
continue;
}
for (int i = 1; pOggPin; i++) {
CStringW key;
key.Format(L"CHAPTER%02d", i);
CStringW time = pOggPin->GetComment(key);
if (time.IsEmpty()) {
break;
}
key.Format(L"CHAPTER%02dNAME", i);
CStringW name = pOggPin->GetComment(key);
if (name.IsEmpty()) {
name.Format(L"Chapter %d", i);
}
int h, m, s, ms;
WCHAR c;
if (7 != swscanf_s(time, L"%d%c%d%c%d%c%d", &h, &c, 1, &m, &c, 1, &s, &c, 1, &ms)) {
break;
}
REFERENCE_TIME rt = ((((REFERENCE_TIME)h * 60 + m) * 60 + s) * 1000 + ms) * 10000;
ChapAppend(rt, name);
}
}
}
return !m_pOutputs.IsEmpty() ? S_OK : E_FAIL;
}
bool COggSplitterFilter::DemuxInit()
{
SetThreadName((DWORD) - 1, "COggSplitterFilter");
if (!m_pFile) {
return false;
}
return true;
}
void COggSplitterFilter::DemuxSeek(REFERENCE_TIME rt)
{
if (rt <= 0) {
m_pFile->Seek(0);
} else if (m_rtDuration > 0) {
// oh, the horror...
__int64 len = m_pFile->GetLength();
__int64 startpos = len * rt / m_rtDuration;
//__int64 diff = 0;
REFERENCE_TIME rtMinDiff = _I64_MAX;
for (;;) {
__int64 endpos = startpos;
REFERENCE_TIME rtPos = -1;
OggPage page;
m_pFile->Seek(startpos);
while (m_pFile->Read(page, false)) {
if (page.m_hdr.granule_position == -1) {
continue;
}
COggSplitterOutputPin* pOggPin = dynamic_cast(GetOutputPin(page.m_hdr.bitstream_serial_number));
if (!pOggPin) {
ASSERT(0);
continue;
}
rtPos = pOggPin->GetRefTime(page.m_hdr.granule_position);
endpos = m_pFile->GetPos();
break;
}
__int64 rtDiff = rtPos - rt;
if (rtDiff < 0) {
rtDiff = -rtDiff;
if (rtDiff < 1000000 || rtDiff >= rtMinDiff) {
m_pFile->Seek(startpos);
break;
}
rtMinDiff = rtDiff;
}
__int64 newpos = startpos;
if (rtPos < rt && rtPos < m_rtDuration) {
newpos = startpos + (__int64)((1.0 * (rt - rtPos) / (m_rtDuration - rtPos)) * (len - startpos)) + 1024;
if (newpos < endpos) {
newpos = endpos + 1024;
}
} else if (rtPos > rt && rtPos > 0) {
newpos = startpos - (__int64)((1.0 * (rtPos - rt) / (rtPos - 0)) * (startpos - 0)) - 1024;
if (newpos >= startpos) {
newpos = startpos - 1024;
}
} else if (rtPos == rt) {
m_pFile->Seek(startpos);
break;
} else {
ASSERT(0);
m_pFile->Seek(0);
break;
}
//diff = newpos - startpos;
startpos = max(min(newpos, len), 0);
}
m_pFile->Seek(startpos);
POSITION pos = m_pOutputs.GetHeadPosition();
while (pos) {
COggSplitterOutputPin* pPin = dynamic_cast(static_cast(m_pOutputs.GetNext(pos)));
if (!dynamic_cast(pPin) && !dynamic_cast(pPin)) {
continue;
}
bool fKeyFrameFound = false, fSkipKeyFrame = true;
__int64 endpos = _I64_MAX;
while (!(fKeyFrameFound && !fSkipKeyFrame) && startpos > 0) {
OggPage page;
while (!(fKeyFrameFound && !fSkipKeyFrame) && m_pFile->GetPos() < endpos && m_pFile->Read(page)) {
if (page.m_hdr.granule_position == -1) {
continue;
}
if (pPin != dynamic_cast(GetOutputPin(page.m_hdr.bitstream_serial_number))) {
continue;
}
if (pPin->GetRefTime(page.m_hdr.granule_position) > rt) {
break;
}
if (!fKeyFrameFound) {
pPin->UnpackPage(page);
CAutoPtr p;
while (p = pPin->GetPacket()) {
if (p->bSyncPoint) {
fKeyFrameFound = true;
fSkipKeyFrame = p->fSkip;
}
}
if (fKeyFrameFound) {
break;
}
} else {
pPin->UnpackPage(page);
CAutoPtr p;
while (p = pPin->GetPacket()) {
if (!p->fSkip) {
fSkipKeyFrame = false;
break;
}
}
}
}
if (!(fKeyFrameFound && !fSkipKeyFrame)) {
endpos = startpos;
startpos = max(startpos - 10 * MAX_PROBE_SIZE, 0);
}
m_pFile->Seek(startpos);
}
#ifdef _DEBUG
// verify kf
{
fKeyFrameFound = false;
OggPage page;
while (!fKeyFrameFound && m_pFile->Read(page)) {
if (page.m_hdr.granule_position == -1) {
continue;
}
if (pPin != dynamic_cast(GetOutputPin(page.m_hdr.bitstream_serial_number))) {
continue;
}
REFERENCE_TIME rtPos = pPin->GetRefTime(page.m_hdr.granule_position);
if (rtPos > rt) {
break;
}
pPin->UnpackPage(page);
CAutoPtr p;
while (p = pPin->GetPacket()) {
if (p->bSyncPoint) {
fKeyFrameFound = true;
break;
}
}
}
ASSERT(fKeyFrameFound);
m_pFile->Seek(startpos);
}
#endif
break;
}
}
}
bool COggSplitterFilter::DemuxLoop()
{
HRESULT hr = S_OK;
OggPage page;
while (SUCCEEDED(hr) && !CheckRequest(nullptr) && m_pFile->Read(page, true, GetRequestHandle())) {
COggSplitterOutputPin* pOggPin = dynamic_cast(GetOutputPin(page.m_hdr.bitstream_serial_number));
if (!pOggPin) {
ASSERT(0);
continue;
}
if (!pOggPin->IsConnected()) {
continue;
}
if (FAILED(hr = pOggPin->UnpackPage(page))) {
ASSERT(0);
break;
}
CAutoPtr p;
while (!CheckRequest(nullptr) && SUCCEEDED(hr) && (p = pOggPin->GetPacket())) {
if (!p->fSkip) {
hr = DeliverPacket(p);
}
}
}
return true;
}
//
// COggSourceFilter
//
COggSourceFilter::COggSourceFilter(LPUNKNOWN pUnk, HRESULT* phr)
: COggSplitterFilter(pUnk, phr)
{
m_clsid = __uuidof(this);
m_pInput.Free();
}
//
// COggSplitterOutputPin
//
COggSplitterOutputPin::COggSplitterOutputPin(LPCWSTR pName, CBaseFilter* pFilter, CCritSec* pLock, HRESULT* phr)
: CBaseSplitterOutputPin(pName, pFilter, pLock, phr)
{
ResetState((DWORD) - 1);
}
void COggSplitterOutputPin::AddComment(BYTE* p, int len)
{
bitstream bs(p, len);
bs.getbits(bs.getbits(32) * 8);
for (int n = bs.getbits(32); n-- > 0;) {
CStringA str;
for (int len2 = bs.getbits(32); len2-- > 0;) {
str += (CHAR)bs.getbits(8);
}
CAtlList sl;
Explode(str, sl, '=', 2);
if (sl.GetCount() == 2) {
CAutoPtr ptr(DEBUG_NEW CComment(UTF8To16(sl.GetHead()), UTF8To16(sl.GetTail())));
if (ptr->m_key == L"LANGUAGE") {
CString lang = ISO6392ToLanguage(sl.GetTail()), iso6392 = LanguageToISO6392(CString(ptr->m_value));
if (ptr->m_value.GetLength() == 3 && !lang.IsEmpty()) {
SetName(CStringW(lang));
SetProperty(L"LANG", ptr->m_value);
} else if (!iso6392.IsEmpty()) {
SetName(ptr->m_value);
SetProperty(L"LANG", CStringW(iso6392));
} else {
SetName(ptr->m_value);
SetProperty(L"NAME", ptr->m_value);
}
}
m_pComments.AddTail(ptr);
}
}
ASSERT(bs.getbits(1) == 1);
}
CStringW COggSplitterOutputPin::GetComment(CStringW key)
{
key.MakeUpper();
CAtlList sl;
POSITION pos = m_pComments.GetHeadPosition();
while (pos) {
CComment* p = m_pComments.GetNext(pos);
if (key == p->m_key) {
sl.AddTail(p->m_value);
}
}
return Implode(sl, ';');
}
void COggSplitterOutputPin::ResetState(DWORD seqnum)
{
CAutoLock csAutoLock(&m_csPackets);
m_packets.RemoveAll();
m_lastpacket.Free();
m_lastseqnum = seqnum;
m_rtLast = 0;
m_fSkip = true;
}
HRESULT COggSplitterOutputPin::UnpackPage(OggPage& page)
{
if (m_lastseqnum != page.m_hdr.page_sequence_number - 1) {
ResetState(page.m_hdr.page_sequence_number);
return S_FALSE; // FIXME
} else {
m_lastseqnum = page.m_hdr.page_sequence_number;
}
POSITION first = page.m_lens.GetHeadPosition();
while (first && page.m_lens.GetAt(first) == 255) {
page.m_lens.GetNext(first);
}
if (!first) {
first = page.m_lens.GetTailPosition();
}
POSITION last = page.m_lens.GetTailPosition();
while (last && page.m_lens.GetAt(last) == 255) {
page.m_lens.GetPrev(last);
}
if (!last) {
last = page.m_lens.GetTailPosition();
}
BYTE* pData = page.GetData();
int i = 0, j = 0, len = 0;
for (POSITION pos = page.m_lens.GetHeadPosition(); pos; page.m_lens.GetNext(pos)) {
len = page.m_lens.GetAt(pos);
j += len;
if (len < 255 || pos == page.m_lens.GetTailPosition()) {
if (first == pos && (page.m_hdr.header_type_flag & OggPageHeader::continued)) {
// ASSERT(m_lastpacket);
if (m_lastpacket) {
size_t size = m_lastpacket->GetCount();
m_lastpacket->SetCount(size + j - i);
memcpy(m_lastpacket->GetData() + size, pData + i, j - i);
CAutoLock csAutoLock(&m_csPackets);
if (len < 255) {
m_packets.AddTail(m_lastpacket);
}
}
} else {
CAutoPtr p(DEBUG_NEW OggPacket());
if (last == pos && page.m_hdr.granule_position != -1) {
p->bDiscontinuity = m_fSkip;
REFERENCE_TIME rtLast = m_rtLast;
m_rtLast = GetRefTime(page.m_hdr.granule_position);
// some bad encodings have a +/-1 frame difference from the normal timeline,
// but these seem to cancel eachother out nicely so we can just ignore them
// to make it play a bit more smooth.
if (abs(rtLast - m_rtLast) == GetRefTime(1)) {
m_rtLast = rtLast; // FIXME
}
m_fSkip = false;
}
p->TrackNumber = page.m_hdr.bitstream_serial_number;
if (S_OK == UnpackPacket(p, pData + i, j - i)) {
//if (p->TrackNumber == 1)
//TRACE(_T("[%d]: %d, %I64d -> %I64d (skip=%d, disc=%d, sync=%d)\n"),
// (int)p->TrackNumber, p->GetCount(), p->rtStart, p->rtStop,
// (int)m_fSkip, (int)p->bDiscontinuity, (int)p->bSyncPoint);
if (p->rtStart <= p->rtStop && p->rtStop <= p->rtStart + 10000000i64 * 60) {
CAutoLock csAutoLock(&m_csPackets);
m_rtLast = p->rtStop;
p->fSkip = m_fSkip;
if (len < 255) {
m_packets.AddTail(p);
} else {
m_lastpacket = p;
}
} else {
ASSERT(0);
}
}
}
i = j;
}
}
return S_OK;
}
CAutoPtr COggSplitterOutputPin::GetPacket()
{
CAutoPtr p;
CAutoLock csAutoLock(&m_csPackets);
if (m_packets.GetCount()) {
p = m_packets.RemoveHead();
}
return p;
}
HRESULT COggSplitterOutputPin::DeliverEndFlush()
{
ResetState();
return __super::DeliverEndFlush();
}
HRESULT COggSplitterOutputPin::DeliverNewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate)
{
ResetState();
return __super::DeliverNewSegment(tStart, tStop, dRate);
}
//
// COggVorbisOutputPin
//
COggVorbisOutputPin::COggVorbisOutputPin(OggVorbisIdHeader* h, LPCWSTR pName, CBaseFilter* pFilter, CCritSec* pLock, HRESULT* phr)
: COggSplitterOutputPin(pName, pFilter, pLock, phr)
{
m_audio_sample_rate = h->audio_sample_rate;
m_blocksize[0] = 1 << h->blocksize_0;
m_blocksize[1] = 1 << h->blocksize_1;
m_lastblocksize = 0;
CMediaType mt;
mt.InitMediaType();
mt.majortype = MEDIATYPE_Audio;
mt.subtype = MEDIASUBTYPE_Vorbis;
mt.formattype = FORMAT_VorbisFormat;
VORBISFORMAT* vf = (VORBISFORMAT*)mt.AllocFormatBuffer(sizeof(VORBISFORMAT));
memset(mt.Format(), 0, mt.FormatLength());
vf->nChannels = h->audio_channels;
vf->nSamplesPerSec = h->audio_sample_rate;
vf->nAvgBitsPerSec = h->bitrate_nominal;
vf->nMinBitsPerSec = h->bitrate_minimum;
vf->nMaxBitsPerSec = h->bitrate_maximum;
vf->fQuality = -1;
mt.SetSampleSize(8192);
m_mts.Add(mt);
mt.InitMediaType();
mt.majortype = MEDIATYPE_Audio;
mt.subtype = MEDIASUBTYPE_Vorbis2;
mt.formattype = FORMAT_VorbisFormat2;
VORBISFORMAT2* vf2 = (VORBISFORMAT2*)mt.AllocFormatBuffer(sizeof(VORBISFORMAT2));
memset(mt.Format(), 0, mt.FormatLength());
vf2->Channels = h->audio_channels;
vf2->SamplesPerSec = h->audio_sample_rate;
mt.SetSampleSize(8192);
m_mts.InsertAt(0, mt);
}
HRESULT COggVorbisOutputPin::UnpackInitPage(OggPage& page)
{
HRESULT hr = __super::UnpackPage(page);
while (m_packets.GetCount()) {
Packet* p = m_packets.GetHead();
if (p->GetCount() >= 6 && p->GetAt(0) == 0x05) {
// yeah, right, we are going to be parsing this backwards! :P
bitstream bs(p->GetData(), (int)p->GetCount(), true);
while (bs.hasbits(-1) && bs.getbits(-1) != 1) {
;
}
for (int cnt = 0; bs.hasbits(-8 - 16 - 16 - 1 - 6); cnt++) {
unsigned int modes = bs.showbits(-6) + 1;
unsigned int mapping = bs.getbits(-8);
unsigned int transformtype = bs.getbits(-16);
unsigned int windowtype = bs.getbits(-16);
unsigned int blockflag = bs.getbits(-1);
UNREFERENCED_PARAMETER(mapping);
if (transformtype != 0 || windowtype != 0) {
ASSERT(modes == cnt);
UNREFERENCED_PARAMETER(modes);
break;
}
m_blockflags.InsertAt(0, !!blockflag);
}
}
int cnt = (int)m_initpackets.GetCount();
if (cnt < 3 && (p->GetCount() >= 6 && p->GetAt(0) == 1 + cnt * 2)) {
VORBISFORMAT2* vf2 = (VORBISFORMAT2*)m_mts[0].Format();
vf2->HeaderSize[cnt] = p->GetCount();
int len = m_mts[0].FormatLength();
memcpy(m_mts[0].ReallocFormatBuffer(len + p->GetCount()) + len, p->GetData(), p->GetCount());
}
m_initpackets.AddTail(m_packets.RemoveHead());
}
return hr;
}
REFERENCE_TIME COggVorbisOutputPin::GetRefTime(__int64 granule_position)
{
REFERENCE_TIME rt = granule_position * 10000000 / m_audio_sample_rate;
return rt;
}
HRESULT COggVorbisOutputPin::UnpackPacket(CAutoPtr& p, BYTE* pData, int len)
{
if (len > 0 && m_blockflags.GetCount()) {
bitstream bs(pData, len);
if (bs.getbits(1) == 0) {
unsigned int x = (unsigned int)m_blockflags.GetCount() - 1;
int n = 0;
while (x) {
n++;
x >>= 1;
}
DWORD blocksize = m_blocksize[m_blockflags[bs.getbits(n)] ? 1 : 0];
if (m_lastblocksize) {
m_rtLast += GetRefTime((m_lastblocksize + blocksize) >> 2);
}
m_lastblocksize = blocksize;
}
}
p->bSyncPoint = TRUE;
p->rtStart = m_rtLast;
p->rtStop = m_rtLast + 1;
p->SetData(pData, len);
return S_OK;
}
HRESULT COggVorbisOutputPin::DeliverPacket(CAutoPtr p)
{
if (p->GetCount() > 0 && (p->GetAt(0) & 1)) {
return S_OK;
}
return __super::DeliverPacket(p);
}
HRESULT COggVorbisOutputPin::DeliverNewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate)
{
HRESULT hr = __super::DeliverNewSegment(tStart, tStop, dRate);
m_lastblocksize = 0;
if (m_mt.subtype == MEDIASUBTYPE_Vorbis) {
POSITION pos = m_initpackets.GetHeadPosition();
while (pos) {
Packet* pi = m_initpackets.GetNext(pos);
CAutoPtr p(DEBUG_NEW OggPacket());
p->TrackNumber = pi->TrackNumber;
p->bDiscontinuity = p->bSyncPoint = FALSE;//TRUE;
p->rtStart = p->rtStop = 0;
p->Copy(*pi);
__super::DeliverPacket(p);
}
}
return hr;
}
//
// COggFlacOutputPin
//
COggFlacOutputPin::COggFlacOutputPin(BYTE* h, int nCount, LPCWSTR pName, CBaseFilter* pFilter, CCritSec* pLock, HRESULT* phr)
: COggSplitterOutputPin(pName, pFilter, pLock, phr)
{
CGolombBuffer Buffer(h, nCount);
Buffer.BitRead(1); // Last-metadata-block flag
if (Buffer.BitRead(7) != 0) { // Should be a STREAMINFO block
if (phr) {
*phr = VFW_E_INVALID_FILE_FORMAT;
}
return;
}
Buffer.BitRead(24); // Length (in bytes) of metadata to follow
Buffer.ReadShort(); // m_nMinBlocksize
Buffer.ReadShort(); // m_nMaxBlocksize
Buffer.BitRead(24); // m_nMinFrameSize
Buffer.BitRead(24); // m_nMaxFrameSize
m_nSamplesPerSec = (int)Buffer.BitRead(20);
m_nChannels = (int)Buffer.BitRead(3) + 1;
m_wBitsPerSample = (WORD)Buffer.BitRead(5) + 1;
Buffer.BitRead(36); // m_i64TotalNumSamples
m_nAvgBytesPerSec = (m_nChannels * (m_wBitsPerSample >> 3)) * m_nSamplesPerSec;
CMediaType mt;
mt.majortype = MEDIATYPE_Audio;
mt.subtype = MEDIASUBTYPE_FLAC_FRAMED;
mt.formattype = FORMAT_WaveFormatEx;
WAVEFORMATEX* wfe = (WAVEFORMATEX*)mt.AllocFormatBuffer(sizeof(WAVEFORMATEX));
memset(wfe, 0, sizeof(WAVEFORMATEX));
wfe->cbSize = sizeof(WAVEFORMATEX);
wfe->wFormatTag = WAVE_FORMAT_FLAC;
wfe->nSamplesPerSec = m_nSamplesPerSec;
wfe->nAvgBytesPerSec = m_nAvgBytesPerSec;
wfe->nChannels = m_nChannels;
wfe->nBlockAlign = 1;
wfe->wBitsPerSample = m_wBitsPerSample;
m_mts.InsertAt(0, mt);
*phr = S_OK;
}
REFERENCE_TIME COggFlacOutputPin::GetRefTime(__int64 granule_position)
{
REFERENCE_TIME rt = (granule_position * UNITS) / m_nSamplesPerSec;
return rt;
}
HRESULT COggFlacOutputPin::UnpackPacket(CAutoPtr& p, BYTE* pData, int len)
{
if (pData[0] != 0xFF || (pData[1] & 0xFE) != 0xF8) {
return S_FALSE;
}
p->bSyncPoint = TRUE;
p->rtStart = m_rtLast;
p->rtStop = m_rtLast + 1; // TODO : find packet duration !
p->SetData(pData, len);
return S_OK;
}
HRESULT COggFlacOutputPin::DeliverPacket(CAutoPtr p)
{
if (p->GetCount() > 0 && (p->GetAt(0) & 1)) {
return S_OK;
}
return __super::DeliverPacket(p);
}
HRESULT COggFlacOutputPin::DeliverNewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate)
{
HRESULT hr = __super::DeliverNewSegment(tStart, tStop, dRate);
m_lastblocksize = 0;
if (m_mt.subtype == MEDIASUBTYPE_FLAC_FRAMED) {
POSITION pos = m_initpackets.GetHeadPosition();
while (pos) {
Packet* pi = m_initpackets.GetNext(pos);
CAutoPtr p(DEBUG_NEW OggPacket());
p->TrackNumber = pi->TrackNumber;
p->bDiscontinuity = p->bSyncPoint = FALSE;//TRUE;
p->rtStart = p->rtStop = 0;
p->Copy(*pi);
__super::DeliverPacket(p);
}
}
return hr;
}
//
// COggDirectShowOutputPin
//
COggDirectShowOutputPin::COggDirectShowOutputPin(AM_MEDIA_TYPE* pmt, LPCWSTR pName, CBaseFilter* pFilter, CCritSec* pLock, HRESULT* phr)
: COggSplitterOutputPin(pName, pFilter, pLock, phr)
{
CMediaType mt;
memcpy((AM_MEDIA_TYPE*)&mt, pmt, FIELD_OFFSET(AM_MEDIA_TYPE, pUnk));
mt.SetFormat((BYTE*)(pmt + 1), pmt->cbFormat);
mt.SetSampleSize(1);
if (mt.majortype == MEDIATYPE_Video) { // TODO: find samples for audio and find out what to return in GetRefTime...
m_mts.Add(mt);
}
}
REFERENCE_TIME COggDirectShowOutputPin::GetRefTime(__int64 granule_position)
{
REFERENCE_TIME rt = 0;
if (m_mt.majortype == MEDIATYPE_Video) {
rt = granule_position * ((VIDEOINFOHEADER*)m_mt.Format())->AvgTimePerFrame;
} else if (m_mt.majortype == MEDIATYPE_Audio) {
rt = granule_position; // ((WAVEFORMATEX*)m_mt.Format())-> // TODO
}
return rt;
}
HRESULT COggDirectShowOutputPin::UnpackPacket(CAutoPtr& p, BYTE* pData, int len)
{
int i = 0;
BYTE hdr = pData[i++];
if (!(hdr & 1)) {
// TODO: verify if this was still present in the old format (haven't found one sample yet)
BYTE nLenBytes = (hdr >> 6) | ((hdr & 2) << 1);
__int64 Length = 0;
for (int j = 0; j < nLenBytes; j++) {
Length |= (__int64)pData[i++] << (j << 3);
}
if (len < i) {
ASSERT(0);
return E_FAIL;
}
p->bSyncPoint = !!(hdr & 8);
p->rtStart = m_rtLast;
p->rtStop = m_rtLast + (nLenBytes ? GetRefTime(Length) : GetRefTime(1));
p->SetData(&pData[i], len - i);
return S_OK;
}
return S_FALSE;
}
//
// COggStreamOutputPin
//
COggStreamOutputPin::COggStreamOutputPin(OggStreamHeader* h, LPCWSTR pName, CBaseFilter* pFilter, CCritSec* pLock, HRESULT* phr)
: COggSplitterOutputPin(pName, pFilter, pLock, phr)
{
m_time_unit = h->time_unit;
m_samples_per_unit = h->samples_per_unit;
m_default_len = h->default_len;
}
REFERENCE_TIME COggStreamOutputPin::GetRefTime(__int64 granule_position)
{
return granule_position * m_time_unit / m_samples_per_unit;
}
HRESULT COggStreamOutputPin::UnpackPacket(CAutoPtr& p, BYTE* pData, int len)
{
int i = 0;
BYTE hdr = pData[i++];
if (!(hdr & 1)) {
BYTE nLenBytes = (hdr >> 6) | ((hdr & 2) << 1);
__int64 Length = 0;
for (int j = 0; j < nLenBytes; j++) {
Length |= (__int64)pData[i++] << (j << 3);
}
if (len < i) {
ASSERT(0);
return E_FAIL;
}
p->bSyncPoint = !!(hdr & 8);
p->rtStart = m_rtLast;
p->rtStop = m_rtLast + (nLenBytes ? GetRefTime(Length) : GetRefTime(m_default_len));
p->SetData(&pData[i], len - i);
return S_OK;
}
return S_FALSE;
}
//
// COggVideoOutputPin
//
COggVideoOutputPin::COggVideoOutputPin(OggStreamHeader* h, LPCWSTR pName, CBaseFilter* pFilter, CCritSec* pLock, HRESULT* phr)
: COggStreamOutputPin(h, pName, pFilter, pLock, phr)
{
int extra = (int)h->size - sizeof(OggStreamHeader);
extra = max(extra, 0);
CMediaType mt;
mt.majortype = MEDIATYPE_Video;
mt.subtype = FOURCCMap(MAKEFOURCC(h->subtype[0], h->subtype[1], h->subtype[2], h->subtype[3]));
mt.formattype = FORMAT_VideoInfo;
VIDEOINFOHEADER* pvih = (VIDEOINFOHEADER*)mt.AllocFormatBuffer(sizeof(VIDEOINFOHEADER) + extra);
memset(mt.Format(), 0, mt.FormatLength());
memcpy(mt.Format() + sizeof(VIDEOINFOHEADER), h + 1, extra);
pvih->AvgTimePerFrame = h->time_unit / h->samples_per_unit;
pvih->bmiHeader.biWidth = h->v.w;
pvih->bmiHeader.biHeight = h->v.h;
pvih->bmiHeader.biBitCount = (WORD)h->bps;
pvih->bmiHeader.biCompression = mt.subtype.Data1;
switch (pvih->bmiHeader.biCompression) {
case BI_RGB:
case BI_BITFIELDS:
mt.subtype =
pvih->bmiHeader.biBitCount == 1 ? MEDIASUBTYPE_RGB1 :
pvih->bmiHeader.biBitCount == 4 ? MEDIASUBTYPE_RGB4 :
pvih->bmiHeader.biBitCount == 8 ? MEDIASUBTYPE_RGB8 :
pvih->bmiHeader.biBitCount == 16 ? MEDIASUBTYPE_RGB565 :
pvih->bmiHeader.biBitCount == 24 ? MEDIASUBTYPE_RGB24 :
pvih->bmiHeader.biBitCount == 32 ? MEDIASUBTYPE_RGB32 :
MEDIASUBTYPE_NULL;
break;
case BI_RLE8:
mt.subtype = MEDIASUBTYPE_RGB8;
break;
case BI_RLE4:
mt.subtype = MEDIASUBTYPE_RGB4;
break;
}
mt.SetSampleSize(max(h->buffersize, 1));
m_mts.Add(mt);
}
//
// COggAudioOutputPin
//
COggAudioOutputPin::COggAudioOutputPin(OggStreamHeader* h, LPCWSTR pName, CBaseFilter* pFilter, CCritSec* pLock, HRESULT* phr)
: COggStreamOutputPin(h, pName, pFilter, pLock, phr)
{
int extra = (int)h->size - sizeof(OggStreamHeader);
extra = max(extra, 0);
CMediaType mt;
mt.majortype = MEDIATYPE_Audio;
mt.subtype = FOURCCMap(strtol(CStringA(h->subtype, 4), nullptr, 16));
mt.formattype = FORMAT_WaveFormatEx;
WAVEFORMATEX* wfe = (WAVEFORMATEX*)mt.AllocFormatBuffer(sizeof(WAVEFORMATEX) + extra);
memset(mt.Format(), 0, mt.FormatLength());
memcpy(mt.Format() + sizeof(WAVEFORMATEX), h + 1, extra);
wfe->cbSize = extra;
wfe->wFormatTag = (WORD)mt.subtype.Data1;
wfe->nChannels = h->a.nChannels;
wfe->nSamplesPerSec = (DWORD)(10000000i64 * h->samples_per_unit / h->time_unit);
wfe->wBitsPerSample = (WORD)h->bps;
wfe->nAvgBytesPerSec = h->a.nAvgBytesPerSec; // TODO: verify for PCM
wfe->nBlockAlign = h->a.nBlockAlign; // TODO: verify for PCM
mt.SetSampleSize(max(h->buffersize, 1));
m_mts.Add(mt);
}
//
// COggTextOutputPin
//
COggTextOutputPin::COggTextOutputPin(OggStreamHeader* h, LPCWSTR pName, CBaseFilter* pFilter, CCritSec* pLock, HRESULT* phr)
: COggStreamOutputPin(h, pName, pFilter, pLock, phr)
{
CMediaType mt;
mt.majortype = MEDIATYPE_Text;
mt.subtype = MEDIASUBTYPE_NULL;
mt.formattype = FORMAT_None;
mt.SetSampleSize(1);
m_mts.Add(mt);
}
// COggTheoraOutputPin
COggTheoraOutputPin::COggTheoraOutputPin(BYTE* p, LPCWSTR pName, CBaseFilter* pFilter, CCritSec* pLock, HRESULT* phr)
: COggSplitterOutputPin(pName, pFilter, pLock, phr)
{
CMediaType mt;
mt.majortype = MEDIATYPE_Video;
mt.subtype = FOURCCMap('OEHT');
mt.formattype = FORMAT_MPEG2_VIDEO;
MPEG2VIDEOINFO* vih = (MPEG2VIDEOINFO*)mt.AllocFormatBuffer(sizeof(MPEG2VIDEOINFO));
memset(mt.Format(), 0, mt.FormatLength());
vih->hdr.bmiHeader.biSize = sizeof(vih->hdr.bmiHeader);
vih->hdr.bmiHeader.biWidth = *(WORD*)&p[10] >> 4;
vih->hdr.bmiHeader.biHeight = *(WORD*)&p[12] >> 4;
vih->hdr.bmiHeader.biCompression = 'OEHT';
vih->hdr.bmiHeader.biPlanes = 1;
vih->hdr.bmiHeader.biBitCount = 24;
m_nFpsNum = (p[22] << 24) | (p[23] << 16) | (p[24] << 8) | p[25];
m_nFpsDenum = (p[26] << 24) | (p[27] << 16) | (p[28] << 8) | p[29];
if (m_nFpsNum) {
m_rtAvgTimePerFrame = (REFERENCE_TIME)(10000000.0 * m_nFpsDenum / m_nFpsNum);
vih->hdr.AvgTimePerFrame = m_rtAvgTimePerFrame;
}
vih->hdr.dwPictAspectRatioX = (p[14] << 16) | (p[15] << 8) | p[16];
vih->hdr.dwPictAspectRatioY = (p[17] << 16) | (p[18] << 8) | p[19];
m_KfgShift = (((p[40] << 8) + p[41]) & 0x3E0) >> 5;
m_nIndexOffset = TH_VERSION_CHECK(p[7], p[8], p[9], 3, 2, 1);
if (m_KfgShift == 0) {
m_KfgShift = 6; // Is it really default value ?
}
mt.bFixedSizeSamples = 0;
m_mts.Add(mt);
}
HRESULT COggTheoraOutputPin::UnpackInitPage(OggPage& page)
{
HRESULT hr = __super::UnpackPage(page);
while (m_packets.GetCount()) {
Packet* p = m_packets.GetHead();
CMediaType& mt = m_mts[0];
int size = (int)p->GetCount();
ASSERT(size <= 0xffff);
MPEG2VIDEOINFO* vih = (MPEG2VIDEOINFO*)mt.ReallocFormatBuffer(
FIELD_OFFSET(MPEG2VIDEOINFO, dwSequenceHeader) +
((MPEG2VIDEOINFO*)mt.Format())->cbSequenceHeader +
2 + size);
*(WORD*)((BYTE*)vih->dwSequenceHeader + vih->cbSequenceHeader) = (size >> 8) | (size << 8);
memcpy((BYTE*)vih->dwSequenceHeader + vih->cbSequenceHeader + 2, p->GetData(), size);
vih->cbSequenceHeader += 2 + size;
m_initpackets.AddTail(m_packets.RemoveHead());
}
return hr;
}
REFERENCE_TIME COggTheoraOutputPin::GetRefTime(__int64 granule_position)
{
LONGLONG iframe;
LONGLONG pframe;
iframe = granule_position >> m_KfgShift;
pframe = granule_position - (iframe << m_KfgShift);
/*3.2.0 streams store the frame index in the granule position.
3.2.1 and later store the frame count.
We return the index, so adjust the value if we have a 3.2.1 or later
stream.*/
REFERENCE_TIME rt = (iframe + pframe - m_nIndexOffset) * m_rtAvgTimePerFrame;
return rt;
}
HRESULT COggTheoraOutputPin::UnpackPacket(CAutoPtr& p, BYTE* pData, int len)
{
if (!pData) {
return E_FAIL;
}
p->bSyncPoint = len > 0 ? !(*pData & 0x40) : TRUE;
p->rtStart = m_rtLast;
p->rtStop = m_rtLast + 1;
p->SetData(pData, len);
if (!(*pData & 0x80) && m_mt.majortype == MEDIATYPE_Video) {
p->rtStop = p->rtStart + ((MPEG2VIDEOINFO*)m_mt.Format())->hdr.AvgTimePerFrame;
}
return S_OK;
}