//----------------------------------------------------------------------------- // // Musepack Demuxer // // Author : Igor Janos // //----------------------------------------------------------------------------- #include "stdafx.h" void MakeNiceSpeed(__int64 bps, CString &v) { int r=0; __int64 c=bps; LPCTSTR rady[] = { _T("bps"), _T("kbps"), _T("mbps"), _T("gbps"), _T("tbps") }; // spocitame rad while (c > 1000 && r<4) { r++; c = c / 1000; } c=bps; for (int i=1; iNonDelegatingQueryInterface(IID_IAMMediaContent, (void**)&content); if (FAILED(hr)) { delete amc; content = NULL; } input = new CMPCInputPin(NAME("MPC Input Pin"), this, phr, L"In"); output.RemoveAll(); retired.RemoveAll(); ev_abort.Reset(); } CMPCDemux::~CMPCDemux() { // just to be sure if (reader) { delete reader; reader = NULL; } for (int i=0; iRelease(); } STDMETHODIMP CMPCDemux::NonDelegatingQueryInterface(REFIID riid,void **ppv) { CheckPointer(ppv,E_POINTER); if (riid == IID_ISpecifyPropertyPages) { return GetInterface((ISpecifyPropertyPages*)this, ppv); } else if (riid == IID_IMusepackSplitter) { return GetInterface((IMusepackSplitter*)this, ppv); } else if (riid == IID_IMediaSeeking) { return GetInterface((IMediaSeeking*)this, ppv); } else if (riid == IID_IAMStreamSelect) { return GetInterface((IAMStreamSelect*)this, ppv); } else if (riid == IID_IAMMediaContent) { if (content) { return content->QueryInterface(riid, ppv); } else { return E_NOINTERFACE; } } else { return CBaseFilter::NonDelegatingQueryInterface(riid,ppv); } } STDMETHODIMP CMPCDemux::SetPropertyPageWindow(HWND wnd) { CAutoLock lck(&lock_filter); wnd_prop = wnd; return NOERROR; } STDMETHODIMP CMPCDemux::GetPages(CAUUID *pPages) { CheckPointer(pPages,E_POINTER); pPages->cElems = 1; pPages->pElems = (GUID *) CoTaskMemAlloc(sizeof(GUID)); if (pPages->pElems == NULL) { return E_OUTOFMEMORY; } *(pPages->pElems) = CLSID_MusepackDemuxPage; return NOERROR; } // GetPages int CMPCDemux::GetPinCount() { // return pin count CAutoLock Lock(&lock_filter); return ((input ? 1 : 0) + output.GetCount()); } CBasePin *CMPCDemux::GetPin(int n) { CAutoLock Lock(&lock_filter); if (n == 0) return input; n -= 1; int l = output.GetCount(); // return the requested output pin if (n >= l) return NULL; return output[n]; } HRESULT CMPCDemux::CheckConnect(PIN_DIRECTION Dir, IPin *pPin) { return NOERROR; } HRESULT CMPCDemux::CheckInputType(const CMediaType* mtIn) { if (mtIn->majortype == MEDIATYPE_Stream) { // we are sure we can accept this type if (mtIn->subtype == MEDIASUBTYPE_MusepackStream) return NOERROR; // and we may accept unknown type as well if (mtIn->subtype == MEDIASUBTYPE_None || mtIn->subtype == MEDIASUBTYPE_NULL || mtIn->subtype == GUID_NULL ) return NOERROR; } else if (mtIn->majortype == GUID_NULL) { return NOERROR; } // sorry.. nothing else return E_FAIL; } HRESULT CMPCDemux::CompleteConnect(PIN_DIRECTION Dir, CBasePin *pCaller, IPin *pReceivePin) { if (Dir == PINDIR_INPUT) { // when our input pin gets connected we have to scan // the input file if it is really musepack. ASSERT(input && input->Reader()); ASSERT(!reader); ASSERT(!file); //--------------------------------------------------------------------- // // Analyse the source file // //--------------------------------------------------------------------- CAutoLock lck(&lock_filter); reader = new CMPCReader(input->Reader()); file = new CMPCFile(); // try to open the file int ret = file->Open(reader); if (ret < 0) { delete file; file = NULL; delete reader; reader = NULL; return E_FAIL; } HRESULT hr = NOERROR; CMPCOutputPin *opin = new CMPCOutputPin(_T("Outpin"), this, &hr, L"Out", 5); ConfigureMediaType(opin); AddOutputPin(opin); // refresh property page if there is any if (IsWindow(wnd_prop)) { PostMessage(wnd_prop, WM_UPDATE_VISUALS, 0, 0); } } else { } return NOERROR; } HRESULT CMPCDemux::RemoveOutputPins() { CAutoLock Lck(&lock_filter); if (m_State != State_Stopped) return VFW_E_NOT_STOPPED; // we retire all current output pins for (int i=0; iIsConnected()) { pin->GetConnected()->Disconnect(); pin->Disconnect(); } retired.Add(pin); } output.RemoveAll(); return NOERROR; } HRESULT CMPCDemux::ConfigureMediaType(CMPCOutputPin *pin) { CMediaType mt; mt.majortype = MEDIATYPE_Audio; mt.subtype = MEDIASUBTYPE_MusepackPacket; mt.formattype = FORMAT_WaveFormatEx; mt.lSampleSize = 128*1024; // should be way enough ASSERT(file); int extrasize = file->extradata_size; // let us fill the waveformatex structure WAVEFORMATEX *wfx = (WAVEFORMATEX*)mt.AllocFormatBuffer(sizeof(WAVEFORMATEX) + extrasize); memset(wfx, 0, sizeof(*wfx)); wfx->cbSize = extrasize; wfx->wBitsPerSample = 0; wfx->nChannels = file->channels; wfx->nSamplesPerSec = file->sample_rate; wfx->nBlockAlign = 1; wfx->nAvgBytesPerSec = 0; wfx->wFormatTag = 0; // Extradata - Stream header + other stuff uint8 *extra = ((uint8*)wfx) + sizeof(WAVEFORMATEX); memcpy(extra, file->extradata, extrasize); // the one and only type pin->mt_types.Add(mt); return NOERROR; } HRESULT CMPCDemux::BreakConnect(PIN_DIRECTION Dir, CBasePin *pCaller) { ASSERT(m_State == State_Stopped); if (Dir == PINDIR_INPUT) { // let's disconnect the output pins ev_abort.Set(); //ev_ready.Set(); HRESULT hr = RemoveOutputPins(); if (FAILED(hr)) return hr; // destroy input file, reader and update property page if (file) { delete file; file = NULL; } if (reader) { delete reader; reader = NULL; } if (IsWindow(wnd_prop)) { PostMessage(wnd_prop, WM_UPDATE_VISUALS, 0, 0); } ev_abort.Reset(); } else if (Dir == PINDIR_OUTPUT) { // nothing yet } return NOERROR; } // Output pins HRESULT CMPCDemux::AddOutputPin(CMPCOutputPin *pPin) { CAutoLock lck(&lock_filter); output.Add(pPin); return NOERROR; } STDMETHODIMP CMPCDemux::GetFileInfo(MPC_File_Info *info) { CAutoLock lck(&lock_filter); if (!info) return E_POINTER; if (!file) return E_FAIL; // fill in the struct info->duration = file->duration_10mhz / 10000000.0; info->stream_version = file->stream_version; info->channels = file->channels; info->sample_rate = file->sample_rate; info->block_frames = file->audio_block_frames; info->gain_album_db = file->gain_album_db; info->gain_album_peak_db = file->gain_album_peak_db; info->gain_title_db = file->gain_title_db; info->gain_title_peak_db = file->gain_title_peak_db; return NOERROR; } // IMediaSeeking STDMETHODIMP CMPCDemux::GetCapabilities(DWORD* pCapabilities) { return pCapabilities ? *pCapabilities = AM_SEEKING_CanGetStopPos|AM_SEEKING_CanGetDuration|AM_SEEKING_CanSeekAbsolute|AM_SEEKING_CanSeekForwards|AM_SEEKING_CanSeekBackwards, S_OK : E_POINTER; } STDMETHODIMP CMPCDemux::CheckCapabilities(DWORD* pCapabilities) { CheckPointer(pCapabilities, E_POINTER); if (*pCapabilities == 0) return S_OK; DWORD caps; GetCapabilities(&caps); if ((caps&*pCapabilities) == 0) return E_FAIL; if (caps == *pCapabilities) return S_OK; return S_FALSE; } STDMETHODIMP CMPCDemux::IsFormatSupported(const GUID* pFormat) {return !pFormat ? E_POINTER : *pFormat == TIME_FORMAT_MEDIA_TIME ? S_OK : S_FALSE;} STDMETHODIMP CMPCDemux::QueryPreferredFormat(GUID* pFormat) {return GetTimeFormat(pFormat);} STDMETHODIMP CMPCDemux::GetTimeFormat(GUID* pFormat) {return pFormat ? *pFormat = TIME_FORMAT_MEDIA_TIME, S_OK : E_POINTER;} STDMETHODIMP CMPCDemux::IsUsingTimeFormat(const GUID* pFormat) {return IsFormatSupported(pFormat);} STDMETHODIMP CMPCDemux::SetTimeFormat(const GUID* pFormat) {return S_OK == IsFormatSupported(pFormat) ? S_OK : E_INVALIDARG;} STDMETHODIMP CMPCDemux::GetStopPosition(LONGLONG* pStop) {return this->rtStop; } STDMETHODIMP CMPCDemux::GetCurrentPosition(LONGLONG* pCurrent) {return E_NOTIMPL;} STDMETHODIMP CMPCDemux::ConvertTimeFormat(LONGLONG* pTarget, const GUID* pTargetFormat, LONGLONG Source, const GUID* pSourceFormat) {return E_NOTIMPL;} STDMETHODIMP CMPCDemux::SetPositions(LONGLONG* pCurrent, DWORD dwCurrentFlags, LONGLONG* pStop, DWORD dwStopFlags) { return SetPositionsInternal(0, pCurrent, dwCurrentFlags, pStop, dwStopFlags); } STDMETHODIMP CMPCDemux::GetPositions(LONGLONG* pCurrent, LONGLONG* pStop) { if(pCurrent) *pCurrent = rtCurrent; if(pStop) *pStop = rtStop; return S_OK; } STDMETHODIMP CMPCDemux::GetAvailable(LONGLONG* pEarliest, LONGLONG* pLatest) { if(pEarliest) *pEarliest = 0; return GetDuration(pLatest); } STDMETHODIMP CMPCDemux::SetRate(double dRate) {return dRate > 0 ? rate = dRate, S_OK : E_INVALIDARG;} STDMETHODIMP CMPCDemux::GetRate(double* pdRate) {return pdRate ? *pdRate = rate, S_OK : E_POINTER;} STDMETHODIMP CMPCDemux::GetPreroll(LONGLONG* pllPreroll) {return pllPreroll ? *pllPreroll = 0, S_OK : E_POINTER;} STDMETHODIMP CMPCDemux::GetDuration(LONGLONG* pDuration) { CheckPointer(pDuration, E_POINTER); *pDuration = 0; if (file) { if (pDuration) *pDuration = file->duration_10mhz; } return S_OK; } STDMETHODIMP CMPCDemux::SetPositionsInternal(int iD, LONGLONG* pCurrent, DWORD dwCurrentFlags, LONGLONG* pStop, DWORD dwStopFlags) { // only our first pin can seek if (iD != 0) return NOERROR; CAutoLock cAutoLock(&lock_filter); if (!pCurrent && !pStop || (dwCurrentFlags&AM_SEEKING_PositioningBitsMask) == AM_SEEKING_NoPositioning && (dwStopFlags&AM_SEEKING_PositioningBitsMask) == AM_SEEKING_NoPositioning) return S_OK; REFERENCE_TIME rtCurrent = this->rtCurrent, rtStop = this->rtStop; if (pCurrent) { switch(dwCurrentFlags&AM_SEEKING_PositioningBitsMask) { case AM_SEEKING_NoPositioning: break; case AM_SEEKING_AbsolutePositioning: rtCurrent = *pCurrent; break; case AM_SEEKING_RelativePositioning: rtCurrent = rtCurrent + *pCurrent; break; case AM_SEEKING_IncrementalPositioning: rtCurrent = rtCurrent + *pCurrent; break; } } if (pStop) { switch(dwStopFlags&AM_SEEKING_PositioningBitsMask) { case AM_SEEKING_NoPositioning: break; case AM_SEEKING_AbsolutePositioning: rtStop = *pStop; break; case AM_SEEKING_RelativePositioning: rtStop += *pStop; break; case AM_SEEKING_IncrementalPositioning: rtStop = rtCurrent + *pStop; break; } } if (this->rtCurrent == rtCurrent && this->rtStop == rtStop) { //return S_OK; } this->rtCurrent = rtCurrent; this->rtStop = rtStop; // now there are new valid Current and Stop positions HRESULT hr = DoNewSeek(); return hr; } STDMETHODIMP CMPCDemux::Pause() { CAutoLock lck(&lock_filter); if (m_State == State_Stopped) { ev_abort.Reset(); // activate pins for (int i=0; iActive(); if (input) input->Active(); // seekneme na danu poziciu DoNewSeek(); // pustime parser thread if (!ThreadExists()) { Create(); CallWorker(CMD_RUN); } } m_State = State_Paused; return NOERROR; } STDMETHODIMP CMPCDemux::Stop() { CAutoLock lock(&lock_filter); HRESULT hr = NOERROR; if (m_State != State_Stopped) { // set abort ev_abort.Set(); if (reader) reader->BeginFlush(); // deaktivujeme piny if (input) input->Inactive(); for (int i=0; iInactive(); // zrusime parser thread if (ThreadExists()) { CallWorker(CMD_EXIT); Close(); } if (reader) reader->EndFlush(); ev_abort.Reset(); } m_State = State_Stopped; return hr; } HRESULT CMPCDemux::DoNewSeek() { CMPCOutputPin *pin = output[0]; HRESULT hr; if (!pin->IsConnected()) return NOERROR; // stop first ev_abort.Set(); if (reader) reader->BeginFlush(); FILTER_STATE state = m_State; // abort if (state != State_Stopped) { if (pin->ThreadExists()) { pin->ev_abort.Set(); hr = pin->DeliverBeginFlush(); if (FAILED(hr)) { ASSERT(FALSE); } if (ThreadExists()) { CallWorker(CMD_STOP); } pin->CallWorker(CMD_STOP); hr = pin->DeliverEndFlush(); if (FAILED(hr)) { ASSERT(FALSE); } pin->FlushQueue(); } } pin->DoNewSegment(rtCurrent, rtStop, rate); if (reader) reader->EndFlush(); // seek the file if (file) { int64 sample_pos = (rtCurrent * file->sample_rate) / 10000000; file->Seek(sample_pos); } ev_abort.Reset(); if (state != State_Stopped) { // spustime aj jeho thread pin->FlushQueue(); pin->ev_abort.Reset(); if (pin->ThreadExists()) { pin->CallWorker(CMD_RUN); } if (ThreadExists()) { CallWorker(CMD_RUN); } } return NOERROR; } DWORD CMPCDemux::ThreadProc() { DWORD cmd, cmd2; while (true) { cmd = GetRequest(); switch (cmd) { case CMD_EXIT: Reply(NOERROR); return 0; case CMD_STOP: { Reply(NOERROR); } break; case CMD_RUN: { Reply(NOERROR); if (!file) break; CMPCPacket packet; int32 ret=0; bool eos=false; HRESULT hr; int64 current_sample; /* With a more complex demultiplexer we would need a mechanism to identify streams. Now we have only one output stream so it's easy. */ if (output.GetCount() <= 0) break; if (output[0]->IsConnected() == FALSE) break; int delivered = 0; do { // are we supposed to abort ? if (ev_abort.Check()) { break; } ret = file->ReadAudioPacket(&packet, ¤t_sample); if (ret == -2) { // end of stream if (!ev_abort.Check()) { output[0]->DoEndOfStream(); } break; } else if (ret < 0) { break; } else { // compute time stamp REFERENCE_TIME tStart = (current_sample * 10000000) / file->sample_rate; REFERENCE_TIME tStop = ((current_sample + 1152*file->audio_block_frames) * 10000000) / file->sample_rate; packet.tStart = tStart - rtCurrent; packet.tStop = tStop - rtCurrent; // deliver packet hr = output[0]->DeliverPacket(packet); if (FAILED(hr)) { break; } delivered++; } } while (!CheckRequest(&cmd2)); } break; default: Reply(E_UNEXPECTED); break; } } return 0; } int CMPCDemux::GetContentString(CString key, CString &value) { CAutoLock lck(&lock_filter); if (!file) return -1; if (key == _T("author")) { value = file->tag.FindName(_T("artist")); } else if (key == _T("title")) { value = file->tag.FindName(_T("title")); } else if (key == _T("description")) { value = file->tag.FindName(_T("album")); CString year = file->tag.FindName(_T("year")); if (year != _T("")) { CString tmp; tmp.Format(_T("(%s) %s"), year, value); value = tmp; } } return (value == _T("") ? -1 : 0); } // IAMStreamSelect (chapters hack) STDMETHODIMP CMPCDemux::Count(DWORD *pcStreams) { if (!pcStreams) return E_POINTER; CAutoLock lck(&lock_filter); if (!file) { *pcStreams = 0; return NOERROR; } // pocet chaptersov *pcStreams = file->chapters.size(); return 0; } STDMETHODIMP CMPCDemux::Info(long lIndex, AM_MEDIA_TYPE **ppmt, DWORD *pdwFlags, LCID *plcid, DWORD *pdwGroup, LPWSTR *ppszName, IUnknown **ppObject, IUnknown **ppUnk) { CAutoLock lck(&lock_filter); // correct range ? if (lIndex < 0) return E_INVALIDARG; if (!file) return S_FALSE; if (lIndex >= file->chapters.size()) return S_FALSE; CMPCChapter *chap = file->chapters[lIndex]; if (!chap) return S_FALSE; // all chapters are in the same group if (pdwFlags) pdwFlags = 0; //AMSTREAMSELECTINFO_EXCLUSIVE; if (plcid) *plcid = 0; if (pdwGroup) *pdwGroup = 0; if (ppszName) { int len = chap->caption.GetLength() * sizeof(WCHAR); LPWSTR str = (LPWSTR)CoTaskMemAlloc(len+ sizeof(WCHAR)); if (!str) return E_OUTOFMEMORY; // copy the string memset(str, 0, len+sizeof(WCHAR)); memcpy(str, chap->caption.GetBuffer(), len); *ppszName = str; } // ignore these if (ppmt) *ppmt = NULL; if (ppObject) *ppObject = NULL; if (ppUnk) *ppUnk = NULL; return NOERROR; } STDMETHODIMP CMPCDemux::Enable(long lIndex, DWORD dwFlags) { /* we fake the stream selection by seeking to the desired position. */ REFERENCE_TIME seek_time = 0; CComPtr ms; { CAutoLock lck(&lock_filter); if (!m_pGraph) return NOERROR; HRESULT hr = m_pGraph->QueryInterface(IID_IMediaSeeking, (void**)&ms); if (FAILED(hr)) return E_UNEXPECTED; // correct range ? if (lIndex < 0) return E_INVALIDARG; if (!file) return S_FALSE; if (lIndex >= file->chapters.size()) return S_FALSE; CMPCChapter *chap = file->chapters[lIndex]; if (!chap) return S_FALSE; // this is the time seek_time = llMulDiv(chap->sample_offset, 10000000, file->sample_rate, 0); } // now let's do the seeking ASSERT(ms); // make the seek ms->SetPositions(&seek_time, AM_SEEKING_AbsolutePositioning, NULL, AM_SEEKING_NoPositioning); ms = NULL; return NOERROR; }