| // Copyright (c) 2010 The WebM project authors. All Rights Reserved. |
| // |
| // Use of this source code is governed by a BSD-style license |
| // that can be found in the LICENSE file in the root of the source |
| // tree. An additional intellectual property rights grant can be found |
| // in the file PATENTS. All contributing project authors may |
| // be found in the AUTHORS file in the root of the source tree. |
| |
| #include "playwebmapp.h" |
| #include <cassert> |
| #include <iostream> |
| #include <iomanip> |
| #include <uuids.h> |
| #include <evcode.h> |
| #include "hrtext.h" |
| #include "mediatypeutil.h" |
| #include <string> |
| #include <sstream> |
| using std::hex; |
| using std::dec; |
| using std::wcout; |
| using std::endl; |
| using std::wstring; |
| using GraphUtil::IPinPtr; |
| using GraphUtil::IBaseFilterPtr; |
| // using GraphUtil::IFileSourceFilterPtr; |
| using GraphUtil::FindOutpinVideo; |
| using GraphUtil::FindOutpinAudio; |
| using GraphUtil::FindInpinVideo; |
| using GraphUtil::FindInpinAudio; |
| |
| App::App(HANDLE hQuit) : m_hQuit(hQuit) { |
| assert(m_hQuit); |
| } |
| |
| int App::operator()(int argc, wchar_t* argv[]) { |
| int status = m_cmdline.Parse(argc, argv); |
| |
| if (status) |
| return status; |
| |
| status = BuildGraph(); |
| |
| if (status) |
| return status; |
| |
| IRunningObjectTablePtr rot; |
| |
| HRESULT hr = GetRunningObjectTable(0, &rot); |
| assert(SUCCEEDED(hr)); |
| assert(bool(rot)); |
| |
| IFilterGraph* const pGraph = m_pGraph; |
| |
| std::wostringstream os; |
| os << "FilterGraph " << std::hex << static_cast<const void*>(pGraph) |
| << " pid " << GetCurrentProcessId(); |
| |
| IMonikerPtr mon; |
| |
| hr = CreateItemMoniker(L"!", os.str().c_str(), &mon); |
| assert(SUCCEEDED(hr)); |
| assert(bool(mon)); |
| |
| DWORD dw; |
| hr = rot->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, pGraph, mon, &dw); |
| assert(SUCCEEDED(hr)); |
| |
| status = RunGraph(); |
| |
| hr = rot->Revoke(dw); |
| assert(SUCCEEDED(hr)); |
| |
| DestroyGraph(); |
| |
| return status; |
| } |
| |
| int App::BuildGraph() { |
| const bool bList = m_cmdline.GetList(); |
| // const bool bVerbose = m_cmdline.GetVerbose(); |
| |
| HRESULT hr = m_pGraph.CreateInstance(CLSID_FilterGraphNoThread); |
| assert(SUCCEEDED(hr)); |
| assert(bool(m_pGraph)); |
| |
| const GraphUtil::IGraphBuilderPtr pBuilder(m_pGraph); |
| assert(bool(pBuilder)); |
| |
| #if 0 // console output in unicode, but console in ascii?? |
| const HANDLE hFile = CreateFile( |
| L"CONOUT$", //see also GetStdHandle |
| GENERIC_READ | GENERIC_WRITE, |
| FILE_SHARE_WRITE, |
| 0, //security |
| OPEN_EXISTING, |
| 0, //ignored |
| 0); //ignored |
| #elif 0 |
| const HANDLE hFile = CreateFile(L"rendermkv_logfile.txt", GENERIC_WRITE, |
| 0, // no sharing |
| 0, // security |
| CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); |
| |
| assert(hFile != INVALID_HANDLE_VALUE); |
| assert(hFile != 0); |
| |
| hr = pBuilder->SetLogFile(DWORD_PTR(hFile)); |
| assert(SUCCEEDED(hr)); |
| #endif |
| |
| if (const CLSID* pclsid = m_cmdline.GetSource()) { |
| IBaseFilterPtr pSource; |
| |
| hr = pSource.CreateInstance(*pclsid); |
| |
| if (FAILED(hr)) { |
| wcout << "Unable to create WebmSource filter instance.\n" << hrtext(hr) |
| << L" (0x" << hex << hr << dec << L")" << endl; |
| |
| return 1; |
| } |
| |
| assert(bool(pSource)); |
| |
| hr = m_pGraph->AddFilter(pSource, L"source"); |
| assert(SUCCEEDED(hr)); |
| |
| const GraphUtil::IFileSourceFilterPtr pFileSource(pSource); |
| assert(bool(pFileSource)); |
| |
| hr = pFileSource->Load(m_cmdline.GetInputFileName(), 0); |
| |
| if (FAILED(hr)) { |
| wcout << "Source filter is unable to load input file \"" |
| << m_cmdline.GetInputFileName() << "\".\n" << hrtext(hr) << L" (0x" |
| << hex << hr << dec << L")" << endl; |
| |
| return 1; |
| } |
| |
| if (GraphUtil::OutpinCount(pSource) == 0) { |
| wcout << "Source filter does not advertise any output pins." << endl; |
| return 1; |
| } |
| |
| GraphUtil::IEnumPinsPtr e; |
| |
| hr = pSource->EnumPins(&e); |
| assert(SUCCEEDED(hr)); |
| |
| int n = 0; |
| |
| for (;;) { |
| IPinPtr pin; |
| |
| hr = e->Next(1, &pin, 0); |
| |
| if (hr != S_OK) |
| break; |
| |
| hr = pBuilder->Render(pin); |
| |
| if (SUCCEEDED(hr)) // TODO: check for partial success |
| ++n; |
| else |
| RenderFailed(pin, hr); |
| } |
| |
| if (n <= 0) { |
| wcout << "No source filter output pins rendered successfully." << endl; |
| return 1; |
| } |
| } else if (const CLSID* pclsid = m_cmdline.GetSplitter()) { |
| // TODO: parameterize this explicitly: asyncreader vs. url source |
| const IBaseFilterPtr pReader(CLSID_AsyncReader); |
| assert(bool(pReader)); |
| |
| hr = m_pGraph->AddFilter(pReader, L"reader"); |
| assert(SUCCEEDED(hr)); |
| |
| const GraphUtil::IFileSourceFilterPtr pFileSource(pReader); |
| assert(bool(pFileSource)); |
| |
| hr = pFileSource->Load(m_cmdline.GetInputFileName(), 0); |
| |
| if (FAILED(hr)) { |
| wcout << "AsyncReader filter is unable to load input file \"" |
| << m_cmdline.GetInputFileName() << "\".\n" << hrtext(hr) << L" (0x" |
| << hex << hr << dec << L")" << endl; |
| |
| return 1; |
| } |
| |
| assert(GraphUtil::OutpinCount(pReader) == 1); |
| |
| IBaseFilterPtr pSplitter; |
| |
| hr = pSplitter.CreateInstance(*pclsid); |
| |
| if (FAILED(hr)) { |
| wcout << "Unable to create WebmSplit filter instance.\n" << hrtext(hr) |
| << L" (0x" << hex << hr << dec << L")" << endl; |
| |
| return 1; |
| } |
| |
| hr = m_pGraph->AddFilter(pSplitter, L"splitter"); |
| assert(SUCCEEDED(hr)); |
| |
| hr = GraphUtil::ConnectDirect(m_pGraph, pReader, pSplitter); |
| |
| if (FAILED(hr)) { |
| wcout << "Unable to create connect reader filter to splitter.\n" |
| << hrtext(hr) << L" (0x" << hex << hr << dec << L")" << endl; |
| |
| return 1; |
| } |
| |
| if (GraphUtil::OutpinCount(pSplitter) == 0) { |
| wcout << "Splitter filter does not advertise any output pins." << endl; |
| return 1; |
| } |
| |
| GraphUtil::IEnumPinsPtr e; |
| |
| hr = pSplitter->EnumPins(&e); |
| assert(SUCCEEDED(hr)); |
| |
| int n = 0; |
| |
| for (;;) { |
| IPinPtr pin; |
| |
| hr = e->Next(1, &pin, 0); |
| |
| if (hr != S_OK) |
| break; |
| |
| PIN_DIRECTION dir; |
| |
| hr = pin->QueryDirection(&dir); |
| assert(SUCCEEDED(hr)); // TODO |
| |
| if (dir != PINDIR_OUTPUT) |
| continue; |
| |
| hr = pBuilder->Render(pin); |
| |
| if (SUCCEEDED(hr)) // TODO: check for partial success |
| ++n; |
| else |
| RenderFailed(pin, hr); |
| } |
| |
| if (n <= 0) { |
| wcout << "No splitter filter output pins rendered successfully." << endl; |
| return 1; |
| } |
| } else { |
| hr = pBuilder->RenderFile(m_cmdline.GetInputFileName(), 0); |
| |
| if (FAILED(hr)) { |
| wcout << "RenderFile failed to load input file \"" |
| << m_cmdline.GetInputFileName() << "\".\n" << hrtext(hr) << L" (0x" |
| << hex << hr << dec << L")" << endl; |
| |
| return 1; |
| } else if (hr != S_OK) { |
| wcout << "RenderFile only partially succeeded.\n" << hrtext(hr) << L" (0x" |
| << hex << hr << dec << L")" << endl; |
| } |
| } |
| |
| #if 0 |
| hr = pBuilder->SetLogFile(0); |
| assert(SUCCEEDED(hr)); |
| |
| const BOOL b = CloseHandle(hFile); |
| assert(b); |
| #endif |
| |
| if (bList) |
| return 1; // soft error |
| |
| return 0; // success |
| } |
| |
| void App::DestroyGraph() { |
| if (IFilterGraph* pGraph = m_pGraph.Detach()) { |
| const ULONG n = pGraph->Release(); |
| n; |
| assert(n == 0); |
| } |
| } |
| |
| int App::RunGraph() { |
| assert(bool(m_pGraph)); |
| |
| const GraphUtil::IMediaEventPtr pEvent(m_pGraph); |
| assert(bool(pEvent)); |
| |
| HANDLE h; |
| |
| HRESULT hr = pEvent->GetEventHandle((OAEVENT*)&h); |
| assert(hr == S_OK); |
| assert(h); |
| |
| // DWORD dw = WaitForSingleObject(h, 0); |
| |
| enum { nh = 2 }; |
| const HANDLE ha[nh] = {m_hQuit, h}; |
| |
| const GraphUtil::IMediaControlPtr pControl(m_pGraph); |
| assert(bool(pControl)); |
| |
| hr = pControl->Run(); |
| assert(SUCCEEDED(hr)); |
| |
| for (;;) { |
| MSG msg; |
| |
| while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { |
| assert(msg.message != WM_QUIT); |
| |
| // wcout << "msg.message=" << msg.message << endl; |
| |
| TranslateMessage(&msg); |
| DispatchMessage(&msg); |
| } |
| |
| const DWORD dw = MsgWaitForMultipleObjects(nh, ha, 0, |
| INFINITE, // timeout (ms) |
| QS_ALLINPUT); |
| |
| assert(dw >= WAIT_OBJECT_0); |
| assert(dw <= (WAIT_OBJECT_0 + nh)); |
| |
| if (dw == WAIT_OBJECT_0) // quit |
| break; |
| |
| if (dw == (WAIT_OBJECT_0 + nh)) // window message |
| continue; |
| |
| // media event |
| |
| long code, param1, param2; |
| |
| HRESULT hr = pEvent->GetEvent(&code, ¶m1, ¶m2, 0); |
| assert(hr == S_OK); |
| |
| hr = pEvent->FreeEventParams(code, param1, param2); |
| assert(hr == S_OK); |
| |
| if (code == EC_USERABORT) // window closed |
| break; |
| |
| if (code == EC_COMPLETE) |
| break; |
| } |
| |
| wcout << endl; |
| |
| hr = pControl->Stop(); |
| assert(SUCCEEDED(hr)); |
| |
| return 0; |
| } |
| |
| void App::RenderFailed(IPin* pin, HRESULT hrRender) { |
| assert(pin); |
| |
| wstring name; |
| |
| PIN_INFO info; |
| |
| HRESULT hr = pin->QueryPinInfo(&info); |
| |
| if (SUCCEEDED(hr)) { |
| if (info.pFilter) |
| info.pFilter->Release(); |
| |
| name = info.achName; |
| } |
| |
| if (name.empty()) { |
| wchar_t* id; |
| |
| hr = pin->QueryId(&id); |
| |
| if (SUCCEEDED(hr) && (id != 0)) { |
| name = id; |
| CoTaskMemFree(id); |
| } |
| } |
| |
| if (name.empty()) { |
| AM_MEDIA_TYPE mt; |
| |
| hr = pin->ConnectionMediaType(&mt); |
| |
| if (SUCCEEDED(hr)) { |
| if (mt.majortype == MEDIATYPE_Video) |
| name = L"video"; |
| |
| else if (mt.subtype == MEDIATYPE_Audio) |
| name = L"audio"; |
| |
| MediaTypeUtil::Destroy(mt); |
| } |
| } |
| |
| wcout << L"Pin"; |
| |
| if (!name.empty()) |
| wcout << L"[" << name << L"]"; |
| |
| wcout << L" failed to render.\n" << hrtext(hrRender) << L" (0x" << hex |
| << hrRender << dec << L")\n" << endl; |
| } |