/*
 * (C) 2003-2006 Gabest
 * (C) 2006-2015 see Authors.txt
 *
 * This file is part of MPC-BE.
 *
 * MPC-BE 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-BE 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 <http://www.gnu.org/licenses/>.
 *
 */

#include "stdafx.h"
#include <Shlwapi.h>
#include <atlpath.h>
#include <MMReg.h>
#include <ks.h>
#include <ksmedia.h>
#include "../../../DSUtil/DSUtil.h"
#include "RealMediaSplitter.h"
#include "../../../Subtitles/SubtitleInputPin.h"

#ifdef REGISTER_FILTER
#include <InitGuid.h>
#endif
#include <moreuuids.h>

template<typename T>
static void bswap(T& var)
{
	BYTE* s = (BYTE*)&var;
	for (BYTE* d = s + sizeof(var)-1; s < d; s++, d--) {
		*s ^= *d, *d ^= *s, *s ^= *d;
	}
}

void rvinfo::bswap()
{
	::bswap(dwSize);
	::bswap(w);
	::bswap(h);
	::bswap(bpp);
	::bswap(unk1);
	::bswap(fps);
	::bswap(type1);
	::bswap(type2);
}

void rainfo3::bswap()
{
	::bswap(fourcc);
	::bswap(version);
	::bswap(header_size);
	::bswap(data_size);
}

void rainfo::bswap()
{
	::bswap(version1);
	::bswap(version2);
	::bswap(header_size);
	::bswap(flavor);
	::bswap(coded_frame_size);
	::bswap(sub_packet_h);
	::bswap(frame_size);
	::bswap(sub_packet_size);
}

void rainfo4::bswap()
{
	__super::bswap();
	::bswap(sample_rate);
	::bswap(sample_size);
	::bswap(channels);
}

void rainfo5::bswap()
{
	__super::bswap();
	::bswap(sample_rate);
	::bswap(sample_size);
	::bswap(channels);
}

using namespace RMFF;

#ifdef REGISTER_FILTER

const AMOVIESETUP_MEDIATYPE sudPinTypesIn[] = {
	{&MEDIATYPE_Stream, &MEDIASUBTYPE_NULL},
};

const AMOVIESETUP_PIN sudpPins[] = {
	{L"Input", FALSE, FALSE, FALSE, FALSE, &CLSID_NULL, NULL, _countof(sudPinTypesIn), sudPinTypesIn},
	{L"Output", FALSE, TRUE, FALSE, FALSE, &CLSID_NULL, NULL, 0, NULL}
};

const AMOVIESETUP_MEDIATYPE sudPinTypesIn2[] = {
	{&MEDIATYPE_Video, &MEDIASUBTYPE_RV20},
	{&MEDIATYPE_Video, &MEDIASUBTYPE_RV30},
	{&MEDIATYPE_Video, &MEDIASUBTYPE_RV40},
	{&MEDIATYPE_Video, &MEDIASUBTYPE_RV41},
};

const AMOVIESETUP_MEDIATYPE sudPinTypesOut2[] = {
	{&MEDIATYPE_Video, &MEDIASUBTYPE_NULL},
};

const AMOVIESETUP_PIN sudpPins2[] = {
	{L"Input", FALSE, FALSE, FALSE, FALSE, &CLSID_NULL, NULL, _countof(sudPinTypesIn2), sudPinTypesIn2},
	{L"Output", FALSE, TRUE, FALSE, FALSE, &CLSID_NULL, NULL, _countof(sudPinTypesOut2), sudPinTypesOut2}
};

const AMOVIESETUP_MEDIATYPE sudPinTypesIn3[] = {
	{&MEDIATYPE_Audio, &MEDIASUBTYPE_14_4},
	{&MEDIATYPE_Audio, &MEDIASUBTYPE_28_8},
	{&MEDIATYPE_Audio, &MEDIASUBTYPE_ATRC},
	{&MEDIATYPE_Audio, &MEDIASUBTYPE_COOK},
	{&MEDIATYPE_Audio, &MEDIASUBTYPE_DNET},
	{&MEDIATYPE_Audio, &MEDIASUBTYPE_SIPR},
	{&MEDIATYPE_Audio, &MEDIASUBTYPE_SIPR_WAVE},
	{&MEDIATYPE_Audio, &MEDIASUBTYPE_RAW_AAC1},
	{&MEDIATYPE_Audio, &MEDIASUBTYPE_RAAC},
	{&MEDIATYPE_Audio, &MEDIASUBTYPE_RACP},
};

const AMOVIESETUP_MEDIATYPE sudPinTypesOut3[] = {
	{&MEDIATYPE_Audio, &MEDIASUBTYPE_PCM},
};

const AMOVIESETUP_PIN sudpPins3[] = {
	{L"Input", FALSE, FALSE, FALSE, FALSE, &CLSID_NULL, NULL, _countof(sudPinTypesIn3), sudPinTypesIn3},
	{L"Output", FALSE, TRUE, FALSE, FALSE, &CLSID_NULL, NULL, _countof(sudPinTypesOut3), sudPinTypesOut3}
};

const AMOVIESETUP_FILTER sudFilter[] = {
	{&__uuidof(CRealMediaSplitterFilter), RMSplitterName, MERIT_NORMAL, _countof(sudpPins), sudpPins, CLSID_LegacyAmFilterCategory},
	{&__uuidof(CRealMediaSourceFilter), RMSourceName, MERIT_NORMAL, 0, NULL, CLSID_LegacyAmFilterCategory},
	{&__uuidof(CRealVideoDecoder), L"MPC RealVideo Decoder", MERIT_NORMAL, _countof(sudpPins2), sudpPins2, CLSID_LegacyAmFilterCategory},
	{&__uuidof(CRealAudioDecoder), L"MPC RealAudio Decoder", MERIT_NORMAL, _countof(sudpPins3), sudpPins3, CLSID_LegacyAmFilterCategory},
};

CFactoryTemplate g_Templates[] = {
	{sudFilter[0].strName, sudFilter[0].clsID, CreateInstance<CRealMediaSplitterFilter>, NULL, &sudFilter[0]},
	{sudFilter[1].strName, sudFilter[1].clsID, CreateInstance<CRealMediaSourceFilter>, NULL, &sudFilter[1]},
	{sudFilter[2].strName, sudFilter[2].clsID, CreateInstance<CRealVideoDecoder>, NULL, &sudFilter[2]},
	{sudFilter[3].strName, sudFilter[3].clsID, CreateInstance<CRealAudioDecoder>, NULL, &sudFilter[3]},
};

int g_cTemplates = _countof(g_Templates);

STDAPI DllRegisterServer()
{
	RegisterSourceFilter(CLSID_AsyncReader, MEDIASUBTYPE_RealMedia, _T("0,4,,2E524D46"), _T(".rm"), _T(".rmvb"), _T(".ram"), NULL);

	return AMovieDllRegisterServer2(TRUE);
}

STDAPI DllUnregisterServer()
{
	UnRegisterSourceFilter(MEDIASUBTYPE_RealMedia);

	return AMovieDllRegisterServer2(FALSE);
}

#include "../../filters/Filters.h"

CFilterApp theApp;

#endif

//
// CRealMediaSplitterFilter
//

CRealMediaSplitterFilter::CRealMediaSplitterFilter(LPUNKNOWN pUnk, HRESULT* phr)
	: CBaseSplitterFilter(NAME("CRealMediaSplitterFilter"), pUnk, phr, __uuidof(this))
{
}

CRealMediaSplitterFilter::~CRealMediaSplitterFilter()
{
}

STDMETHODIMP CRealMediaSplitterFilter::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, RMSourceName);
	}
	pInfo->pGraph = m_pGraph;
	if (m_pGraph) {
		m_pGraph->AddRef();
	}

	return S_OK;
}

