/*
* (C) 2003-2006 Gabest
* (C) 2006-2012 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"
#ifdef STANDALONE_FILTER
#include
#endif
#include "ShoutcastSource.h"
#include "../../../DSUtil/DSUtil.h"
#include "moreuuids.h"
#define MAXFRAMESIZE ((144 * 320000 / 8000) + 1)
#define BUFFERS 2
#define MINBUFFERLENGTH 1000000i64
#define AVGBUFFERLENGTH 30000000i64
#define MAXBUFFERLENGTH 100000000i64
static const DWORD s_bitrate[2][16] = {
{1, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0},
{1, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0}
};
static const DWORD s_freq[4][4] = {
{11025, 12000, 8000, 0},
{ 0, 0, 0, 0},
{22050, 24000, 16000, 0},
{44100, 48000, 32000, 0}
};
static const BYTE s_channels[4] = {
2, 2, 2, 1 // stereo, joint stereo, dual, mono
};
typedef struct {
WORD sync;
BYTE version;
BYTE layer;
DWORD bitrate;
DWORD freq;
BYTE channels;
DWORD framesize;
bool ExtractHeader(CSocket& socket) {
BYTE buff[4];
if (4 != socket.Receive(buff, 4, MSG_PEEK)) {
return false;
}
sync = (buff[0] << 4) | (buff[1] >> 4) | 1;
version = (buff[1] >> 3) & 3;
layer = 4 - ((buff[1] >> 1) & 3);
bitrate = s_bitrate[version & 1][buff[2] >> 4] * 1000;
freq = s_freq[version][(buff[2] >> 2) & 3];
channels = s_channels[(buff[3] >> 6) & 3];
framesize = freq ? ((((version & 1) ? 144 : 72) * bitrate / freq) + ((buff[2] >> 1) & 1)) : 0;
return (sync == 0xfff && layer == 3 && bitrate != 0 && freq != 0);
}
} mp3hdr;
#ifdef STANDALONE_FILTER
const AMOVIESETUP_MEDIATYPE sudPinTypesOut[] = {
{&MEDIATYPE_Audio, &MEDIASUBTYPE_MP3},
};
const AMOVIESETUP_PIN sudOpPin[] = {
{L"Output", FALSE, TRUE, FALSE, FALSE, &CLSID_NULL, NULL, _countof(sudPinTypesOut), sudPinTypesOut}
};
const AMOVIESETUP_FILTER sudFilter[] = {
{&__uuidof(CShoutcastSource), ShoutcastSourceName, MERIT_NORMAL, _countof(sudOpPin), sudOpPin, CLSID_LegacyAmFilterCategory}
};
CFactoryTemplate g_Templates[] = {
{sudFilter[0].strName, sudFilter[0].clsID, CreateInstance, NULL, &sudFilter[0]}
};
int g_cTemplates = _countof(g_Templates);
STDAPI DllRegisterServer()
{
return AMovieDllRegisterServer2(TRUE);
}
STDAPI DllUnregisterServer()
{
return AMovieDllRegisterServer2(FALSE);
}
#include "../../FilterApp.h"
class CShoutcastSourceApp : public CFilterApp
{
public:
BOOL InitInstance() {
if (!__super::InitInstance()) {
return FALSE;
}
/*
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
AfxMessageBox(_T("AfxWinInit failed!"));
return FALSE;
}
*/
if (!AfxSocketInit(NULL)) {
AfxMessageBox(_T("AfxSocketInit failed!"));
return FALSE;
}
return TRUE;
}
};
CShoutcastSourceApp theApp;
#endif
//
// CShoutcastSource
//
CShoutcastSource::CShoutcastSource(LPUNKNOWN lpunk, HRESULT* phr)
: CSource(NAME("CShoutcastSource"), lpunk, __uuidof(this))
{
#ifndef STANDALONE_FILTER
AfxSocketInit();
#endif
}
CShoutcastSource::~CShoutcastSource()
{
}
STDMETHODIMP CShoutcastSource::NonDelegatingQueryInterface(REFIID riid, void** ppv)
{
CheckPointer(ppv, E_POINTER);
return
QI(IFileSourceFilter)
QI(IAMFilterMiscFlags)
QI(IAMOpenProgress)
QI2(IAMMediaContent)
__super::NonDelegatingQueryInterface(riid, ppv);
}
// IFileSourceFilter
STDMETHODIMP CShoutcastSource::Load(LPCOLESTR pszFileName, const AM_MEDIA_TYPE* pmt)
{
if (GetPinCount() > 0) {
return VFW_E_ALREADY_CONNECTED;
}
HRESULT hr = E_OUTOFMEMORY;
if (!(DEBUG_NEW CShoutcastStream(pszFileName, this, &hr)) || FAILED(hr)) {
return hr;
}
m_fn = pszFileName;
return S_OK;
}
STDMETHODIMP CShoutcastSource::GetCurFile(LPOLESTR* ppszFileName, AM_MEDIA_TYPE* pmt)
{
if (!ppszFileName) {
return E_POINTER;
}
*ppszFileName = (LPOLESTR)CoTaskMemAlloc((m_fn.GetLength() + 1) * sizeof(WCHAR));
if (!(*ppszFileName)) {
return E_OUTOFMEMORY;
}
wcscpy_s(*ppszFileName, m_fn.GetLength() + 1, m_fn);
return S_OK;
}
// IAMFilterMiscFlags
ULONG CShoutcastSource::GetMiscFlags()
{
return AM_FILTER_MISC_FLAGS_IS_SOURCE;
}
// IAMOpenProgress
STDMETHODIMP CShoutcastSource::QueryProgress(LONGLONG* pllTotal, LONGLONG* pllCurrent)
{
if (m_iPins == 1) {
if (pllTotal) {
*pllTotal = 100;
}
if (pllCurrent) {
*pllCurrent = (static_cast(m_paStreams[0]))->GetBufferFullness();
}
return S_OK;
}
return E_UNEXPECTED;
}
STDMETHODIMP CShoutcastSource::AbortOperation()
{
return E_NOTIMPL;
}
// IAMMediaContent
STDMETHODIMP CShoutcastSource::get_Title(BSTR* pbstrTitle)
{
CheckPointer(pbstrTitle, E_POINTER);
if (m_iPins == 1) {
*pbstrTitle = (static_cast(m_paStreams[0]))->GetTitle().AllocSysString();
return S_OK;
}
return E_UNEXPECTED;
}
STDMETHODIMP CShoutcastSource::QueryFilterInfo(FILTER_INFO* pInfo)
{
CheckPointer(pInfo, E_POINTER);
ValidateReadWritePtr(pInfo, sizeof(FILTER_INFO));
wcscpy_s(pInfo->achName, ShoutcastSourceName);
pInfo->pGraph = m_pGraph;
if (m_pGraph) {
m_pGraph->AddRef();
}
return S_OK;
}
// CShoutcastStream
CShoutcastStream::CShoutcastStream(const WCHAR* wfn, CShoutcastSource* pParent, HRESULT* phr)
: CSourceStream(NAME("ShoutcastStream"), phr, pParent, L"Output")
, m_fBuffering(false)
{
ASSERT(phr);
*phr = S_OK;
CString fn(wfn);
if (fn.Find(_T("://")) < 0) {
fn = _T("http://") + fn;
}
if (!m_url.CrackUrl(fn)) {
*phr = E_FAIL;
return;
}
if (m_url.GetUrlPathLength() == 0) {
m_url.SetUrlPath(_T("/"));
}
if (m_url.GetPortNumber() == ATL_URL_INVALID_PORT_NUMBER) {
m_url.SetPortNumber(ATL_URL_DEFAULT_HTTP_PORT);
}
if (m_url.GetScheme() != ATL_URL_SCHEME_HTTP) {
*phr = E_FAIL;
return;
}
if (!m_socket.Create() || !m_socket.Connect(m_url)) {
*phr = E_FAIL;
return;
}
m_socket.Close();
}
CShoutcastStream::~CShoutcastStream()
{
}
void CShoutcastStream::EmptyBuffer()
{
CAutoLock cAutoLock(&m_queue);
m_queue.RemoveAll();
}
LONGLONG CShoutcastStream::GetBufferFullness()
{
CAutoLock cAutoLock(&m_queue);
if (!m_fBuffering) {
return 100;
}
if (m_queue.IsEmpty()) {
return 0;
}
LONGLONG ret = 100i64 * (m_queue.GetTail().rtStart - m_queue.GetHead().rtStart) / AVGBUFFERLENGTH;
return min(ret, 100);
}
CString CShoutcastStream::GetTitle()
{
CAutoLock cAutoLock(&m_queue);
return m_title;
}
HRESULT CShoutcastStream::DecideBufferSize(IMemAllocator* pAlloc, ALLOCATOR_PROPERTIES* pProperties)
{
ASSERT(pAlloc);
ASSERT(pProperties);
HRESULT hr = NOERROR;
pProperties->cBuffers = BUFFERS;
pProperties->cbBuffer = MAXFRAMESIZE;
ALLOCATOR_PROPERTIES Actual;
if (FAILED(hr = pAlloc->SetProperties(pProperties, &Actual))) {
return hr;
}
if (Actual.cbBuffer < pProperties->cbBuffer) {
return E_FAIL;
}
ASSERT(Actual.cBuffers == pProperties->cBuffers);
return NOERROR;
}
HRESULT CShoutcastStream::FillBuffer(IMediaSample* pSample)
{
HRESULT hr;
BYTE* pData = NULL;
if (FAILED(hr = pSample->GetPointer(&pData)) || !pData) {
return S_FALSE;
}
do {
// do we have to refill our buffer?
{
CAutoLock cAutoLock(&m_queue);
if (!m_queue.IsEmpty() && m_queue.GetHead().rtStart < m_queue.GetTail().rtStart - MINBUFFERLENGTH) {
break; // nope, that's great
}
}
TRACE(_T("START BUFFERING\n"));
m_fBuffering = true;
for (;;) {
if (fExitThread) { // playback stopped?
return S_FALSE;
}
Sleep(50);
CAutoLock cAutoLock(&m_queue);
if (!m_queue.IsEmpty() && m_queue.GetHead().rtStart < m_queue.GetTail().rtStart - AVGBUFFERLENGTH) {
break; // this is enough
}
}
pSample->SetDiscontinuity(TRUE);
DeliverBeginFlush();
DeliverEndFlush();
DeliverNewSegment(0, ~0, 1.0);
TRACE(_T("END BUFFERING\n"));
m_fBuffering = false;
} while (false);
{
CAutoLock cAutoLock(&m_queue);
ASSERT(!m_queue.IsEmpty());
if (!m_queue.IsEmpty()) {
mp3frame f = m_queue.RemoveHead();
DWORD len = min((DWORD)pSample->GetSize(), f.len);
memcpy(pData, f.pData, len);
pSample->SetActualDataLength(len);
pSample->SetTime(&f.rtStart, &f.rtStop);
m_title = f.title;
}
}
pSample->SetSyncPoint(TRUE);
return S_OK;
}
HRESULT CShoutcastStream::GetMediaType(int iPosition, CMediaType* pmt)
{
CAutoLock cAutoLock(m_pFilter->pStateLock());
if (iPosition < 0) {
return E_INVALIDARG;
}
if (iPosition > 0) {
return VFW_S_NO_MORE_ITEMS;
}
pmt->SetType(&MEDIATYPE_Audio);
pmt->SetSubtype(&MEDIASUBTYPE_MP3);
pmt->SetFormatType(&FORMAT_WaveFormatEx);
WAVEFORMATEX* wfe = (WAVEFORMATEX*)pmt->AllocFormatBuffer(sizeof(WAVEFORMATEX));
memset(wfe, 0, sizeof(WAVEFORMATEX));
wfe->wFormatTag = (WORD)MEDIASUBTYPE_MP3.Data1;
wfe->nChannels = (WORD)m_socket.m_channels;
wfe->nSamplesPerSec = m_socket.m_freq;
wfe->nAvgBytesPerSec = m_socket.m_bitrate / 8;
wfe->nBlockAlign = 1;
wfe->wBitsPerSample = 0;
return NOERROR;
}
HRESULT CShoutcastStream::CheckMediaType(const CMediaType* pmt)
{
if (pmt->majortype == MEDIATYPE_Audio
&& pmt->subtype == MEDIASUBTYPE_MP3
&& pmt->formattype == FORMAT_WaveFormatEx) {
return S_OK;
}
return E_INVALIDARG;
}
static UINT SocketThreadProc(LPVOID pParam)
{
return (static_cast(pParam))->SocketThreadProc();
}
UINT CShoutcastStream::SocketThreadProc()
{
fExitThread = false;
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
AfxSocketInit();
CAutoVectorPtr pData;
if (!m_socket.Create() || !m_socket.Connect(m_url)
|| !pData.Allocate(max(m_socket.m_metaint, MAXFRAMESIZE))) {
m_socket.Close();
return 1;
}
REFERENCE_TIME m_rtSampleTime = 0;
while (!fExitThread) {
int len = MAXFRAMESIZE;
len = m_socket.Receive(pData, len);
if (len <= 0) {
break;
}
mp3frame f(len);
memcpy(f.pData, pData, len);
f.rtStop = (f.rtStart = m_rtSampleTime) + (10000000i64 * len * 8 / m_socket.m_bitrate);
m_rtSampleTime = f.rtStop;
f.title = m_socket.m_title;
if (f.title.IsEmpty()) {
f.title = m_socket.m_url;
}
CAutoLock cAutoLock(&m_queue);
m_queue.AddTail(f);
}
m_socket.Close();
return 0;
}
HRESULT CShoutcastStream::OnThreadCreate()
{
EmptyBuffer();
fExitThread = true;
m_hSocketThread = AfxBeginThread(::SocketThreadProc, this)->m_hThread;
while (fExitThread) {
Sleep(10);
}
return NOERROR;
}
HRESULT CShoutcastStream::OnThreadDestroy()
{
EmptyBuffer();
fExitThread = true;
m_socket.CancelBlockingCall();
WaitForSingleObject(m_hSocketThread, (DWORD) - 1);
return NOERROR;
}
HRESULT CShoutcastStream::Inactive()
{
fExitThread = true;
return __super::Inactive();
}
//
int CShoutcastStream::CShoutcastSocket::Receive(void* lpBuf, int nBufLen, int nFlags)
{
if (nFlags & MSG_PEEK) {
return __super::Receive(lpBuf, nBufLen, nFlags);
}
if (m_metaint > 0 && m_nBytesRead + nBufLen > m_metaint) {
nBufLen = m_metaint - m_nBytesRead;
}
int len = __super::Receive(lpBuf, nBufLen, nFlags);
if (len <= 0) {
return len;
}
if ((m_nBytesRead += len) == m_metaint) {
m_nBytesRead = 0;
static BYTE buff[255 * 16], b = 0;
memset(buff, 0, sizeof(buff));
if (1 == __super::Receive(&b, 1) && b && b * 16 == __super::Receive(buff, b * 16)) {
CStringA str = (LPCSTR)buff;
TRACE(_T("Metainfo: %s\n"), CString(str));
CStringA title("StreamTitle='"), url("StreamUrl='");
int i = str.Find(title);
if (i >= 0) {
i += title.GetLength();
int j = str.Find('\'', i);
if (j > i) {
m_title = str.Mid(i, j - i);
}
} else {
TRACE(_T("!!!!!!!!!Missing StreamTitle!!!!!!!!!\n"));
}
i = str.Find(url);
if (i >= 0) {
i += url.GetLength();
int j = str.Find('\'', i);
if (j > i) {
m_url = str.Mid(i, j - i);
}
}
}
} else if (m_metaint > 0) {
char* p = (char*)lpBuf;
char* p0 = p;
char* pend = p + len - 13;
for (; p < pend; p++) {
if (strncmp(p, "StreamTitle='", 13)) {
continue;
}
TRACE(_T("!!!!!!!!!StreamTitle found inside mp3 data!!!!!!!!! offset=%d\n"), p - p0);
TRACE(_T("resyncing...\n"));
while (p-- > p0) {
if ((BYTE)*p >= 0x20) {
continue;
}
TRACE(_T("found possible length byte: %d, skipping %d bytes\n"), *p, 1 + *p * 16);
p += 1 + *p * 16;
len = (int)(p0 + len - p);
TRACE(_T("returning the remaining bytes in the packet: %d\n"), len);
if (len <= 0) {
TRACE(_T("nothing to return, reading a bit more in\n"));
if (len < 0) {
__super::Receive(lpBuf, -len, nFlags);
}
int len = __super::Receive(lpBuf, nBufLen, nFlags);
if (len <= 0) {
return len;
}
}
m_nBytesRead = len;
memcpy(lpBuf, p, len);
break;
}
break;
}
}
return len;
}
bool CShoutcastStream::CShoutcastSocket::Connect(CUrl& url)
{
if (!__super::Connect(url.GetHostName(), url.GetPortNumber())) {
return false;
}
CStringA str;
str.Format(
"GET %s HTTP/1.0\r\n"
"Icy-MetaData:1\r\n"
"User-Agent: shoutcastsource\r\n"
"Host: %s\r\n"
"Accept: */*\r\n"
"Connection: Keep-Alive\r\n"
"\r\n", CStringA(url.GetUrlPath()), CStringA(url.GetHostName()));
bool fOK = false;
bool fTryAgain = false;
int metaint = 0;
do {
int len = Send((BYTE*)(LPCSTR)str, str.GetLength());
UNREFERENCED_PARAMETER(len);
m_nBytesRead = 0;
m_metaint = metaint = 0;
m_bitrate = 0;
str.Empty();
BYTE cur = 0, prev = 0;
while (Receive(&cur, 1) == 1 && cur && !(cur == '\n' && prev == '\n')) {
if (cur == '\r') {
continue;
}
if (cur == '\n') {
str.MakeLower();
if (str.Find("icy 200 ok") >= 0) {
fOK = true;
} else if (1 == sscanf_s(str, "icy-br:%d", &m_bitrate)) {
m_bitrate *= 1000;
} else if (1 == sscanf_s(str, "icy-metaint:%d", &metaint)) {
m_metaint = metaint;
}
str.Empty();
} else {
str += cur;
}
prev = cur;
cur = 0;
}
if (!fOK && GetLastError() == WSAECONNRESET && !fTryAgain) {
str.Format(
"GET %s HTTP/1.0\r\n"
"Icy-MetaData:1\r\n"
"Host: %s\r\n"
"Accept: */*\r\n"
"Connection: Keep-Alive\r\n"
"\r\n", CStringA(url.GetUrlPath()), CStringA(url.GetHostName()));
fTryAgain = true;
} else {
fTryAgain = false;
}
} while (fTryAgain);
if (!fOK || m_bitrate == 0) {
Close();
return false;
}
m_metaint = metaint;
m_nBytesRead = 0;
return FindSync();
}
bool CShoutcastStream::CShoutcastSocket::FindSync()
{
m_freq = (DWORD) - 1;
m_channels = (DWORD) - 1;
BYTE b;
for (int i = MAXFRAMESIZE; i > 0; i--, Receive(&b, 1)) {
mp3hdr h;
if (h.ExtractHeader(*this) && m_bitrate == h.bitrate) {
if (h.bitrate > 1) {
m_bitrate = h.bitrate;
}
m_freq = h.freq;
m_channels = h.channels;
return true;
}
}
return false;
}