/* * (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; }