HRESULT CRealMediaSplitterFilter::CreateOutputs(IAsyncReader* pAsyncReader)
{
	CheckPointer(pAsyncReader, E_POINTER);

	{
		DWORD dw;
		if (FAILED(pAsyncReader->SyncRead(0, 4, (BYTE*)&dw)) || dw != 'FMR.') {
			return E_FAIL;
		}
	}

	HRESULT hr = E_FAIL;

	m_pFile.Free();

	m_pFile.Attach(DNew CRMFile(pAsyncReader, hr));
	if (!m_pFile) {
		return E_OUTOFMEMORY;
	}
	if (FAILED(hr)) {
		m_pFile.Free();
		return hr;
	}

	m_rtNewStart = m_rtCurrent = m_rtNewStop = 0;

	m_rtStop = 10000i64*m_pFile->m_p.tDuration;

	POSITION pos = m_pFile->m_mps.GetHeadPosition();
	while (pos) {
		MediaProperies* pmp = m_pFile->m_mps.GetNext(pos);

		CStringW name;
		name.Format(L"Output %02d", pmp->stream);
		if (!pmp->name.IsEmpty()) {
			name += L" (" + CStringW(pmp->name) + L")";
		}

		CAtlArray<CMediaType> mts;

		CMediaType mt;
		mt.SetSampleSize(max(pmp->maxPacketSize*16/**/, 1));

		if (pmp->mime == "video/x-pn-realvideo") {
			mt.majortype = MEDIATYPE_Video;
			mt.formattype = FORMAT_VideoInfo;

			VIDEOINFOHEADER* pvih = (VIDEOINFOHEADER*)mt.AllocFormatBuffer(sizeof(VIDEOINFOHEADER) + pmp->typeSpecData.GetCount());
			memset(mt.Format(), 0, mt.FormatLength());
			memcpy(pvih + 1, pmp->typeSpecData.GetData(), pmp->typeSpecData.GetCount());

			rvinfo rvi = *(rvinfo*)pmp->typeSpecData.GetData();
			rvi.bswap();

			ASSERT(rvi.dwSize >= FIELD_OFFSET(rvinfo, morewh));
			ASSERT(rvi.fcc1 == 'ODIV');

			mt.subtype = FOURCCMap(rvi.fcc2);
			if (rvi.fps > 0x10000) {
				pvih->AvgTimePerFrame = REFERENCE_TIME(10000000i64 / ((float)rvi.fps/0x10000));
			}
			pvih->dwBitRate = pmp->avgBitRate;
			pvih->bmiHeader.biSize = sizeof(pvih->bmiHeader);
			pvih->bmiHeader.biWidth = rvi.w;
			pvih->bmiHeader.biHeight = rvi.h;
			pvih->bmiHeader.biPlanes = 3;
			pvih->bmiHeader.biBitCount = rvi.bpp;
			pvih->bmiHeader.biCompression = rvi.fcc2;
			pvih->bmiHeader.biSizeImage = rvi.w*rvi.h*3/2;
			mts.Add(mt);

			BYTE* extra		= pmp->typeSpecData.GetData();
			int extralen	= pmp->typeSpecData.GetCount();

			if (extralen > 26) {
				extra		+= 26;
				extralen	-= 26;
				VIDEOINFOHEADER* pvih2 = (VIDEOINFOHEADER*)mt.ReallocFormatBuffer(sizeof(VIDEOINFOHEADER) + extralen);
				memcpy(pvih2 + 1, extra, extralen);
				mts.InsertAt(0, mt);
			}

			if (pmp->width > 0 && pmp->height > 0) {
				BITMAPINFOHEADER bmi = pvih->bmiHeader;
				mt.formattype = FORMAT_VideoInfo2;
				VIDEOINFOHEADER2* pvih2 = (VIDEOINFOHEADER2*)mt.ReallocFormatBuffer(sizeof(VIDEOINFOHEADER2) + pmp->typeSpecData.GetCount());
				memset(mt.Format() + FIELD_OFFSET(VIDEOINFOHEADER2, dwInterlaceFlags), 0, mt.FormatLength() - FIELD_OFFSET(VIDEOINFOHEADER2, dwInterlaceFlags));
				memcpy(pvih2 + 1, pmp->typeSpecData.GetData(), pmp->typeSpecData.GetCount());
				pvih2->bmiHeader = bmi;
				pvih2->bmiHeader.biWidth = (DWORD)pmp->width;
				pvih2->bmiHeader.biHeight = (DWORD)pmp->height;
				pvih2->dwPictAspectRatioX = rvi.w;
				pvih2->dwPictAspectRatioY = rvi.h;

				mts.InsertAt(0, mt);
			}
		} else if (pmp->mime == "audio/x-pn-realaudio") {
			mt.majortype = MEDIATYPE_Audio;
			mt.formattype = FORMAT_WaveFormatEx;
			mt.bTemporalCompression = 1;

			WAVEFORMATEX* pwfe = (WAVEFORMATEX*)mt.AllocFormatBuffer(sizeof(WAVEFORMATEX) + pmp->typeSpecData.GetCount());
			memset(mt.Format(), 0, mt.FormatLength());
			memcpy(pwfe + 1, pmp->typeSpecData.GetData(), pmp->typeSpecData.GetCount());
			pwfe->cbSize = pmp->typeSpecData.GetCount();

			union {
				DWORD fcc;
				char fccstr[5];
			};

			fcc = 0;
			fccstr[4] = 0;

			BYTE* fmt = pmp->typeSpecData.GetData();
			for (size_t i = 0; i < pmp->typeSpecData.GetCount()-4; i++, fmt++) {
				if (*(DWORD*)fmt == MAKEFOURCC('.' ,'r' ,'a' ,0xfd)) {
					break;
				}
			}

			BYTE* extra = NULL;

			WORD ver = fmt[4] << 8 | fmt[5];
			if (ver == 3) {
				rainfo3 rai3 = *(rainfo3*)fmt;
				rai3.bswap();
				fcc = '4_41';
				pwfe->nChannels       = 1;
				pwfe->nSamplesPerSec  = 8000;
				pwfe->nAvgBytesPerSec = 1000;
				pwfe->nBlockAlign     = 2;
				pwfe->wBitsPerSample  = 16;
			} else {
				rainfo rai = *(rainfo*)fmt;
				rai.bswap();

				if (rai.version2 == 4) {
					rainfo4 rai4 = *(rainfo4*)fmt;
					rai4.bswap();
					pwfe->nChannels = rai4.channels;
					pwfe->wBitsPerSample = rai4.sample_size;
					pwfe->nSamplesPerSec = rai4.sample_rate;
					pwfe->nBlockAlign = rai4.frame_size;
					BYTE* p = (BYTE*)((rainfo4*)fmt + 1);
					int len = *p++;
					p += len;
					len = *p++;
					ASSERT(len == 4);
					if (len == 4) {
						fcc = MAKEFOURCC(p[0], p[1], p[2], p[3]);
					}
					extra = p + len + 3;
				} else if (rai.version2 == 5) {
					rainfo5 rai5 = *(rainfo5*)fmt;
					rai5.bswap();
					pwfe->nChannels = rai5.channels;
					pwfe->wBitsPerSample = rai5.sample_size;
					pwfe->nSamplesPerSec = rai5.sample_rate;
					pwfe->nBlockAlign = rai5.frame_size;
					fcc = rai5.fourcc3;
					extra = fmt + sizeof(rainfo5) + 4;
				} else {
					continue;
				}
			}

			_strupr_s(fccstr);

			mt.subtype = FOURCCMap(fcc);

			bswap(fcc);

			switch (fcc) {
				case '14_4':
					pwfe->wFormatTag = WAVE_FORMAT_14_4;
					break;
				case '28_8':
					pwfe->wFormatTag = WAVE_FORMAT_28_8;
					break;
				case 'ATRC':
					pwfe->wFormatTag = WAVE_FORMAT_ATRC;
					break;
				case 'COOK':
					pwfe->wFormatTag = WAVE_FORMAT_COOK;
					break;
				case 'DNET':
					pwfe->wFormatTag = WAVE_FORMAT_DNET;
					break;
				case 'SIPR':
					pwfe->wFormatTag = WAVE_FORMAT_SIPR;
					break;
				case 'RAAC':
					pwfe->wFormatTag = WAVE_FORMAT_RAAC;
					break;
				case 'RACP':
					pwfe->wFormatTag = WAVE_FORMAT_RACP;
					break;
			}

			if (pwfe->wFormatTag) {
				mts.Add(mt);

				if (fcc == 'DNET') {
					mt.subtype = FOURCCMap(pwfe->wFormatTag = WAVE_FORMAT_DOLBY_AC3);
					mts.InsertAt(0, mt);
				} else if (fcc == 'RAAC' || fcc == 'RACP') {
					mt.subtype = FOURCCMap(pwfe->wFormatTag = WAVE_FORMAT_RAW_AAC1);
					int extralen = *(DWORD*)extra;
					extra += 4;
					::bswap(extralen);
					ASSERT(*extra == 2);	// always 2? why? what does it mean?
					if (*extra == 2) {
						extra++;
						extralen--;
						WAVEFORMATEX* pwfe = (WAVEFORMATEX*)mt.ReallocFormatBuffer(sizeof(WAVEFORMATEX) + extralen);
						pwfe->cbSize = extralen;
						memcpy(pwfe + 1, extra, extralen);
					} else {
						WAVEFORMATEX* pwfe = (WAVEFORMATEX*)mt.ReallocFormatBuffer(sizeof(WAVEFORMATEX) + 5);
						pwfe->cbSize = MakeAACInitData((BYTE*)(pwfe+1), 0, pwfe->nSamplesPerSec, pwfe->nChannels);
					}
					mts.InsertAt(0, mt);
				}
			}
		} else if (pmp->mime == "logical-fileinfo") {
			CAtlMap<CStringA, CStringA, CStringElementTraits<CStringA> > lfi;
			CStringA key, value;

			BYTE* p = pmp->typeSpecData.GetData();
			BYTE* end = p + pmp->typeSpecData.GetCount();
			p += 8;

			DWORD cnt = p <= end-4 ? *(DWORD*)p : 0;
			bswap(cnt);
			p += 4;

			if (cnt > 0xffff) { // different format?
				p += 2;
				cnt = p <= end-4 ? *(DWORD*)p : 0;
				bswap(cnt);
				p += 4;
			}

			while (p < end-4 && cnt-- > 0) {
				BYTE* base = p;
				DWORD len = *(DWORD*)p;
				bswap(len);
				p += 4;
				if (base + len > end) {
					break;
				}

				p++;
				WORD keylen = *(WORD*)p;
				bswap(keylen);
				p += 2;
				memcpy(key.GetBufferSetLength(keylen), p, keylen);
				p += keylen;

				p+=4;
				WORD valuelen = *(WORD*)p;
				bswap(valuelen);
				p += 2;
				memcpy(value.GetBufferSetLength(valuelen), p, valuelen);
				p += valuelen;

				ASSERT(p == base + len);
				p = base + len;

				lfi[key] = value;
			}

			POSITION pos = lfi.GetStartPosition();
			while (pos) {
				lfi.GetNextAssoc(pos, key, value);

				int n = 0;
				if (key.Find("CHAPTER") == 0 && key.Find("TIME") == key.GetLength()-4
						&& (n = strtol(key.Mid(7), NULL, 10)) > 0) {
					int h, m, s, ms;
					char c;
					if (7 != sscanf_s(value, "%d%c%d%c%d%c%d", &h, &c, sizeof(CHAR),
									  &m, &c, sizeof(CHAR), &s, &c, sizeof(CHAR), &ms)) {
						continue;
					}

					key.Format("CHAPTER%02dNAME", n);
					if (!lfi.Lookup(key, value) || value.IsEmpty()) {
						value.Format("Chapter %d", n);
					}

					ChapAppend(
						((((REFERENCE_TIME)h*60+m)*60+s)*1000+ms)*10000,
						CStringW(CString(value)));
				}
			}
		}

		if (mts.IsEmpty()) {
			TRACE(_T("Unsupported RealMedia stream (%d): %s\n"), pmp->stream, CString(pmp->mime));
			continue;
		}

		HRESULT hr;

		CAutoPtr<CBaseSplitterOutputPin> pPinOut(DNew CRealMediaSplitterOutputPin(mts, name, this, this, &hr));
		if (SUCCEEDED(AddOutputPin((DWORD)pmp->stream, pPinOut))) {
			if (!m_rtStop) {
				m_pFile->m_p.tDuration = max(m_pFile->m_p.tDuration, pmp->tDuration);
			}
		}
	}

	pos = m_pFile->m_subs.GetHeadPosition();
	for (DWORD stream = 0; pos; stream++) {
		CRMFile::subtitle& s = m_pFile->m_subs.GetNext(pos);

		CStringW name;
		name.Format(L"Subtitle %02d", stream);
		if (!s.name.IsEmpty()) {
			name += L" (" + CStringW(CString(s.name)) + L")";
		}

		CMediaType mt;
		mt.SetSampleSize(1);
		mt.majortype = MEDIATYPE_Text;

		CAtlArray<CMediaType> mts;
		mts.Add(mt);

		HRESULT hr;

		CAutoPtr<CBaseSplitterOutputPin> pPinOut(DNew CRealMediaSplitterOutputPin(mts, name, this, this, &hr));
		AddOutputPin((DWORD)~stream, pPinOut);
	}

	m_rtDuration = m_rtNewStop = m_rtStop = 10000i64*m_pFile->m_p.tDuration;

	SetProperty(L"TITL", CStringW(m_pFile->m_cd.title));
	SetProperty(L"AUTH", CStringW(m_pFile->m_cd.author));
	SetProperty(L"CPYR", CStringW(m_pFile->m_cd.copyright));
	SetProperty(L"DESC", CStringW(m_pFile->m_cd.comment));

	return m_pOutputs.GetCount() > 0 ? S_OK : E_FAIL;
}

bool CRealMediaSplitterFilter::DemuxInit()
{
	SetThreadName((DWORD)-1, "CRealMediaSplitterFilter");

	if (!m_pFile) {
		return false;
	}

	// reindex if needed
	if (m_pFile->m_irs.GetCount() == 0) {
		m_nOpenProgress = 0;

		int stream = m_pFile->GetMasterStream();

		UINT32 tLastStart = (UINT32)-1;
		UINT32 nPacket = 0;

		POSITION pos = m_pFile->m_dcs.GetHeadPosition();
		while (pos && !m_fAbort) {
			DataChunk* pdc = m_pFile->m_dcs.GetNext(pos);

			m_pFile->Seek(pdc->pos);

			for (UINT32 i = 0; i < pdc->nPackets && !m_fAbort; i++, nPacket++) {
				UINT64 filepos = m_pFile->GetPos();

				HRESULT hr;

				MediaPacketHeader mph;
				if (S_OK != (hr = m_pFile->Read(mph, false))) {
					break;
				}

				if (mph.stream == stream) {
					m_rtDuration = max((__int64)(10000i64*mph.tStart), m_rtDuration);

					if (mph.flags&MediaPacketHeader::PN_KEYFRAME_FLAG && tLastStart != mph.tStart) {
						CAutoPtr<IndexRecord> pir(DNew IndexRecord);
						pir->tStart = mph.tStart;
						pir->ptrFilePos = (UINT32)filepos;
						pir->packet = nPacket;
						m_pFile->m_irs.AddTail(pir);

						tLastStart = mph.tStart;
					}
				}

				m_nOpenProgress = m_pFile->GetPos()*100/m_pFile->GetLength();

				DWORD cmd;
				if (CheckRequest(&cmd)) {
					if (cmd == CMD_EXIT) {
						m_fAbort = true;
					} else {
						Reply(S_OK);
					}
				}
			}
		}

		m_nOpenProgress = 100;

		if (m_fAbort) {
			m_pFile->m_irs.RemoveAll();
		}

		m_fAbort = false;
	}

	m_seekpos		= NULL;
	m_seekpacket	= 0;
	m_seekfilepos	= 0;

	return true;
}

