/* * (C) 2003-2006 Gabest * (C) 2006-2015 see Authors.txt * * This file is part of MPC-HC. * * MPC-HC is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * MPC-HC is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "stdafx.h" #include "SubtitleInputPin.h" #include "VobSubFile.h" #include "RTS.h" #include "DVBSub.h" #include "PGSSub.h" #include #include #include "moreuuids.h" // our first format id #define __GAB1__ "GAB1" // our tags for __GAB1__ (ushort) + size (ushort) // "lang" + '0' #define __GAB1_LANGUAGE__ 0 // (int)start+(int)stop+(char*)line+'0' #define __GAB1_ENTRY__ 1 // L"lang" + '0' #define __GAB1_LANGUAGE_UNICODE__ 2 // (int)start+(int)stop+(WCHAR*)line+'0' #define __GAB1_ENTRY_UNICODE__ 3 // same as __GAB1__, but the size is (uint) and only __GAB1_LANGUAGE_UNICODE__ is valid #define __GAB2__ "GAB2" // (BYTE*) #define __GAB1_RAWTEXTSUBTITLE__ 4 CSubtitleInputPin::CSubtitleInputPin(CBaseFilter* pFilter, CCritSec* pLock, CCritSec* pSubLock, HRESULT* phr) : CBaseInputPin(NAME("CSubtitleInputPin"), pFilter, pLock, phr, L"Input") , m_pSubLock(pSubLock) , m_bExitDecodingThread(false) , m_bStopDecoding(false) { m_bCanReconnectWhenActive = true; m_decodeThread = std::thread([this]() { DecodeSamples(); }); } CSubtitleInputPin::~CSubtitleInputPin() { m_bExitDecodingThread = m_bStopDecoding = true; m_condQueueReady.notify_one(); if (m_decodeThread.joinable()) { m_decodeThread.join(); } } HRESULT CSubtitleInputPin::CheckMediaType(const CMediaType* pmt) { return pmt->majortype == MEDIATYPE_Text && (pmt->subtype == MEDIASUBTYPE_NULL || pmt->subtype == FOURCCMap((DWORD)0)) || pmt->majortype == MEDIATYPE_Subtitle && pmt->subtype == MEDIASUBTYPE_UTF8 || pmt->majortype == MEDIATYPE_Subtitle && (pmt->subtype == MEDIASUBTYPE_SSA || pmt->subtype == MEDIASUBTYPE_ASS || pmt->subtype == MEDIASUBTYPE_ASS2) || pmt->majortype == MEDIATYPE_Subtitle && (pmt->subtype == MEDIASUBTYPE_VOBSUB) || IsRLECodedSub(pmt) ? S_OK : E_FAIL; } HRESULT CSubtitleInputPin::CompleteConnect(IPin* pReceivePin) { InvalidateSamples(); if (m_mt.majortype == MEDIATYPE_Text) { if (!(m_pSubStream = DEBUG_NEW CRenderedTextSubtitle(m_pSubLock))) { return E_FAIL; } CRenderedTextSubtitle* pRTS = (CRenderedTextSubtitle*)(ISubStream*)m_pSubStream; pRTS->m_name = CString(GetPinName(pReceivePin)) + _T(" (embeded)"); pRTS->m_dstScreenSize = CSize(384, 288); pRTS->CreateDefaultStyle(DEFAULT_CHARSET); } else if (m_mt.majortype == MEDIATYPE_Subtitle) { SUBTITLEINFO* psi = (SUBTITLEINFO*)m_mt.pbFormat; DWORD dwOffset = 0; CString name; LCID lcid = 0; if (psi != nullptr) { dwOffset = psi->dwOffset; name = ISO6392ToLanguage(psi->IsoLang); lcid = ISO6392ToLcid(psi->IsoLang); CString trackName(psi->TrackName); trackName.Trim(); if (!trackName.IsEmpty()) { if (!name.IsEmpty()) { if (trackName[0] != _T('(') && trackName[0] != _T('[')) { name += _T(","); } name += _T(" "); } name += trackName; } if (name.IsEmpty()) { name = _T("Unknown"); } } name.Replace(_T(""), _T("")); name.Replace(_T(""), _T("")); if (m_mt.subtype == MEDIASUBTYPE_UTF8 /*|| m_mt.subtype == MEDIASUBTYPE_USF*/ || m_mt.subtype == MEDIASUBTYPE_SSA || m_mt.subtype == MEDIASUBTYPE_ASS || m_mt.subtype == MEDIASUBTYPE_ASS2) { if (!(m_pSubStream = DEBUG_NEW CRenderedTextSubtitle(m_pSubLock))) { return E_FAIL; } CRenderedTextSubtitle* pRTS = (CRenderedTextSubtitle*)(ISubStream*)m_pSubStream; pRTS->m_name = name; pRTS->m_lcid = lcid; pRTS->m_dstScreenSize = CSize(384, 288); pRTS->CreateDefaultStyle(DEFAULT_CHARSET); if (dwOffset > 0 && m_mt.cbFormat - dwOffset > 0) { CMediaType mt = m_mt; if (mt.pbFormat[dwOffset + 0] != 0xef && mt.pbFormat[dwOffset + 1] != 0xbb && mt.pbFormat[dwOffset + 2] != 0xfb) { dwOffset -= 3; mt.pbFormat[dwOffset + 0] = 0xef; mt.pbFormat[dwOffset + 1] = 0xbb; mt.pbFormat[dwOffset + 2] = 0xbf; } pRTS->Open(mt.pbFormat + dwOffset, mt.cbFormat - dwOffset, DEFAULT_CHARSET, pRTS->m_name); } } else if (m_mt.subtype == MEDIASUBTYPE_VOBSUB) { if (!(m_pSubStream = DEBUG_NEW CVobSubStream(m_pSubLock))) { return E_FAIL; } CVobSubStream* pVSS = (CVobSubStream*)(ISubStream*)m_pSubStream; pVSS->Open(name, m_mt.pbFormat + dwOffset, m_mt.cbFormat - dwOffset); } else if (IsRLECodedSub(&m_mt)) { if (m_mt.subtype == MEDIASUBTYPE_DVB_SUBTITLES) { m_pSubStream = DEBUG_NEW CDVBSub(m_pSubLock, name, lcid); } else { m_pSubStream = DEBUG_NEW CPGSSub(m_pSubLock, name, lcid); } if (!m_pSubStream) { return E_FAIL; } } } AddSubStream(m_pSubStream); return __super::CompleteConnect(pReceivePin); } HRESULT CSubtitleInputPin::BreakConnect() { InvalidateSamples(); RemoveSubStream(m_pSubStream); m_pSubStream = nullptr; ASSERT(IsStopped()); return __super::BreakConnect(); } STDMETHODIMP CSubtitleInputPin::ReceiveConnection(IPin* pConnector, const AM_MEDIA_TYPE* pmt) { if (m_Connected) { InvalidateSamples(); RemoveSubStream(m_pSubStream); m_pSubStream = nullptr; m_Connected->Release(); m_Connected = nullptr; } return __super::ReceiveConnection(pConnector, pmt); } STDMETHODIMP CSubtitleInputPin::NewSegment(REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate) { CAutoLock cAutoLock(&m_csReceive); InvalidateSamples(); if (m_mt.majortype == MEDIATYPE_Text || m_mt.majortype == MEDIATYPE_Subtitle && (m_mt.subtype == MEDIASUBTYPE_UTF8 /*|| m_mt.subtype == MEDIASUBTYPE_USF*/ || m_mt.subtype == MEDIASUBTYPE_SSA || m_mt.subtype == MEDIASUBTYPE_ASS || m_mt.subtype == MEDIASUBTYPE_ASS2)) { CAutoLock cAutoLock2(m_pSubLock); CRenderedTextSubtitle* pRTS = (CRenderedTextSubtitle*)(ISubStream*)m_pSubStream; pRTS->RemoveAll(); pRTS->CreateSegments(); } else if (m_mt.majortype == MEDIATYPE_Subtitle && (m_mt.subtype == MEDIASUBTYPE_VOBSUB)) { CAutoLock cAutoLock2(m_pSubLock); CVobSubStream* pVSS = (CVobSubStream*)(ISubStream*)m_pSubStream; pVSS->RemoveAll(); } else if (IsRLECodedSub(&m_mt)) { CAutoLock cAutoLock2(m_pSubLock); CRLECodedSubtitle* pRLECodedSubtitle = (CRLECodedSubtitle*)(ISubStream*)m_pSubStream; pRLECodedSubtitle->NewSegment(tStart, tStop, dRate); } TRACE(_T("NewSegment: InvalidateSubtitle(%I64d, ...)\n"), tStart); // IMPORTANT: m_pSubLock must not be locked when calling this InvalidateSubtitle(tStart, m_pSubStream); return __super::NewSegment(tStart, tStop, dRate); } STDMETHODIMP CSubtitleInputPin::Receive(IMediaSample* pSample) { HRESULT hr = __super::Receive(pSample); if (FAILED(hr)) { return hr; } CAutoLock cAutoLock(&m_csReceive); REFERENCE_TIME tStart, tStop; hr = pSample->GetTime(&tStart, &tStop); switch (hr) { case S_OK: tStart += m_tStart; tStop += m_tStart; break; case VFW_S_NO_STOP_TIME: tStart += m_tStart; tStop = INVALID_TIME; break; case VFW_E_SAMPLE_TIME_NOT_SET: tStart = tStop = INVALID_TIME; break; default: ASSERT(FALSE); return hr; } if ((tStart == INVALID_TIME || tStop == INVALID_TIME) && !IsRLECodedSub(&m_mt)) { ASSERT(FALSE); } else { BYTE* pData = nullptr; hr = pSample->GetPointer(&pData); long len = pSample->GetActualDataLength(); if (FAILED(hr) || pData == nullptr || len <= 0) { return hr; } { std::unique_lock lock(m_mutexQueue); m_sampleQueue.emplace_back(DEBUG_NEW SubtitleSample(tStart, tStop, pData, size_t(len))); lock.unlock(); m_condQueueReady.notify_one(); } } return S_OK; } STDMETHODIMP CSubtitleInputPin::EndOfStream(void) { HRESULT hr = __super::EndOfStream(); if (SUCCEEDED(hr)) { std::unique_lock lock(m_mutexQueue); m_sampleQueue.emplace_back(nullptr); // nullptr means end of stream lock.unlock(); m_condQueueReady.notify_one(); } return hr; } bool CSubtitleInputPin::IsRLECodedSub(const CMediaType* pmt) const { return !!(pmt->majortype == MEDIATYPE_Subtitle && (pmt->subtype == MEDIASUBTYPE_HDMVSUB // Blu-Ray presentation graphics || pmt->subtype == MEDIASUBTYPE_DVB_SUBTITLES // DVB subtitles || (pmt->subtype == MEDIASUBTYPE_NULL && pmt->formattype == FORMAT_SubtitleInfo))); // Workaround : support for Haali PGS } void CSubtitleInputPin::DecodeSamples() { SetThreadName(DWORD(-1), "Subtitle Input Pin Thread"); for (; !m_bExitDecodingThread;) { std::unique_lock lock(m_mutexQueue); auto needStopProcessing = [this]() { return m_bStopDecoding || m_bExitDecodingThread; }; auto isQueueReady = [&]() { return !m_sampleQueue.empty() || needStopProcessing(); }; m_condQueueReady.wait(lock, isQueueReady); lock.unlock(); // Release this lock until we can acquire the other one REFERENCE_TIME rtInvalidate = -1; if (!needStopProcessing()) { CAutoLock cAutoLock(m_pSubLock); lock.lock(); // Reacquire the lock while (!m_sampleQueue.empty() && !needStopProcessing()) { const auto& pSample = m_sampleQueue.front(); if (pSample) { REFERENCE_TIME rtSampleInvalidate = DecodeSample(pSample); if (rtSampleInvalidate >= 0 && (rtSampleInvalidate < rtInvalidate || rtInvalidate < 0)) { rtInvalidate = rtSampleInvalidate; } } else { // marker for end of stream if (IsRLECodedSub(&m_mt)) { CRLECodedSubtitle* pRLECodedSubtitle = (CRLECodedSubtitle*)(ISubStream*)m_pSubStream; pRLECodedSubtitle->EndOfStream(); } } m_sampleQueue.pop_front(); } } if (rtInvalidate >= 0) { TRACE(_T("NewSegment: InvalidateSubtitle(%I64d, ...)\n"), rtInvalidate); // IMPORTANT: m_pSubLock must not be locked when calling this InvalidateSubtitle(rtInvalidate, m_pSubStream); } } } REFERENCE_TIME CSubtitleInputPin::DecodeSample(const std::unique_ptr& pSample) { bool bInvalidate = false; if (m_mt.majortype == MEDIATYPE_Text) { CRenderedTextSubtitle* pRTS = (CRenderedTextSubtitle*)(ISubStream*)m_pSubStream; char* pData = (char*)pSample->data.data(); if (!strncmp(pData, __GAB1__, strlen(__GAB1__))) { char* ptr = &pData[strlen(__GAB1__) + 1]; char* end = &pData[pSample->data.size()]; while (ptr < end) { WORD tag = *((WORD*)(ptr)); ptr += 2; WORD size = *((WORD*)(ptr)); ptr += 2; if (tag == __GAB1_LANGUAGE__) { pRTS->m_name = CString(ptr); } else if (tag == __GAB1_ENTRY__) { pRTS->Add(AToW(&ptr[8]), false, *(int*)ptr, *(int*)(ptr + 4)); bInvalidate = true; } else if (tag == __GAB1_LANGUAGE_UNICODE__) { pRTS->m_name = (WCHAR*)ptr; } else if (tag == __GAB1_ENTRY_UNICODE__) { pRTS->Add((WCHAR*)(ptr + 8), true, *(int*)ptr, *(int*)(ptr + 4)); bInvalidate = true; } ptr += size; } } else if (!strncmp(pData, __GAB2__, strlen(__GAB2__))) { char* ptr = &pData[strlen(__GAB2__) + 1]; char* end = &pData[pSample->data.size()]; while (ptr < end) { WORD tag = *((WORD*)(ptr)); ptr += 2; DWORD size = *((DWORD*)(ptr)); ptr += 4; if (tag == __GAB1_LANGUAGE_UNICODE__) { pRTS->m_name = (WCHAR*)ptr; } else if (tag == __GAB1_RAWTEXTSUBTITLE__) { pRTS->Open((BYTE*)ptr, size, DEFAULT_CHARSET, pRTS->m_name); bInvalidate = true; } ptr += size; } } else if (pSample->data.size() > 1 && *pData != '\0') { CStringA str(pData, (int)pSample->data.size()); str.Replace("\r\n", "\n"); str.Trim(); if (!str.IsEmpty()) { pRTS->Add(AToW(str), false, (int)(pSample->rtStart / 10000), (int)(pSample->rtStop / 10000)); bInvalidate = true; } } } else if (m_mt.majortype == MEDIATYPE_Subtitle) { if (m_mt.subtype == MEDIASUBTYPE_UTF8) { CRenderedTextSubtitle* pRTS = (CRenderedTextSubtitle*)(ISubStream*)m_pSubStream; CStringW str = UTF8To16(CStringA((LPCSTR)pSample->data.data(), (int)pSample->data.size())).Trim(); if (!str.IsEmpty()) { pRTS->Add(str, true, (int)(pSample->rtStart / 10000), (int)(pSample->rtStop / 10000)); bInvalidate = true; } } else if (m_mt.subtype == MEDIASUBTYPE_SSA || m_mt.subtype == MEDIASUBTYPE_ASS || m_mt.subtype == MEDIASUBTYPE_ASS2) { CRenderedTextSubtitle* pRTS = (CRenderedTextSubtitle*)(ISubStream*)m_pSubStream; CStringW str = UTF8To16(CStringA((LPCSTR)pSample->data.data(), (int)pSample->data.size())).Trim(); if (!str.IsEmpty()) { STSEntry stse; int fields = m_mt.subtype == MEDIASUBTYPE_ASS2 ? 10 : 9; CAtlList sl; Explode(str, sl, ',', fields); if (sl.GetCount() == (size_t)fields) { stse.readorder = wcstol(sl.RemoveHead(), nullptr, 10); stse.layer = wcstol(sl.RemoveHead(), nullptr, 10); stse.style = sl.RemoveHead(); stse.actor = sl.RemoveHead(); stse.marginRect.left = wcstol(sl.RemoveHead(), nullptr, 10); stse.marginRect.right = wcstol(sl.RemoveHead(), nullptr, 10); stse.marginRect.top = stse.marginRect.bottom = wcstol(sl.RemoveHead(), nullptr, 10); if (fields == 10) { stse.marginRect.bottom = wcstol(sl.RemoveHead(), nullptr, 10); } stse.effect = sl.RemoveHead(); stse.str = sl.RemoveHead(); } if (!stse.str.IsEmpty()) { pRTS->Add(stse.str, true, (int)(pSample->rtStart / 10000), (int)(pSample->rtStop / 10000), stse.style, stse.actor, stse.effect, stse.marginRect, stse.layer, stse.readorder); bInvalidate = true; } } } else if (m_mt.subtype == MEDIASUBTYPE_VOBSUB) { CVobSubStream* pVSS = (CVobSubStream*)(ISubStream*)m_pSubStream; pVSS->Add(pSample->rtStart, pSample->rtStop, pSample->data.data(), (int)pSample->data.size()); } else if (IsRLECodedSub(&m_mt)) { CRLECodedSubtitle* pRLECodedSubtitle = (CRLECodedSubtitle*)(ISubStream*)m_pSubStream; pRLECodedSubtitle->ParseSample(pSample->rtStart, pSample->rtStop, pSample->data.data(), (int)pSample->data.size()); } } return bInvalidate ? pSample->rtStart : -1; } void CSubtitleInputPin::InvalidateSamples() { m_bStopDecoding = true; { std::lock_guard lock(m_mutexQueue); m_sampleQueue.clear(); m_bStopDecoding = false; } }