void CRealMediaSplitterFilter::DemuxSeek(REFERENCE_TIME rt)
{
	if (rt <= 0) {
		m_seekpos = m_pFile->m_dcs.GetHeadPosition();
		m_seekpacket = 0;
		m_seekfilepos = m_pFile->m_dcs.GetHead()->pos;
	} else {
		m_seekpos = NULL;

		POSITION pos = m_pFile->m_irs.GetTailPosition();
		while (pos && !m_seekpos) {
			IndexRecord* pir = m_pFile->m_irs.GetPrev(pos);
			if (pir->tStart <= rt/10000) {
				m_seekpacket = pir->packet;

				pos = m_pFile->m_dcs.GetTailPosition();
				while (pos && !m_seekpos) {
					POSITION tmp = pos;

					DataChunk* pdc = m_pFile->m_dcs.GetPrev(pos);

					if (pdc->pos <= pir->ptrFilePos) {
						m_seekpos = tmp;
						m_seekfilepos = pir->ptrFilePos;

						POSITION pos = m_pFile->m_dcs.GetHeadPosition();
						while (pos != m_seekpos) {
							m_seekpacket -= m_pFile->m_dcs.GetNext(pos)->nPackets;
						}
					}
				}

				// search the closest keyframe to the seek time (commented out 'cause rm seems to index all of its keyframes...)
				/*
								if(m_seekpos)
								{
									DataChunk* pdc = m_pFile->m_dcs.GetAt(m_seekpos);

									m_pFile->Seek(m_seekfilepos);

									REFERENCE_TIME seektime = -1;
									UINT32 seekstream = -1;

									for(UINT32 i = m_seekpacket; i < pdc->nPackets; i++)
									{
										UINT64 filepos = m_pFile->GetPos();

										MediaPacketHeader mph;
										if(S_OK != m_pFile->Read(mph, false))
											break;

										if(seekstream == -1) seekstream = mph.stream;
										if(seekstream != mph.stream) continue;

										if(seektime == 10000i64*mph.tStart) continue;
										if(rt < 10000i64*mph.tStart) break;

										if((mph.flags&MediaPacketHeader::PN_KEYFRAME_FLAG))
										{
											m_seekpacket = i;
											m_seekfilepos = filepos;
											seektime = 10000i64*mph.tStart;
										}
									}
								}
				*/
			}
		}

		if (!m_seekpos) {
			m_seekpos = m_pFile->m_dcs.GetHeadPosition();
			m_seekpacket = 0;
			m_seekfilepos = m_pFile->m_dcs.GetAt(m_seekpos)->pos;
		}
	}
}

bool CRealMediaSplitterFilter::DemuxLoop()
{
	HRESULT hr = S_OK;
	POSITION pos;

	pos = m_pFile->m_subs.GetHeadPosition();
	for (DWORD stream = 0; pos && SUCCEEDED(hr) && !CheckRequest(NULL); stream++) {
		CRMFile::subtitle& s = m_pFile->m_subs.GetNext(pos);

		CAutoPtr<CPacket> p(DNew CPacket);

		p->TrackNumber = ~stream;
		p->bSyncPoint = TRUE;
		p->rtStart = 0;
		p->rtStop = 1;

		size_t count = (4+1) + (2+4+(s.name.GetLength()+1)*2) + (2+4+s.data.GetLength());
		p->SetCount(count);
		BYTE* ptr = p->GetData();

		strcpy_s((char*)ptr, count, "GAB2");
		ptr += 4+1;
		count -= 4+1;

		*(WORD*)ptr = 2;
		ptr += 2;
		count -= 2;
		*(DWORD*)ptr = (s.name.GetLength()+1)*2;
		ptr += 4;
		count -= 4;
		wcscpy_s((WCHAR*)ptr, count / 2, CStringW(s.name));
		ptr += (s.name.GetLength()+1)*2;

		*(WORD*)ptr = 4;
		ptr += 2;
		*(DWORD*)ptr = s.data.GetLength();
		ptr += 4;
		memcpy((char*)ptr, s.data, s.data.GetLength());
		ptr += s.name.GetLength();

		hr = DeliverPacket(p);
	}

	pos = m_seekpos;
	while (pos && SUCCEEDED(hr) && !CheckRequest(NULL)) {
		DataChunk* pdc = m_pFile->m_dcs.GetNext(pos);

		m_pFile->Seek(m_seekfilepos > 0 ? m_seekfilepos : pdc->pos);

		for (UINT32 i = m_seekpacket; (pdc->nPackets ? i < pdc->nPackets : TRUE) && SUCCEEDED(hr) && !CheckRequest(NULL); i++) {
			MediaPacketHeader mph;
			if (S_OK != (hr = m_pFile->Read(mph))) {
				break;
			}

			CAutoPtr<CPacket> p(DNew CPacket);
			p->TrackNumber	= mph.stream;
			p->bSyncPoint	= !!(mph.flags & MediaPacketHeader::PN_KEYFRAME_FLAG);
			p->rtStart		= 10000i64 * mph.tStart;
			p->rtStop		= p->rtStart + 1;
			p->Copy(mph.pData);
			hr				= DeliverPacket(p);
		}

		m_seekpacket = 0;
		m_seekfilepos = 0;
	}

	return true;
}

// IKeyFrameInfo

STDMETHODIMP CRealMediaSplitterFilter::GetKeyFrameCount(UINT& nKFs)
{
	if (!m_pFile) {
		return E_UNEXPECTED;
	}
	nKFs = m_pFile->m_irs.GetCount();
	return S_OK;
}

STDMETHODIMP CRealMediaSplitterFilter::GetKeyFrames(const GUID* pFormat, REFERENCE_TIME* pKFs, UINT& nKFs)
{
	CheckPointer(pFormat, E_POINTER);
	CheckPointer(pKFs, E_POINTER);

	if (!m_pFile) {
		return E_UNEXPECTED;
	}
	if (*pFormat != TIME_FORMAT_MEDIA_TIME) {
		return E_INVALIDARG;
	}

	UINT nKFsTmp = 0;
	POSITION pos = m_pFile->m_irs.GetHeadPosition();
	for (int i = 0; pos && nKFsTmp < nKFs; i++) {
		pKFs[nKFsTmp++] = 10000i64*m_pFile->m_irs.GetNext(pos)->tStart;
	}
	nKFs = nKFsTmp;

	return S_OK;
}

//
// CRealMediaSplitterOutputPin
//

CRealMediaSplitterOutputPin::CRealMediaSplitterOutputPin(CAtlArray<CMediaType>& mts, LPCWSTR pName, CBaseFilter* pFilter, CCritSec* pLock, HRESULT* phr)
	: CBaseSplitterOutputPin(mts, pName, pFilter, pLock, phr, 10.0)
{
}

CRealMediaSplitterOutputPin::~CRealMediaSplitterOutputPin()
{
}

HRESULT CRealMediaSplitterOutputPin::DeliverEndFlush()
{
	{
		CAutoLock cAutoLock(&m_csQueue);
		m_segments.Clear();
	}

	return __super::DeliverEndFlush();
}

HRESULT CRealMediaSplitterOutputPin::DeliverSegments()
{
	HRESULT hr;

	if (m_segments.GetCount() == 0) {
		m_segments.Clear();
		return S_OK;
	}

	CAutoPtr<CPacket> p(DNew CPacket());

	p->TrackNumber = (DWORD)-1;
	p->bDiscontinuity = m_segments.fDiscontinuity;
	p->bSyncPoint = m_segments.fSyncPoint;
	p->rtStart = m_segments.rtStart;
	p->rtStop = m_segments.rtStart+1;

	DWORD len = 0, total = 0;
	POSITION pos = m_segments.GetHeadPosition();
	while (pos) {
		segment* s = m_segments.GetNext(pos);
		len = max(len, s->offset + s->data.GetCount());
		total += s->data.GetCount();
	}
	ASSERT(len == total);
	len += 1 + 2*4*(!m_segments.fMerged ? m_segments.GetCount() : 1);

	p->SetCount(len);

	BYTE* pData = p->GetData();

	*pData++ = m_segments.fMerged ? 0 : m_segments.GetCount()-1;

	if (m_segments.fMerged) {
		*((DWORD*)pData) = 1;
		pData += 4;
		*((DWORD*)pData) = 0;
		pData += 4;
	} else {
		pos = m_segments.GetHeadPosition();
		while (pos) {
			*((DWORD*)pData) = 1;
			pData += 4;
			*((DWORD*)pData) = m_segments.GetNext(pos)->offset;
			pData += 4;
		}
	}

	pos = m_segments.GetHeadPosition();
	while (pos) {
		segment* s = m_segments.GetNext(pos);
		memcpy(pData + s->offset, s->data.GetData(), s->data.GetCount());
	}

	hr = __super::DeliverPacket(p);

	m_segments.Clear();

	return hr;
}

HRESULT CRealMediaSplitterOutputPin::DeliverPacket(CAutoPtr<CPacket> p)
{
	HRESULT hr = S_OK;

	ASSERT(p->rtStart < p->rtStop);

	if (m_mt.subtype == MEDIASUBTYPE_WAVE_DOLBY_AC3) {
		WORD* s = (WORD*)p->GetData();
		WORD* e = s + p->GetCount()/2;
		while (s < e) {
			bswap(*s++);
		}
	}

	if (m_mt.subtype == MEDIASUBTYPE_RV10 || m_mt.subtype == MEDIASUBTYPE_RV20
			|| m_mt.subtype == MEDIASUBTYPE_RV30 || m_mt.subtype == MEDIASUBTYPE_RV40
			|| m_mt.subtype == MEDIASUBTYPE_RV41) {
		CAutoLock cAutoLock(&m_csQueue);

		int len = p->GetCount();
		BYTE* pIn = p->GetData();
		BYTE* pInOrg = pIn;

		if (m_segments.rtStart != p->rtStart) {
			if (S_OK != (hr = DeliverSegments())) {
				return hr;
			}
		}

		if (!m_segments.fDiscontinuity && p->bDiscontinuity) {
			m_segments.fDiscontinuity = true;
		}
		m_segments.fSyncPoint = !!p->bSyncPoint;
		m_segments.rtStart = p->rtStart;

		while (pIn - pInOrg < len) {
			BYTE hdr = *pIn++;
			DWORD packetlen = 0, packetoffset = 0;

			if ((hdr&0xc0) == 0x40) {
				pIn++;
				packetlen = len - (pIn - pInOrg);
			} else {
				if ((hdr&0x40) == 0) {
					pIn++; //BYTE subseq = (*pIn++)&0x7f;
				}

#define GetWORD(var) \
					var = (var<<8)|(*pIn++); \
					var = (var<<8)|(*pIn++); \
 
				GetWORD(packetlen);
				if (packetlen&0x8000) {
					m_segments.fMerged = true;
				}
				if ((packetlen&0x4000) == 0) {
					GetWORD(packetlen);
					packetlen &= 0x3fffffff;
				} else {
					packetlen &= 0x3fff;
				}

				GetWORD(packetoffset);
				if ((packetoffset&0x4000) == 0) {
					GetWORD(packetoffset);
					packetoffset &= 0x3fffffff;
				} else {
					packetoffset &= 0x3fff;
				}

#undef GetWORD

				if ((hdr&0xc0) == 0xc0) {
					m_segments.rtStart = 10000i64*packetoffset - m_rtStart, packetoffset = 0;
				} else if ((hdr&0xc0) == 0x80) {
					packetoffset = packetlen - packetoffset;
				}

				pIn++; //BYTE seqnum = *pIn++;
			}

			int len2 = min(len - (pIn - pInOrg), int(packetlen - packetoffset));

			CAutoPtr<segment> s(DNew segment);
			s->offset = packetoffset;
			s->data.SetCount(len2);
			memcpy(s->data.GetData(), pIn, len2);
			m_segments.AddTail(s);

			pIn += len2;

			if ((hdr&0x80) || packetoffset+len2 >= packetlen) {
				if (S_OK != (hr = DeliverSegments())) {
					return hr;
				}
			}
		}
	} else if (m_mt.subtype == MEDIASUBTYPE_RAAC || m_mt.subtype == MEDIASUBTYPE_RACP
			   || m_mt.subtype == MEDIASUBTYPE_RAW_AAC1) {
		BYTE* ptr = p->GetData()+2;

		CAtlList<WORD> sizes;
		int total = 0;
		int remaining = p->GetCount()-2;
		int expected = *(ptr-1)>>4;

		while (total < remaining) {
			int size = (ptr[0]<<8)|(ptr[1]);
			sizes.AddTail(size);
			total += size;
			ptr += 2;
			remaining -= 2;
			expected--;
		}

		ASSERT(total == remaining);
		ASSERT(expected == 0);

		WAVEFORMATEX* wfe = (WAVEFORMATEX*)m_mt.pbFormat;
		REFERENCE_TIME rtDur = 10240000000i64/wfe->nSamplesPerSec * (wfe->cbSize>2?2:1);
		REFERENCE_TIME rtStart = p->rtStart;
		BOOL bDiscontinuity = p->bDiscontinuity;

		POSITION pos = sizes.GetHeadPosition();
		while (pos) {
			WORD size = sizes.GetNext(pos);

			CAutoPtr<CPacket> p(DNew CPacket);
			p->bDiscontinuity = bDiscontinuity;
			p->bSyncPoint = true;
			p->rtStart = rtStart;
			p->rtStop = rtStart += rtDur;
			p->SetData(ptr, size);
			ptr += size;
			bDiscontinuity = false;
			if (S_OK != (hr = __super::DeliverPacket(p))) {
				break;
			}
		}
	} else {
		hr = __super::DeliverPacket(p);
	}

	return hr;
}

//
// CRealMediaSourceFilter
//

CRealMediaSourceFilter::CRealMediaSourceFilter(LPUNKNOWN pUnk, HRESULT* phr)
	: CRealMediaSplitterFilter(pUnk, phr)
{
	m_clsid = __uuidof(this);
	m_pInput.Free();
}

//
// CRMFile
//

CRMFile::CRMFile(IAsyncReader* pAsyncReader, HRESULT& hr)
	: CBaseSplitterFile(pAsyncReader, hr)
{
	if (FAILED(hr)) {
		return;
	}
	hr = Init();
}

template<typename T>
HRESULT CRMFile::Read(T& var)
{
	HRESULT hr = ByteRead((BYTE*)&var, sizeof(var));
	bswap(var);
	return hr;
}

HRESULT CRMFile::Read(ChunkHdr& hdr)
{
	memset(&hdr, 0, sizeof(hdr));
	HRESULT hr;
	if (S_OK != (hr = Read(hdr.object_id))
			|| S_OK != (hr = Read(hdr.size))
			|| S_OK != (hr = Read(hdr.object_version))) {
		return hr;
	}
	return S_OK;
}

HRESULT CRMFile::Read(MediaPacketHeader& mph, bool fFull)
{
	memset(&mph, 0, FIELD_OFFSET(MediaPacketHeader, pData));
	mph.stream = (UINT16)-1;

	HRESULT hr;

	UINT16 object_version;
	if (S_OK != (hr = Read(object_version))) {
		return hr;
	}
	if (object_version != 0 && object_version != 1) {
		return S_OK;
	}

	UINT8 flags;
	if (S_OK != (hr = Read(mph.len))
			|| S_OK != (hr = Read(mph.stream))
			|| S_OK != (hr = Read(mph.tStart))
			|| S_OK != (hr = Read(mph.reserved))
			|| S_OK != (hr = Read(flags))) {
		return hr;
	}

	mph.flags = (MediaPacketHeader::flag_t)flags;

	LONG len = mph.len;
	len -= sizeof(object_version);
	len -= FIELD_OFFSET(MediaPacketHeader, flags);
	len -= sizeof(flags);
	ASSERT(len >= 0);
	len = max(len, 0);

	if (fFull) {
		mph.pData.SetCount(len);
		if (mph.len > 0 && S_OK != (hr = ByteRead(mph.pData.GetData(), len))) {
			return hr;
		}
	} else {
		Seek(GetPos() + len);
	}

	return S_OK;
}

HRESULT CRMFile::Init()
{
	Seek(0);

	bool fFirstChunk = true;

	HRESULT hr;

	ChunkHdr hdr;
	while (GetRemaining() && S_OK == (hr = Read(hdr))) {
		__int64 pos = GetPos() - sizeof(hdr);

		if (fFirstChunk && hdr.object_id != '.RMF') {
			return E_FAIL;
		}

		fFirstChunk = false;

		if (pos + hdr.size > GetLength() && hdr.object_id != 'DATA') {	// truncated?
			break;
		}

		if (hdr.object_id == 0x2E7261FD) {	// '.ra+0xFD'
			return E_FAIL;
		}

		if (hdr.object_version == 0) {
			switch (hdr.object_id) {
				case '.RMF':
					if (S_OK != (hr = Read(m_fh.version))) {
						return hr;
					}
					if (hdr.size == 0x10) {
						WORD w = 0;
						if (S_OK != (hr = Read(w))) {
							return hr;
						}
						m_fh.nHeaders = w;
					} else if (S_OK != (hr = Read(m_fh.nHeaders))) {
						return hr;
					}
					break;
				case 'CONT':
					UINT16 slen;
					if (S_OK != (hr = Read(slen))) {
						return hr;
					}
					if (slen > 0 && S_OK != (hr = ByteRead((BYTE*)m_cd.title.GetBufferSetLength(slen), slen))) {
						return hr;
					}
					if (S_OK != (hr = Read(slen))) {
						return hr;
					}
					if (slen > 0 && S_OK != (hr = ByteRead((BYTE*)m_cd.author.GetBufferSetLength(slen), slen))) {
						return hr;
					}
					if (S_OK != (hr = Read(slen))) {
						return hr;
					}
					if (slen > 0 && S_OK != (hr = ByteRead((BYTE*)m_cd.copyright.GetBufferSetLength(slen), slen))) {
						return hr;
					}
					if (S_OK != (hr = Read(slen))) {
						return hr;
					}
					if (slen > 0 && S_OK != (hr = ByteRead((BYTE*)m_cd.comment.GetBufferSetLength(slen), slen))) {
						return hr;
					}
					break;
				case 'PROP':
					if (S_OK != (hr = Read(m_p.maxBitRate))) {
						return hr;
					}
					if (S_OK != (hr = Read(m_p.avgBitRate))) {
						return hr;
					}
					if (S_OK != (hr = Read(m_p.maxPacketSize))) {
						return hr;
					}
					if (S_OK != (hr = Read(m_p.avgPacketSize))) {
						return hr;
					}
					if (S_OK != (hr = Read(m_p.nPackets))) {
						return hr;
					}
					if (S_OK != (hr = Read(m_p.tDuration))) {
						return hr;
					}
					if (S_OK != (hr = Read(m_p.tPreroll))) {
						return hr;
					}
					if (S_OK != (hr = Read(m_p.ptrIndex))) {
						return hr;
					}
					if (S_OK != (hr = Read(m_p.ptrData))) {
						return hr;
					}
					if (S_OK != (hr = Read(m_p.nStreams))) {
						return hr;
					}
					UINT16 flags;
					if (S_OK != (hr = Read(flags))) {
						return hr;
					}
					m_p.flags = (Properies::flags_t)flags;
					break;
				case 'MDPR': {
					CAutoPtr<MediaProperies> mp(DNew MediaProperies);
					if (S_OK != (hr = Read(mp->stream))) {
						return hr;
					}
					if (S_OK != (hr = Read(mp->maxBitRate))) {
						return hr;
					}
					if (S_OK != (hr = Read(mp->avgBitRate))) {
						return hr;
					}
					if (S_OK != (hr = Read(mp->maxPacketSize))) {
						return hr;
					}
					if (S_OK != (hr = Read(mp->avgPacketSize))) {
						return hr;
					}
					if (S_OK != (hr = Read(mp->tStart))) {
						return hr;
					}
					if (S_OK != (hr = Read(mp->tPreroll))) {
						return hr;
					}
					if (S_OK != (hr = Read(mp->tDuration))) {
						return hr;
					}
					UINT8 slen;
					if (S_OK != (hr = Read(slen))) {
						return hr;
					}
					if (slen > 0 && S_OK != (hr = ByteRead((BYTE*)mp->name.GetBufferSetLength(slen), slen))) {
						return hr;
					}
					if (S_OK != (hr = Read(slen))) {
						return hr;
					}
					if (slen > 0 && S_OK != (hr = ByteRead((BYTE*)mp->mime.GetBufferSetLength(slen), slen))) {
						return hr;
					}
					UINT32 tsdlen;
					if (S_OK != (hr = Read(tsdlen))) {
						return hr;
					}
					mp->typeSpecData.SetCount(tsdlen);
					if (tsdlen > 0 && S_OK != (hr = ByteRead(mp->typeSpecData.GetData(), tsdlen))) {
						return hr;
					}
					mp->width = mp->height = 0;
					mp->interlaced = mp->top_field_first = false;
					m_mps.AddTail(mp);
					break;
				}
				case 'DATA': {
					CAutoPtr<DataChunk> dc(DNew DataChunk);
					if (S_OK != (hr = Read(dc->nPackets))) {
						return hr;
					}
					if (S_OK != (hr = Read(dc->ptrNext))) {
						return hr;
					}
					dc->pos = GetPos();
					m_dcs.AddTail(dc);
					GetDimensions();
					break;
				}
				case 'INDX': {
					IndexChunkHeader ich;
					if (S_OK != (hr = Read(ich.nIndices))) {
						return hr;
					}
					if (S_OK != (hr = Read(ich.stream))) {
						return hr;
					}
					if (S_OK != (hr = Read(ich.ptrNext))) {
						return hr;
					}
					int stream = GetMasterStream();
					while (ich.nIndices-- > 0) {
						UINT16 object_version;
						if (S_OK != (hr = Read(object_version))) {
							return hr;
						}
						if (object_version == 0) {
							CAutoPtr<IndexRecord> ir(DNew IndexRecord);
							if (S_OK != (hr = Read(ir->tStart))) {
								return hr;
							}
							if (S_OK != (hr = Read(ir->ptrFilePos))) {
								return hr;
							}
							if (S_OK != (hr = Read(ir->packet))) {
								return hr;
							}
							if (ich.stream == stream) {
								m_irs.AddTail(ir);
							}
						}
					}
					break;
				}
				case '.SUB':
					if (hdr.size > sizeof(hdr)) {
						int size = hdr.size - sizeof(hdr);
						CAutoVectorPtr<char> buff;
						if (!buff.Allocate(size)) {
							return E_OUTOFMEMORY;
						}
						char* p = buff;
						if (S_OK != (hr = ByteRead((BYTE*)p, size))) {
							return hr;
						}
						for (char* end = p + size; p < end; ) {
							subtitle s;
							s.name = p;
							p += s.name.GetLength()+1;
							CStringA len(p);
							p += len.GetLength()+1;
							s.data = CStringA(p, strtol(len, NULL, 10));
							p += s.data.GetLength();
							m_subs.AddTail(s);
						}
					}
					break;
			}
		}

		if (hdr.object_id == 'CONT' && BitRead(32, true) == 'DATA') {
			hdr.size = GetPos() - pos;
		}

		ASSERT(hdr.object_id == 'DATA'
			   || GetPos() == pos + hdr.size
			   || GetPos() == pos + sizeof(hdr));

		pos += hdr.size;
		if (pos > GetPos()) {
			Seek(pos);
		}
	}

	return S_OK;
}

#define GetBits(n) GetBits2(n, p, bit_offset, bit_buffer)
static unsigned int GetBits2(int n, unsigned char*& p, unsigned int& bit_offset, unsigned int& bit_buffer)
{
	unsigned int ret = ((unsigned int)bit_buffer >> (32-(n)));

	bit_offset += n;
	bit_buffer <<= n;
	if (bit_offset > (32-16)) {
		p += bit_offset >> 3;
		bit_offset &= 7;
		bit_buffer = (unsigned int)p[0] << 24;
		bit_buffer |= (unsigned int)p[1] << 16;
		bit_buffer |= (unsigned int)p[2] << 8;
		bit_buffer |= (unsigned int)p[3];
		bit_buffer <<= bit_offset;
	}

	return ret;
}

static void GetDimensions(unsigned char* p, unsigned int* wi, unsigned int* hi)
{
	unsigned int w, h, c;

	const unsigned int cw[8] = {160, 176, 240, 320, 352, 640, 704, 0};
	const unsigned int ch1[8] = {120, 132, 144, 240, 288, 480, 0, 0};
	const unsigned int ch2[4] = {180, 360, 576, 0};

	unsigned int bit_offset = 0;
	unsigned int bit_buffer = *(unsigned int*)p;
	bswap(bit_buffer);

	GetBits(13);

	GetBits(13);

	w = cw[GetBits(3)];
	if (w == 0) {
		do {
			c = GetBits(8);
			w += (c << 2);
		} while (c == 255);
	}

	c = GetBits(3);

	h = ch1[c];
	if (h == 0) {
		c = ((c << 1) | GetBits(1)) & 3;

		h = ch2[c];
		if (h == 0) {
			do {
				c = GetBits(8);
				h += (c << 2);
			} while (c == 255);
		}
	}

	*wi = w;
	*hi = h;
}

static void GetDimensions_X10(unsigned char* p, unsigned int* wi, unsigned int* hi,
							  bool *interlaced, bool *top_field_first, bool *repeat_field)
{
	unsigned int w, h, c;

	const unsigned int cw[8] = {160, 176, 240, 320, 352, 640, 704, 0};
	const unsigned int ch1[8] = {120, 132, 144, 240, 288, 480, 0, 0};
	const unsigned int ch2[4] = {180, 360, 576, 0};

	unsigned int bit_offset = 0;
	unsigned int bit_buffer = *(unsigned int*)p;
	bswap(bit_buffer);

	GetBits(9);

	*interlaced = false;
	*top_field_first = false;
	*repeat_field = false;
	c = GetBits(1);
	if (c) {
		c = GetBits(1);
		if (c) {
			*interlaced = true;
		}
		c = GetBits(1);
		if (c) {
			*top_field_first = true;
		}
		c = GetBits(1);
		if (c) {
			*repeat_field = true;
		}

		GetBits(1);
		c = GetBits(1);
		if (c) {
			GetBits(2);
		}
	}

	GetBits(16);

	w = cw[GetBits(3)];
	if (w == 0) {
		do {
			c = GetBits(8);
			w += (c << 2);
		} while (c == 255);
	}

	c = GetBits(3);

	h = ch1[c];
	if (h == 0) {
		c = ((c << 1) | GetBits(1)) & 3;

		h = ch2[c];
		if (h == 0) {
			do {
				c = GetBits(8);
				h += (c << 2);
			} while (c == 255);
		}
	}

	*wi = w;
	*hi = h;
}

void CRMFile::GetDimensions()
{
	POSITION pos = m_mps.GetHeadPosition();
	while (pos) {
		UINT64 filepos = GetPos();

		MediaProperies* pmp = m_mps.GetNext(pos);
		if (pmp->mime == "video/x-pn-realvideo") {
			pmp->width = pmp->height = 0;

			rvinfo rvi = *(rvinfo*)pmp->typeSpecData.GetData();
			rvi.bswap();

			if (rvi.fcc2 != '04VR' && rvi.fcc2 != '14VR') {
				continue;
			}

			MediaPacketHeader mph;
			while (S_OK == Read(mph)) {
				if (mph.stream != pmp->stream || mph.len == 0
						|| !(mph.flags&MediaPacketHeader::PN_KEYFRAME_FLAG)) {
					continue;
				}

				BYTE* p = mph.pData.GetData();
				BYTE* p0 = p;
				int len = mph.pData.GetCount();

				BYTE hdr = *p++;
				DWORD packetlen = 0, packetoffset = 0;

				if ((hdr&0xc0) == 0x40) {
					packetlen = len - (++p - p0);
				} else {
					if ((hdr&0x40) == 0) {
						p++;
					}

#define GetWORD(var) \
						var = (var<<8)|(*p++); \
						var = (var<<8)|(*p++); \
 
					GetWORD(packetlen);
					if ((packetlen&0x4000) == 0) {
						GetWORD(packetlen);
						packetlen &= 0x3fffffff;
					} else {
						packetlen &= 0x3fff;
					}

					GetWORD(packetoffset);
					if ((packetoffset&0x4000) == 0) {
						GetWORD(packetoffset);
						packetoffset &= 0x3fffffff;
					} else {
						packetoffset &= 0x3fff;
					}

#undef GetWORD

					if ((hdr&0xc0) == 0xc0) {
						packetoffset = 0;
					} else if ((hdr&0xc0) == 0x80) {
						packetoffset = packetlen - packetoffset;
					}

					p++;
				}

				len = min(len - (p - p0), int(packetlen - packetoffset));

				if (len > 0) {
					bool repeat_field;
					if (rvi.fcc2 == '14VR') {
						::GetDimensions_X10(p, &pmp->width, &pmp->height, &pmp->interlaced, &pmp->top_field_first, &repeat_field);
					} else {
						::GetDimensions(p, &pmp->width, &pmp->height);
					}

					if (rvi.w == pmp->width && rvi.h == pmp->height) {
						pmp->width = pmp->height = 0;
					}

					break;
				}
			}
		}

		Seek(filepos);
	}
}

int CRMFile::GetMasterStream()
{
	int s1 = -1, s2 = -1;

	POSITION pos = m_mps.GetHeadPosition();
	while (pos) {
		MediaProperies* pmp = m_mps.GetNext(pos);
		if (pmp->mime == "video/x-pn-realvideo") {
			s1 = pmp->stream;
			break;
		} else if (pmp->mime == "audio/x-pn-realaudio" && s2 == -1) {
			s2 = pmp->stream;
		}
	}

	if (s1 == -1) {
		s1 = s2;
	}

	return s1;
}


////////////////////////////

//
// CRealVideoDecoder
//

CRealVideoDecoder::CRealVideoDecoder(LPUNKNOWN lpunk, HRESULT* phr)
	: CBaseVideoFilter(NAME("CRealVideoDecoder"), lpunk, phr, __uuidof(this))
	, m_hDrvDll(NULL)
	, m_dwCookie(0)
	, m_lastBuffSizeDim(0)
	, m_pI420(NULL)
	, m_pI420Tmp(NULL)
{
}

CRealVideoDecoder::~CRealVideoDecoder()
{
	if (m_hDrvDll) {
		FreeLibrary(m_hDrvDll);
	}
}

HRESULT CRealVideoDecoder::InitRV(const CMediaType* pmt)
{
	FreeRV();

	HRESULT hr = VFW_E_TYPE_NOT_ACCEPTED;

	rvinfo rvi = *(rvinfo*)(pmt->Format() + (pmt->formattype == FORMAT_VideoInfo ? sizeof(VIDEOINFOHEADER) : sizeof(VIDEOINFOHEADER2)));
	rvi.bswap();

#pragma pack(push, 1)
	struct {
		WORD unk1, w, h, unk3;
		DWORD unk2, subformat, unk5, format;
	} i =
	{11, rvi.w, rvi.h, 0, 0, rvi.type1, 1, rvi.type2};
#pragma pack(pop)

	if (FAILED(hr = RVInit(&i, &m_dwCookie))) {
		return hr;
	}

	if (rvi.fcc2 <= '03VR' && rvi.type2 >= 0x20200002) {
		int nWidthHeight = (1+((rvi.type1>>16)&7));
		UINT32* pWH = DNew UINT32[nWidthHeight*2];
		pWH[0] = rvi.w;
		pWH[1] = rvi.h;
		for (int i = 2; i < nWidthHeight*2; i++) {
			pWH[i] = rvi.morewh[i-2]*4;
		}
#pragma pack(push, 1)
		struct {
			UINT32 data1;
			UINT32 data2;
			UINT32* dimensions;
		} cmsg_data =
		{0x24, nWidthHeight, pWH};
#pragma pack(pop)
		hr = RVCustomMessage(&cmsg_data, m_dwCookie);
		delete [] pWH;
	}

	m_fcc = rvi.fcc2;

	return hr;
}

void CRealVideoDecoder::FreeRV()
{
	if (m_dwCookie) {
		RVFree(m_dwCookie);
		m_dwCookie = 0;
	}
}

HRESULT CRealVideoDecoder::Transform(IMediaSample* pIn)
{
	CAutoLock cAutoLock(&m_csReceive);

	HRESULT hr;

	BYTE* pDataIn = NULL;
	if (FAILED(hr = pIn->GetPointer(&pDataIn))) {
		return hr;
	}

	long len = pIn->GetActualDataLength();
	if (len <= 0) {
		return S_OK;    // nothing to do
	}

	REFERENCE_TIME rtStart, rtStop;
	pIn->GetTime(&rtStart, &rtStop);

	rtStart += m_tStart;

	int offset = 1+((*pDataIn)+1)*8;

#pragma pack(push, 1)
	struct {
		DWORD len, unk1, chunks;
		DWORD* extra;
		DWORD unk2, timestamp;
	} transform_in =
	{len - offset, 0, *pDataIn, (DWORD*)(pDataIn+1), 0, (DWORD)(rtStart/10000)};
	struct {
		DWORD unk1, unk2, timestamp, w, h;
	} transform_out =
	{0,0,0,0,0};
#pragma pack(pop)

	pDataIn += offset;

	if (m_fDropFrames && m_timestamp+1 == transform_in.timestamp) {
		m_timestamp = transform_in.timestamp;
		return S_OK;
	}


	unsigned int width, height;
	bool interlaced = false, top_field_first = false, repeat_field = false;
	if (m_fcc == '14VR') {
		::GetDimensions_X10(pDataIn, &width, &height, &interlaced, &top_field_first, &repeat_field);
	} else {
		::GetDimensions(pDataIn, &width, &height);
	}

	int size =  width * height;
	if (m_lastBuffSizeDim < size) {
		_aligned_free(m_pI420);
		_aligned_free(m_pI420Tmp);

		m_lastBuffSizeDim = size;
		m_pI420 = (BYTE*)_aligned_malloc(size * 3 / 2, 16);
		if (m_pI420) {
			memset(m_pI420, 0, size);
			memset(m_pI420 + size, 0x80, size / 2);
		} else {
			DbgLog((LOG_TRACE, 3, L"m_pI420.Allocate fail %d", size * 3 / 2));
			return S_OK;
		}
		m_pI420Tmp = (BYTE*)_aligned_malloc(size * 3 / 2, 16);
		if (m_pI420Tmp) {
			memset(m_pI420Tmp, 0, size);
			memset(m_pI420Tmp + size, 0x80, size / 2);
		} else {
			DbgLog((LOG_TRACE, 3, L"m_pI420Tmp.Allocate fail %d", size * 3 / 2));
			return S_OK;
		}
	}

	hr = RVTransform(pDataIn, m_pI420, &transform_in, &transform_out, m_dwCookie);

	m_timestamp = transform_in.timestamp;

	if (FAILED(hr)) {
		TRACE(_T("RV returned an error code!!!\n"));
		ASSERT(!(transform_out.unk1 & 1));	// error allowed when the "render" flag is not set
	}

	if (pIn->IsPreroll() == S_OK || rtStart < 0 || !(transform_out.unk1 & 1)) {
		return S_OK;
	}

	CComPtr<IMediaSample> pOut;
	BYTE* pDataOut = NULL;
	if (/*FAILED(hr = GetDeliveryBuffer(transform_out.w, transform_out.h, &pOut)) // TODO
	&& */ FAILED(hr = GetDeliveryBuffer(m_w, m_h, &pOut))
		|| FAILED(hr = pOut->GetPointer(&pDataOut))) {
		return hr;
	}

	BYTE* pI420[3] = {m_pI420, m_pI420Tmp, NULL};

	if (interlaced) {
		int size = m_w * m_h;
		DeinterlaceBlend(pI420[1],                pI420[0],                m_w,     m_h,     m_w,     m_w);
		DeinterlaceBlend(pI420[1] + size,         pI420[0] + size,         m_w / 2, m_h / 2, m_w / 2, m_w / 2);
		DeinterlaceBlend(pI420[1] + size * 5 / 4, pI420[0] + size * 5 / 4, m_w / 2, m_h / 2, m_w / 2, m_w / 2);
		pI420[2] = pI420[1], pI420[1] = pI420[0], pI420[0] = pI420[2];
	}

	if (transform_out.w != m_w || transform_out.h != m_h) {
		Resize(pI420[0], transform_out.w, transform_out.h, pI420[1], m_w, m_h);
		// only one of these can be true, and when it happens the result image must be in the tmp buffer
		if (transform_out.w == m_w || transform_out.h == m_h) {
			pI420[2] = pI420[1], pI420[1] = pI420[0], pI420[0] = pI420[2];
		}
	}

	CopyBuffer(pDataOut, pI420[0], m_w, m_h, m_w, MEDIASUBTYPE_I420);

	rtStart = 10000i64 * transform_out.timestamp - m_tStart;
	rtStop = rtStart + 1;

	pOut->SetTime(&rtStart, &rtStop);
	pOut->SetMediaTime(NULL, NULL);
	pOut->SetDiscontinuity(pIn->IsDiscontinuity() == S_OK);

#if 0
	DbgLog((LOG_TRACE, 3, _T("V: rtStart = %I64d, rtStop = %I64d, disc = %d, sync = %d"),
			rtStart, rtStop, pOut->IsDiscontinuity() == S_OK, pOut->IsSyncPoint() == S_OK));
#endif

	if (CComQIPtr<IMediaSample2> pMS2 = pOut) {
		AM_SAMPLE2_PROPERTIES props;
		if (SUCCEEDED(pMS2->GetProperties(sizeof(props), (BYTE*)&props))) {
			props.dwTypeSpecificFlags &= ~0x7f;

			if(!interlaced) {
				props.dwTypeSpecificFlags |= AM_VIDEO_FLAG_WEAVE;
			}

			if (top_field_first) {
				props.dwTypeSpecificFlags |= AM_VIDEO_FLAG_FIELD1FIRST;
			}

			if (repeat_field) {
				props.dwTypeSpecificFlags |= AM_VIDEO_FLAG_REPEAT_FIELD;
			}

			pMS2->SetProperties(sizeof(props), (BYTE*)&props);
		}
	}

	return m_pOutput->Deliver(pOut);
}

void CRealVideoDecoder::Resize(BYTE* pIn, DWORD wi, DWORD hi, BYTE* pOut, DWORD wo, DWORD ho)
{
	int si = wi*hi, so = wo*ho;
	ASSERT(((si*so)&3) == 0);

	if (wi < wo) {
		ResizeWidth(pIn, wi, hi, pOut, wo, ho);
		ResizeWidth(pIn + si, wi/2, hi/2, pOut + so, wo/2, ho/2);
		ResizeWidth(pIn + si + si/4, wi/2, hi/2, pOut + so + so/4, wo/2, ho/2);
		if (hi == ho) {
			return;
		}
		ResizeHeight(pOut, wo, hi, pIn, wo, ho);
		ResizeHeight(pOut + so, wo/2, hi/2, pIn + so, wo/2, ho/2);
		ResizeHeight(pOut + so + so/4, wo/2, hi/2, pIn + so + so/4, wo/2, ho/2);
	} else if (hi < ho) {
		ResizeHeight(pIn, wi, hi, pOut, wo, ho);
		ResizeHeight(pIn + si, wi/2, hi/2, pOut + so, wo/2, ho/2);
		ResizeHeight(pIn + si + si/4, wi/2, hi/2, pOut + so + so/4, wo/2, ho/2);
		if (wi == wo) {
			return;
		}
		ASSERT(0);	// this is uncreachable code, but anyway... looks nice being so symmetric
		ResizeWidth(pOut, wi, ho, pIn, wo, ho);
		ResizeWidth(pOut + so, wi/2, ho/2, pIn + so, wo/2, ho/2);
		ResizeWidth(pOut + so + so/4, wi/2, ho/2, pIn + so + so/4, wo/2, ho/2);
	}
}

void CRealVideoDecoder::ResizeWidth(BYTE* pIn, DWORD wi, DWORD hi, BYTE* pOut, DWORD wo, DWORD ho)
{
	for (DWORD y = 0; y < hi; y++, pIn += wi, pOut += wo) {
		if (wi == wo) {
			memcpy(pOut, pIn, wo);
		} else {
			ResizeRow(pIn, wi, 1, pOut, wo, 1);
		}
	}
}

void CRealVideoDecoder::ResizeHeight(BYTE* pIn, DWORD wi, DWORD hi, BYTE* pOut, DWORD wo, DWORD ho)
{
	if (hi == ho) {
		memcpy(pOut, pIn, wo*ho);
	} else {
		for (DWORD x = 0; x < wo; x++, pIn++, pOut++) {
			ResizeRow(pIn, hi, wo, pOut, ho, wo);
		}
	}
}

void CRealVideoDecoder::ResizeRow(BYTE* pIn, DWORD wi, DWORD dpi, BYTE* pOut, DWORD wo, DWORD dpo)
{
	ASSERT(wi < wo);

	if (dpo == 1) {
		for (DWORD i = 0, j = 0, dj = (wi<<16)/wo; i < wo-1; i++, pOut++, j += dj)
			//			pOut[i] = pIn[j>>16];
		{
			BYTE* p = &pIn[j>>16];
			DWORD jf = j&0xffff;
			*pOut = ((p[0]*(0xffff-jf) + p[1]*jf) + 0x7fff) >> 16;
		}

		*pOut = pIn[wi-1];
	} else {
		for (DWORD i = 0, j = 0, dj = (wi<<16)/wo; i < wo-1; i++, pOut += dpo, j += dj)
			//			*pOut = pIn[dpi*(j>>16)];
		{
			BYTE* p = &pIn[dpi*(j>>16)];
			DWORD jf = j&0xffff;
			*pOut = ((p[0]*(0xffff-jf) + p[dpi]*jf) + 0x7fff) >> 16;
		}

		*pOut = pIn[dpi*(wi-1)];
	}
}

static VIDEO_OUTPUT_FORMATS DefaultFormats[] = {
	{&MEDIASUBTYPE_NV12, 3, 12, FCC('NV12')},
	{&MEDIASUBTYPE_YV12, 3, 12, FCC('YV12')},
	{&MEDIASUBTYPE_YUY2, 1, 16, FCC('YUY2')},
	{&MEDIASUBTYPE_I420, 3, 12, FCC('I420')},
	{&MEDIASUBTYPE_IYUV, 3, 12, FCC('IYUV')},
};

void CRealVideoDecoder::GetOutputFormats(int& nNumber, VIDEO_OUTPUT_FORMATS** ppFormats)
{
	nNumber		= _countof(DefaultFormats);
	*ppFormats	= DefaultFormats;
}

HRESULT CRealVideoDecoder::CheckInputType(const CMediaType* mtIn)
{
	if (mtIn->majortype != MEDIATYPE_Video
			|| mtIn->subtype != MEDIASUBTYPE_RV20
			&& mtIn->subtype != MEDIASUBTYPE_RV30
			&& mtIn->subtype != MEDIASUBTYPE_RV40
			&& mtIn->subtype != MEDIASUBTYPE_RV41) {
		return VFW_E_TYPE_NOT_ACCEPTED;
	}

	if (mtIn->formattype == FORMAT_VideoInfo2) {
		VIDEOINFOHEADER2* vih2 = (VIDEOINFOHEADER2*)mtIn->Format();
		if (vih2->dwPictAspectRatioX < (DWORD)vih2->bmiHeader.biWidth
				|| vih2->dwPictAspectRatioY < (DWORD)vih2->bmiHeader.biHeight) {
			return VFW_E_TYPE_NOT_ACCEPTED;
		}
	}

	if (!m_pInput->IsConnected()) {
		if (m_hDrvDll) {
			FreeLibrary(m_hDrvDll);
			m_hDrvDll = NULL;
		}

		CAtlList<CString> paths;
		CString olddll, newdll, oldpath, newpath;

		olddll.Format(_T("drv%c3260.dll"), (TCHAR)((mtIn->subtype.Data1>>16)&0xff));
		newdll =
			mtIn->subtype == FOURCCMap('14VR') ? _T("drvi.dll") :
			mtIn->subtype == FOURCCMap('02VR') ? _T("drv2.dll") :
			_T("drvc.dll");

		CRegKey key;
		TCHAR buff[_MAX_PATH];
		ULONG len = sizeof(buff);
		if (ERROR_SUCCESS == key.Open(HKEY_CLASSES_ROOT, _T("Software\\RealNetworks\\Preferences\\DT_Codecs"), KEY_READ)
				&& ERROR_SUCCESS == key.QueryStringValue(NULL, buff, &len) && _tcslen(buff) > 0) {
			oldpath = buff;
			TCHAR c = oldpath[oldpath.GetLength()-1];
			if (c != '\\' && c != '/') {
				oldpath += '\\';
			}
			key.Close();
		}
		len = sizeof(buff);
		if (ERROR_SUCCESS == key.Open(HKEY_CLASSES_ROOT, _T("Helix\\HelixSDK\\10.0\\Preferences\\DT_Codecs"), KEY_READ)
				&& ERROR_SUCCESS == key.QueryStringValue(NULL, buff, &len) && _tcslen(buff) > 0) {
			newpath = buff;
			TCHAR c = newpath[newpath.GetLength()-1];
			if (c != '\\' && c != '/') {
				newpath += '\\';
			}
			key.Close();
		}

		if (!newpath.IsEmpty()) {
			paths.AddTail(newpath + newdll);
		}
		if (!oldpath.IsEmpty()) {
			paths.AddTail(oldpath + newdll);
		}
		paths.AddTail(newdll);	// default dll paths
		if (!newpath.IsEmpty()) {
			paths.AddTail(newpath + olddll);
		}
		if (!oldpath.IsEmpty()) {
			paths.AddTail(oldpath + olddll);
		}
		paths.AddTail(olddll);	// default dll paths

		POSITION pos = paths.GetHeadPosition();
		do {
			if (pos) {
				m_hDrvDll = LoadLibrary(paths.GetNext(pos));
			}
		} while (pos && !m_hDrvDll);

		if (m_hDrvDll) {
			RVCustomMessage = (PRVCustomMessage)GetProcAddress(m_hDrvDll, "RV20toYUV420CustomMessage");
			RVFree = (PRVFree)GetProcAddress(m_hDrvDll, "RV20toYUV420Free");
			RVHiveMessage = (PRVHiveMessage)GetProcAddress(m_hDrvDll, "RV20toYUV420HiveMessage");
			RVInit = (PRVInit)GetProcAddress(m_hDrvDll, "RV20toYUV420Init");
			RVTransform = (PRVTransform)GetProcAddress(m_hDrvDll, "RV20toYUV420Transform");

			if (!RVCustomMessage) {
				RVCustomMessage = (PRVCustomMessage)GetProcAddress(m_hDrvDll, "RV40toYUV420CustomMessage");
			}
			if (!RVFree) {
				RVFree = (PRVFree)GetProcAddress(m_hDrvDll, "RV40toYUV420Free");
			}
			if (!RVHiveMessage) {
				RVHiveMessage = (PRVHiveMessage)GetProcAddress(m_hDrvDll, "RV40toYUV420HiveMessage");
			}
			if (!RVInit) {
				RVInit = (PRVInit)GetProcAddress(m_hDrvDll, "RV40toYUV420Init");
			}
			if (!RVTransform) {
				RVTransform = (PRVTransform)GetProcAddress(m_hDrvDll, "RV40toYUV420Transform");
			}
		}

		if (!m_hDrvDll || !RVCustomMessage
				|| !RVFree || !RVHiveMessage
				|| !RVInit || !RVTransform) {
			return VFW_E_TYPE_NOT_ACCEPTED;
		}

		if (FAILED(InitRV(mtIn))) {
			return VFW_E_TYPE_NOT_ACCEPTED;
		}
	}

	return S_OK;
}

HRESULT CRealVideoDecoder::CheckTransform(const CMediaType* mtIn, const CMediaType* mtOut)
{
	if (m_pOutput && m_pOutput->IsConnected()) {
		BITMAPINFOHEADER bih1, bih2;
		if (ExtractBIH(mtOut, &bih1) && ExtractBIH(&m_pOutput->CurrentMediaType(), &bih2)
				&& abs(bih1.biHeight) != abs(bih2.biHeight)) {
			return VFW_E_TYPE_NOT_ACCEPTED;
		}
	}

	return __super::CheckTransform(mtIn, mtOut);
}

HRESULT CRealVideoDecoder::StartStreaming()
{
	const CMediaType& mt = m_pInput->CurrentMediaType();
	if (FAILED(InitRV(&mt))) {
		return E_FAIL;
	}

	int size = m_w * m_h;
	m_lastBuffSizeDim = size;

	m_pI420 = (BYTE*)_aligned_malloc(size * 3 / 2, 16);
	if (m_pI420) {
		memset(m_pI420, 0, size);
		memset(m_pI420 + size, 0x80, size / 2);
	} else {
		return E_FAIL;
	}

	m_pI420Tmp = (BYTE*)_aligned_malloc(size * 3 / 2, 16);
	if (m_pI420Tmp) {
		memset(m_pI420Tmp, 0, size);
		memset(m_pI420Tmp + size, 0x80, size / 2);
	} else {
		return E_FAIL;
	}

	return __super::StartStreaming();
}

HRESULT CRealVideoDecoder::StopStreaming()
{
	_aligned_free(m_pI420);
	_aligned_free(m_pI420Tmp);

	FreeRV();

	return __super::StopStreaming();
}

HRESULT CRealVideoDecoder::NewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate)
{
	CAutoLock cAutoLock(&m_csReceive);

	m_timestamp = (DWORD)~0;
	m_fDropFrames = false;

	DWORD tmp[2] = {20, 0};
	RVHiveMessage(tmp, m_dwCookie);

	m_tStart = tStart;
	return __super::NewSegment(tStart, tStop, dRate);
}

HRESULT CRealVideoDecoder::AlterQuality(Quality q)
{
	if (q.Late > 500*10000i64) {
		m_fDropFrames = true;
	}
	if (q.Late <= 0) {
		m_fDropFrames = false;
	}
	//	TRACE(_T("CRealVideoDecoder::AlterQuality: Type=%d, Proportion=%d, Late=%I64d, TimeStamp=%I64d\n"), q.Type, q.Proportion, q.Late, q.TimeStamp);
	return E_NOTIMPL;
}

/////////////////////////

//
// CRealAudioDecoder
//

CRealAudioDecoder::CRealAudioDecoder(LPUNKNOWN lpunk, HRESULT* phr)
	: CTransformFilter(NAME("CRealAudioDecoder"), lpunk, __uuidof(this))
	, m_hDrvDll(NULL)
	, m_dwCookie(0)
{
	if (phr) {
		*phr = S_OK;
	}
}

CRealAudioDecoder::~CRealAudioDecoder()
{
	//	FreeRA();
	if (m_hDrvDll) {
		FreeLibrary(m_hDrvDll);
	}
}

HRESULT CRealAudioDecoder::InitRA(const CMediaType* pmt)
{
	FreeRA();

	HRESULT hr;

	if (RAOpenCodec2 && FAILED(hr = RAOpenCodec2(&m_dwCookie, m_dllpath))
			|| RAOpenCodec && FAILED(hr = RAOpenCodec(&m_dwCookie))) {
		return VFW_E_TYPE_NOT_ACCEPTED;
	}

	WAVEFORMATEX* pwfe = (WAVEFORMATEX*)pmt->Format();

	// someone might be doing cbSize = sizeof(WAVEFORMATEX), chances of
	// cbSize being really sizeof(WAVEFORMATEX) is less than this,
	// especially with our rm splitter ;)
	DWORD cbSize = pwfe->cbSize;
	if (cbSize == sizeof(WAVEFORMATEX)) {
		ASSERT(0);
		cbSize = 0;
	}

	WORD wBitsPerSample = pwfe->wBitsPerSample;
	if (!wBitsPerSample) {
		wBitsPerSample = 16;
	}

#pragma pack(push, 1)
	struct {
		DWORD freq;
		WORD bpsample, channels, quality;
		DWORD bpframe, packetsize, extralen;
		void* extra;
	} initdata = {
		pwfe->nSamplesPerSec, wBitsPerSample, pwfe->nChannels, 100,
		0, 0, 0, NULL
	};
#pragma pack(pop)

	CAutoVectorPtr<BYTE> pBuff;

	if (pmt->subtype == MEDIASUBTYPE_RAW_AAC1) {
		pBuff.Allocate(cbSize + 1);
		pBuff[0] = 0x02;
		memcpy(pBuff+1, pwfe + 1, cbSize);
		initdata.extralen = cbSize + 1;
		initdata.extra = pBuff;
	} else {
		if (pmt->FormatLength() <= sizeof(WAVEFORMATEX) + cbSize) {	// must have type_specific_data appended
			return VFW_E_TYPE_NOT_ACCEPTED;
		}

		BYTE* fmt = pmt->Format() + sizeof(WAVEFORMATEX) + cbSize;

		for (int i = 0, len = pmt->FormatLength() - (sizeof(WAVEFORMATEX) + cbSize); i < len-4; i++, fmt++) {
			if (fmt[0] == '.' || fmt[1] == 'r' || fmt[2] == 'a') {
				break;
			}
		}

		m_rai = *(rainfo*)fmt;
		m_rai.bswap();

		BYTE* p;

		if (m_rai.version2 == 4) {
			p = (BYTE*)((rainfo4*)fmt+1);
			int len = *p++;
			p += len;
			len = *p++;
			p += len;
			ASSERT(len == 4);
		} else if (m_rai.version2 == 5) {
			p = (BYTE*)((rainfo5*)fmt+1);
		} else {
			return VFW_E_TYPE_NOT_ACCEPTED;
		}

		p += 3;
		if (m_rai.version2 == 5) {
			p++;
		}

		initdata.bpframe = m_rai.sub_packet_size;
		initdata.packetsize = m_rai.coded_frame_size;
		initdata.extralen = min((pmt->Format() + pmt->FormatLength()) - (p + 4), (LONG)*(DWORD*)p);
		initdata.extra = p + 4;
	}

	if (FAILED(hr = RAInitDecoder(m_dwCookie, &initdata))) {
		return VFW_E_TYPE_NOT_ACCEPTED;
	}

	if (RASetPwd) {
		RASetPwd(m_dwCookie, "Ardubancel Quazanga");
	}

	if (RASetFlavor && FAILED(hr = RASetFlavor(m_dwCookie, m_rai.flavor))) {
		return VFW_E_TYPE_NOT_ACCEPTED;
	}

	return S_OK;
}

void CRealAudioDecoder::FreeRA()
{
	if (m_dwCookie) {
		RAFreeDecoder(m_dwCookie);
		RACloseCodec(m_dwCookie);
		m_dwCookie = 0;
	}
}

HRESULT CRealAudioDecoder::Receive(IMediaSample* pIn)
{
	CAutoLock cAutoLock(&m_csReceive);

	HRESULT hr;

	AM_SAMPLE2_PROPERTIES* const pProps = m_pInput->SampleProps();
	if (pProps->dwStreamId != AM_STREAM_MEDIA) {
		return m_pOutput->Deliver(pIn);
	}

	BYTE* pDataIn = NULL;
	if (FAILED(hr = pIn->GetPointer(&pDataIn))) {
		return hr;
	}
	BYTE* pDataInOrg = pDataIn;
	UNREFERENCED_PARAMETER(pDataInOrg);

	long len = pIn->GetActualDataLength();
	if (len <= 0) {
		return S_OK;
	}

	REFERENCE_TIME rtStart, rtStop;
	pIn->GetTime(&rtStart, &rtStop);
	/*
	if(pIn->IsPreroll() == S_OK || rtStart < 0)
		return S_OK;
	*/

	if (S_OK == pIn->IsSyncPoint()) {
		m_bufflen = 0;
		m_rtBuffStart = rtStart;
		m_fBuffDiscontinuity = pIn->IsDiscontinuity() == S_OK;
	}

	BYTE* src = NULL;
	BYTE* dst = NULL;

	int w = m_rai.coded_frame_size;
	int h = m_rai.sub_packet_h;
	int sps = m_rai.sub_packet_size;

	if (m_pInput->CurrentMediaType().subtype == MEDIASUBTYPE_RAAC
			|| m_pInput->CurrentMediaType().subtype == MEDIASUBTYPE_RACP
			|| m_pInput->CurrentMediaType().subtype == MEDIASUBTYPE_RAW_AAC1) {
		src = pDataIn;
		dst = pDataIn + len;
		w = len;
	} else {
		memcpy(&m_buff[m_bufflen], pDataIn, len);
		m_bufflen += len;

		len = w*h;

		if (m_bufflen >= len) {
			ASSERT(m_bufflen == len);

			src = m_buff;
			dst = m_buff + len;

			if (sps > 0
					&& (m_pInput->CurrentMediaType().subtype == MEDIASUBTYPE_COOK
						|| m_pInput->CurrentMediaType().subtype == MEDIASUBTYPE_ATRC)) {
				for (int y = 0; y < h; y++) {
					for (int x = 0, w2 = w / sps; x < w2; x++) {
						// TRACE(_T("--- %d, %d\n"), (h*x+((h+1)/2)*(y&1)+(y>>1)), sps*(h*x+((h+1)/2)*(y&1)+(y>>1)));
						memcpy(dst + sps*(h*x+((h+1)/2)*(y&1)+(y>>1)), src, sps);
						src += sps;
					}
				}

				src = m_buff + len;
				dst = m_buff + len*2;
			} else if (m_pInput->CurrentMediaType().subtype == MEDIASUBTYPE_SIPR || m_pInput->CurrentMediaType().subtype == MEDIASUBTYPE_SIPR_WAVE) {
				// http://mplayerhq.hu/pipermail/mplayer-dev-eng/2002-August/010569.html

				static BYTE sipr_swaps[38][2]= {
					{0,63},{1,22},{2,44},{3,90},{5,81},{7,31},{8,86},{9,58},{10,36},{12,68},
					{13,39},{14,73},{15,53},{16,69},{17,57},{19,88},{20,34},{21,71},{24,46},
					{25,94},{26,54},{28,75},{29,50},{32,70},{33,92},{35,74},{38,85},{40,56},
					{42,87},{43,65},{45,59},{48,79},{49,93},{51,89},{55,95},{61,76},{67,83},
					{77,80}
				};

				int bs=h*w*2/96;	// nibbles per subpacket
				for (int n=0; n<38; n++) {
					int i=bs*sipr_swaps[n][0];
					int o=bs*sipr_swaps[n][1];
					// swap nibbles of block 'i' with 'o'      TODO: optimize
					for (int j=0; j<bs; j++) {
						int x=(i&1) ? (src[(i>>1)]>>4) : (src[(i>>1)]&15);
						int y=(o&1) ? (src[(o>>1)]>>4) : (src[(o>>1)]&15);
						if (o&1) {
							src[(o>>1)]=(src[(o>>1)]&0x0F)|(x<<4);
						} else {
							src[(o>>1)]=(src[(o>>1)]&0xF0)|x;
						}
						if (i&1) {
							src[(i>>1)]=(src[(i>>1)]&0x0F)|(y<<4);
						} else {
							src[(i>>1)]=(src[(i>>1)]&0xF0)|y;
						}
						++i;
						++o;
					}
				}
			}

			m_bufflen = 0;
		}
	}

	rtStart = m_rtBuffStart;

	for (; src < dst; src += w) {
		CComPtr<IMediaSample> pOut;
		BYTE* pDataOut = NULL;
		if (FAILED(hr = m_pOutput->GetDeliveryBuffer(&pOut, NULL, NULL, 0))
				|| FAILED(hr = pOut->GetPointer(&pDataOut))) {
			return hr;
		}

		AM_MEDIA_TYPE* pmt;
		if (SUCCEEDED(pOut->GetMediaType(&pmt)) && pmt) {
			CMediaType mt(*pmt);
			m_pOutput->SetMediaType(&mt);
			DeleteMediaType(pmt);
		}

		hr = RADecode(m_dwCookie, src, w, pDataOut, &len, -1);

		if (FAILED(hr)) {
			TRACE(_T("RA returned an error code!!!\n"));
			continue;
			//			return hr;
		}

		WAVEFORMATEX* pwfe = (WAVEFORMATEX*)m_pOutput->CurrentMediaType().Format();

		rtStop = rtStart + 1000i64*len/pwfe->nAvgBytesPerSec*10000;
		pOut->SetTime(&rtStart, &rtStop);
		pOut->SetMediaTime(NULL, NULL);

		pOut->SetDiscontinuity(m_fBuffDiscontinuity);
		m_fBuffDiscontinuity = false;
		pOut->SetSyncPoint(TRUE);

		pOut->SetActualDataLength(len);

#if 0
		DbgLog((LOG_TRACE, 0, _T("A: rtStart=%I64d, rtStop=%I64d, disc=%d, sync=%d"),
				rtStart, rtStop, pOut->IsDiscontinuity() == S_OK, pOut->IsSyncPoint() == S_OK));
#endif

		if (rtStart >= 0 && S_OK != (hr = m_pOutput->Deliver(pOut))) {
			return hr;
		}

		rtStart = rtStop;
	}

	m_rtBuffStart = rtStart;

	return S_OK;
}

HRESULT CRealAudioDecoder::CheckInputType(const CMediaType* mtIn)
{
	if (mtIn->majortype != MEDIATYPE_Audio
			|| mtIn->subtype != MEDIASUBTYPE_14_4
			&& mtIn->subtype != MEDIASUBTYPE_28_8
			&& mtIn->subtype != MEDIASUBTYPE_ATRC
			&& mtIn->subtype != MEDIASUBTYPE_COOK
			&& mtIn->subtype != MEDIASUBTYPE_DNET
			&& mtIn->subtype != MEDIASUBTYPE_SIPR
			&& mtIn->subtype != MEDIASUBTYPE_SIPR_WAVE
			&& mtIn->subtype != MEDIASUBTYPE_RAAC
			&& mtIn->subtype != MEDIASUBTYPE_RACP
			&& mtIn->subtype != MEDIASUBTYPE_RAW_AAC1) {
		return VFW_E_TYPE_NOT_ACCEPTED;
	}

	if (!m_pInput->IsConnected()) {
		if (m_hDrvDll) {
			FreeLibrary(m_hDrvDll);
			m_hDrvDll = NULL;
		}

		CAtlList<CString> paths;
		CString olddll, newdll, oldpath, newpath;

		TCHAR fourcc[5] = {
			(TCHAR)((mtIn->subtype.Data1>>0)&0xff),
			(TCHAR)((mtIn->subtype.Data1>>8)&0xff),
			(TCHAR)((mtIn->subtype.Data1>>16)&0xff),
			(TCHAR)((mtIn->subtype.Data1>>24)&0xff),
			0
		};

		if (!_tcscmp(_T("RACP"), fourcc) || !_tcscmp(_T("\xff"), fourcc)) {
			_tcscpy_s(fourcc, _T("RAAC"));
		}

		olddll.Format(_T("%s3260.dll"), fourcc);
		newdll.Format(_T("%s.dll"), fourcc);

		CRegKey key;
		TCHAR buff[_MAX_PATH];
		ULONG len = sizeof(buff);
		if (ERROR_SUCCESS == key.Open(HKEY_CLASSES_ROOT, _T("Software\\RealNetworks\\Preferences\\DT_Codecs"), KEY_READ)
				&& ERROR_SUCCESS == key.QueryStringValue(NULL, buff, &len) && _tcslen(buff) > 0) {
			oldpath = buff;
			TCHAR c = oldpath[oldpath.GetLength()-1];
			if (c != '\\' && c != '/') {
				oldpath += '\\';
			}
			key.Close();
		}
		len = sizeof(buff);
		if (ERROR_SUCCESS == key.Open(HKEY_CLASSES_ROOT, _T("Helix\\HelixSDK\\10.0\\Preferences\\DT_Codecs"), KEY_READ)
				&& ERROR_SUCCESS == key.QueryStringValue(NULL, buff, &len) && _tcslen(buff) > 0) {
			newpath = buff;
			TCHAR c = newpath[newpath.GetLength()-1];
			if (c != '\\' && c != '/') {
				newpath += '\\';
			}
			key.Close();
		}

		if (!newpath.IsEmpty()) {
			paths.AddTail(newpath + newdll);
		}
		if (!oldpath.IsEmpty()) {
			paths.AddTail(oldpath + newdll);
		}
		paths.AddTail(newdll);	// default dll paths
		if (!newpath.IsEmpty()) {
			paths.AddTail(newpath + olddll);
		}
		if (!oldpath.IsEmpty()) {
			paths.AddTail(oldpath + olddll);
		}
		paths.AddTail(olddll);	// default dll paths

		POSITION pos = paths.GetHeadPosition();
		do {
			if (pos) {
				m_hDrvDll = LoadLibrary(paths.GetNext(pos));
			}
		} while (pos && !m_hDrvDll);

		if (m_hDrvDll) {
			RACloseCodec = (PCloseCodec)GetProcAddress(m_hDrvDll, "RACloseCodec");
			RADecode = (PDecode)GetProcAddress(m_hDrvDll, "RADecode");
			RAFlush = (PFlush)GetProcAddress(m_hDrvDll, "RAFlush");
			RAFreeDecoder = (PFreeDecoder)GetProcAddress(m_hDrvDll, "RAFreeDecoder");
			RAGetFlavorProperty = (PGetFlavorProperty)GetProcAddress(m_hDrvDll, "RAGetFlavorProperty");
			RAInitDecoder = (PInitDecoder)GetProcAddress(m_hDrvDll, "RAInitDecoder");
			RAOpenCodec = (POpenCodec)GetProcAddress(m_hDrvDll, "RAOpenCodec");
			RAOpenCodec2 = (POpenCodec2)GetProcAddress(m_hDrvDll, "RAOpenCodec2");
			RASetFlavor = (PSetFlavor)GetProcAddress(m_hDrvDll, "RASetFlavor");
			RASetDLLAccessPath = (PSetDLLAccessPath)GetProcAddress(m_hDrvDll, "RASetDLLAccessPath");
			RASetPwd = (PSetPwd)GetProcAddress(m_hDrvDll, "RASetPwd");
		}

		if (!m_hDrvDll || !RACloseCodec || !RADecode /*|| !RAFlush*/
				|| !RAFreeDecoder || !RAGetFlavorProperty || !RAInitDecoder
				|| !(RAOpenCodec || RAOpenCodec2) /*|| !RASetFlavor*/) {
			return VFW_E_TYPE_NOT_ACCEPTED;
		}

		if (m_hDrvDll) {
			char buff[_MAX_PATH];
			GetModuleFileNameA(m_hDrvDll, buff, MAX_PATH);
			CPathA p(buff);
			p.RemoveFileSpec();
			p.AddBackslash();
			m_dllpath = p.m_strPath;
			if (RASetDLLAccessPath) {
				RASetDLLAccessPath("DT_Codecs=" + m_dllpath);
			}
		}

		if (FAILED(InitRA(mtIn))) {
			return VFW_E_TYPE_NOT_ACCEPTED;
		}
	}

	return S_OK;
}

HRESULT CRealAudioDecoder::CheckTransform(const CMediaType* mtIn, const CMediaType* mtOut)
{
	return mtIn->majortype == MEDIATYPE_Audio && (mtIn->subtype == MEDIASUBTYPE_14_4
			|| mtIn->subtype == MEDIASUBTYPE_28_8
			|| mtIn->subtype == MEDIASUBTYPE_ATRC
			|| mtIn->subtype == MEDIASUBTYPE_COOK
			|| mtIn->subtype == MEDIASUBTYPE_DNET
			|| mtIn->subtype == MEDIASUBTYPE_SIPR
			|| mtIn->subtype == MEDIASUBTYPE_SIPR_WAVE
			|| mtIn->subtype == MEDIASUBTYPE_RAAC
			|| mtIn->subtype == MEDIASUBTYPE_RACP
			|| mtIn->subtype == MEDIASUBTYPE_RAW_AAC1)
		   && mtOut->majortype == MEDIATYPE_Audio && mtOut->subtype == MEDIASUBTYPE_PCM
		   ? S_OK
		   : VFW_E_TYPE_NOT_ACCEPTED;
}

HRESULT CRealAudioDecoder::DecideBufferSize(IMemAllocator* pAllocator, ALLOCATOR_PROPERTIES* pProperties)
{
	if (m_pInput->IsConnected() == FALSE) {
		return E_UNEXPECTED;
	}

	CComPtr<IMemAllocator> pAllocatorIn;
	m_pInput->GetAllocator(&pAllocatorIn);
	if (!pAllocatorIn) {
		return E_UNEXPECTED;
	}

	WAVEFORMATEX* pwfe = (WAVEFORMATEX*)m_pOutput->CurrentMediaType().Format();

	WORD wBitsPerSample = pwfe->wBitsPerSample;
	if (!wBitsPerSample) {
		wBitsPerSample = 16;
	}

	// ok, maybe this is too much...
	pProperties->cBuffers = 8;
	pProperties->cbBuffer = pwfe->nChannels*pwfe->nSamplesPerSec*wBitsPerSample>>3; // nAvgBytesPerSec;
	pProperties->cbAlign = 1;
	pProperties->cbPrefix = 0;

	HRESULT hr;
	ALLOCATOR_PROPERTIES Actual;
	if (FAILED(hr = pAllocator->SetProperties(pProperties, &Actual))) {
		return hr;
	}

	return(pProperties->cBuffers > Actual.cBuffers || pProperties->cbBuffer > Actual.cbBuffer
		   ? E_FAIL
		   : NOERROR);
}

HRESULT CRealAudioDecoder::GetMediaType(int iPosition, CMediaType* pmt)
{
	if (m_pInput->IsConnected() == FALSE) {
		return E_UNEXPECTED;
	}

	*pmt = m_pInput->CurrentMediaType();
	pmt->subtype = MEDIASUBTYPE_PCM;
	WAVEFORMATEX* pwfe = (WAVEFORMATEX*)pmt->ReallocFormatBuffer(sizeof(WAVEFORMATEX));

	if (iPosition < 0) {
		return E_INVALIDARG;
	}
	if (iPosition > (pwfe->nChannels > 2 && pwfe->nChannels <= 6 ? 1 : 0)) {
		return VFW_S_NO_MORE_ITEMS;
	}

	if (!pwfe->wBitsPerSample) {
		pwfe->wBitsPerSample = 16;
	}

	pwfe->cbSize = 0;
	pwfe->wFormatTag = WAVE_FORMAT_PCM;
	pwfe->nBlockAlign = pwfe->nChannels*pwfe->wBitsPerSample>>3;
	pwfe->nAvgBytesPerSec = pwfe->nSamplesPerSec*pwfe->nBlockAlign;

	if (iPosition == 0 && pwfe->nChannels > 2 && pwfe->nChannels <= 6) {
		static DWORD chmask[] = {
			KSAUDIO_SPEAKER_DIRECTOUT,
			KSAUDIO_SPEAKER_MONO,
			KSAUDIO_SPEAKER_STEREO,
			KSAUDIO_SPEAKER_STEREO|SPEAKER_FRONT_CENTER,
			KSAUDIO_SPEAKER_QUAD,
			KSAUDIO_SPEAKER_QUAD|SPEAKER_FRONT_CENTER,
			KSAUDIO_SPEAKER_5POINT1
		};

		WAVEFORMATEXTENSIBLE* pwfex = (WAVEFORMATEXTENSIBLE*)pmt->ReallocFormatBuffer(sizeof(WAVEFORMATEXTENSIBLE));
		pwfex->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
		pwfex->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
		pwfex->dwChannelMask = chmask[pwfex->Format.nChannels];
		pwfex->Samples.wValidBitsPerSample = pwfex->Format.wBitsPerSample;
		pwfex->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
	}

	return S_OK;
}

HRESULT CRealAudioDecoder::StartStreaming()
{
	int w = m_rai.coded_frame_size;
	int h = m_rai.sub_packet_h;
	int sps = m_rai.sub_packet_size;
	UNREFERENCED_PARAMETER(sps);

	int len = w*h;

	m_buff.Allocate(len*2);
	m_bufflen = 0;
	m_rtBuffStart = 0;

	return __super::StartStreaming();
}

HRESULT CRealAudioDecoder::StopStreaming()
{
	m_buff.Free();
	m_bufflen = 0;

	return __super::StopStreaming();
}

HRESULT CRealAudioDecoder::EndOfStream()
{
	return __super::EndOfStream();
}

HRESULT CRealAudioDecoder::BeginFlush()
{
	return __super::BeginFlush();
}

HRESULT CRealAudioDecoder::EndFlush()
{
	return __super::EndFlush();
}

HRESULT CRealAudioDecoder::NewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate)
{
	CAutoLock cAutoLock(&m_csReceive);
	m_tStart = tStart;
	m_bufflen = 0;
	m_rtBuffStart = 0;
	return __super::NewSegment(tStart, tStop, dRate);
}
