| // 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 "makewebmapp.hpp" | |
| #include <cassert> | |
| #include <iostream> | |
| #include <iomanip> | |
| #include <uuids.h> | |
| #include <evcode.h> | |
| #include "hrtext.hpp" | |
| #include "mediatypeutil.hpp" | |
| #include "webmtypes.hpp" | |
| #include "vorbistypes.hpp" | |
| #include "registry.hpp" | |
| #include "vp8encoderidl.h" | |
| #include "webmmuxidl.h" | |
| #include "versionhandling.hpp" | |
| #include <sstream> | |
| #include <iomanip> | |
| #include <cmath> | |
| using std::hex; | |
| using std::dec; | |
| using std::wcout; | |
| using std::setw; | |
| using std::setfill; | |
| using std::endl; | |
| using std::flush; | |
| using std::wostringstream; | |
| using std::wstring; | |
| using GraphUtil::IPinPtr; | |
| using GraphUtil::IBaseFilterPtr; | |
| using GraphUtil::FindOutpinVideo; | |
| using GraphUtil::FindOutpinAudio; | |
| using GraphUtil::FindInpinVideo; | |
| using GraphUtil::FindInpinAudio; | |
| extern HANDLE g_hQuit; | |
| App::App() | |
| { | |
| } | |
| int App::operator()(int argc, wchar_t* argv[]) | |
| { | |
| int status = m_cmdline.Parse(argc, argv); | |
| if (status) | |
| return status; | |
| const bool bVerbose = m_cmdline.GetVerbose(); | |
| assert(!bool(m_pGraph)); | |
| HRESULT hr = m_pGraph.CreateInstance(CLSID_FilterGraphNoThread); | |
| if (FAILED(hr)) | |
| { | |
| wcout << L"Unable to create filter graph instance.\n" | |
| << hrtext(hr) | |
| << " (0x" << hex << hr << dec << ")" | |
| << endl; | |
| return 1; //error | |
| } | |
| assert(bool(m_pGraph)); | |
| const GraphUtil::IGraphBuilderPtr pBuilder(m_pGraph); | |
| assert(bool(pBuilder)); | |
| const GraphUtil::IMediaFilterPtr pGraphFilter(m_pGraph); | |
| assert(bool(pGraphFilter)); | |
| hr = pGraphFilter->SetSyncSource(0); //process as quickly as possible | |
| //TODO: are we setting this too early? | |
| #ifdef _DEBUG | |
| if (FAILED(hr)) | |
| { | |
| wcout << L"IMediaFilter::SetSyncSource failed.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| } | |
| #endif | |
| const wchar_t* const ext = wcsrchr(m_cmdline.GetInputFileName(), L'.'); | |
| if ((ext != 0) && (_wcsicmp(ext, L".GRF") == 0)) | |
| { | |
| status = LoadGraph(); | |
| if (status) | |
| return status; | |
| GraphUtil::IBaseFilterPtr pMuxer; | |
| hr = m_pGraph->FindFilterByName(L"webmmux", &pMuxer); | |
| if (hr != S_OK) | |
| { | |
| wcout << L"WebM muxer filter not found."; | |
| return 1; | |
| } | |
| const GraphUtil::IMediaSeekingPtr pSeek(pMuxer); | |
| assert(bool(pSeek)); | |
| status = RunGraph(pSeek); | |
| return status; | |
| } | |
| IBaseFilterPtr pReader; | |
| if (m_cmdline.GetOggToWebm() > 0) | |
| { | |
| hr = pReader.CreateInstance(WebmTypes::CLSID_WebmOggSource); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to create instance of Ogg source filter.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| const GraphUtil::IFileSourceFilterPtr pFile(pReader); | |
| if (!pFile) | |
| { | |
| wcout << "Ogg source filter does not support IFileSourceFilter." | |
| << endl; | |
| return 1; | |
| } | |
| hr = pFile->Load(m_cmdline.GetInputFileName(), 0); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to load Ogg source filter.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| hr = m_pGraph->AddFilter(pReader, L"oggsource"); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to add Ogg source filter to graph.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| } | |
| else | |
| { | |
| hr = pBuilder->AddSourceFilter( | |
| m_cmdline.GetInputFileName(), | |
| L"source", | |
| &pReader); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to add source filter to graph.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| } | |
| assert(bool(pReader)); | |
| if (GraphUtil::PinCount(pReader) == 0) | |
| { | |
| wcout << "Source filter does not have any output pins.\n" << endl; | |
| return 1; | |
| } | |
| const GraphUtil::IBaseFilterPtr pDemux = AddDemuxFilter(pReader, L"demux"); | |
| if (!bool(pDemux)) | |
| return 1; | |
| const GraphUtil::IPinPtr pDemuxOutpinVideo = FindOutpinVideo(pDemux); | |
| //TODO: we need to do better here: we check for the 0 case, | |
| //but we must also check for the 1+ case. | |
| if (!bool(pDemuxOutpinVideo)) | |
| { | |
| if (bVerbose) | |
| wcout << "Demuxer does not expose video output pin." << endl; | |
| //TODO: we need to provide a command-line option that says to | |
| //quit immediately if no video stream. | |
| } | |
| else | |
| { | |
| if (bVerbose) | |
| DumpPreferredMediaTypes( | |
| pDemuxOutpinVideo, | |
| L"demuxer video outpin", | |
| &App::DumpVideoMediaType); | |
| } | |
| GraphUtil::IPinPtr pDemuxOutpinAudio; | |
| if (const wchar_t* f = m_cmdline.GetAudioInputFileName()) | |
| { | |
| IBaseFilterPtr pAudioReader; | |
| hr = pBuilder->AddSourceFilter(f, L"audio source", &pAudioReader); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to add audio source filter to graph.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| assert(bool(pAudioReader)); | |
| if (GraphUtil::PinCount(pAudioReader) == 0) | |
| { | |
| wcout << "Audio source filter does not have " | |
| << "any output pins.\n" << endl; | |
| return 1; | |
| } | |
| const GraphUtil::IBaseFilterPtr pAudioDemux = | |
| AddDemuxFilter(pAudioReader, L"audio demux"); | |
| if (!bool(pAudioDemux)) | |
| return 1; | |
| pDemuxOutpinAudio = FindOutpinAudio(pAudioDemux); | |
| } | |
| else | |
| pDemuxOutpinAudio = FindOutpinAudio(pDemux); | |
| //TODO: we need to do better here: we check for the 0 case, | |
| //but we must also check for the 1+ case. | |
| if (!bool(pDemuxOutpinAudio)) | |
| { | |
| if (bVerbose) | |
| wcout << "Demuxer does not expose audio output pin." << endl; | |
| //TODO: we need to provide a command-line option that says to | |
| //quit immediately if no audio stream. | |
| } | |
| else | |
| { | |
| if (bVerbose) | |
| DumpPreferredMediaTypes( | |
| pDemuxOutpinAudio, | |
| L"demuxer audio outpin", | |
| &App::DumpAudioMediaType); | |
| } | |
| #if 0 | |
| IRunningObjectTablePtr rot; | |
| 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)); | |
| #endif | |
| const bool bNoVideo = m_cmdline.GetNoVideo(); | |
| const bool bTwoPass = (m_cmdline.GetTwoPass() >= 1); | |
| if (bTwoPass && !bNoVideo) | |
| { | |
| assert(m_cmdline.GetSaveGraphFile() == 0); | |
| GraphUtil::IPinPtr pEncoderOutpin; | |
| status = CreateFirstPassGraph(pDemuxOutpinVideo, &pEncoderOutpin); | |
| if (status) | |
| return status; | |
| const GraphUtil::IMediaSeekingPtr pSeek(pEncoderOutpin); | |
| assert(bool(pSeek)); | |
| status = RunGraph(pSeek); | |
| if (status) | |
| return status; | |
| IBaseFilterPtr pWriter; | |
| hr = m_pGraph->FindFilterByName(L"writer", &pWriter); | |
| assert(SUCCEEDED(hr)); | |
| assert(bool(pWriter)); | |
| hr = m_pGraph->RemoveFilter(pWriter); | |
| assert(SUCCEEDED(hr)); | |
| } | |
| { | |
| IBaseFilterPtr pMux; | |
| const bool bNoAudio = m_cmdline.GetNoAudio(); | |
| status = CreateMuxerGraph( | |
| bTwoPass, | |
| bNoVideo ? 0 : pDemuxOutpinVideo, | |
| bNoAudio ? 0 : pDemuxOutpinAudio, | |
| &pMux); | |
| if (status) | |
| return status; | |
| status = SaveGraph(); | |
| if (status) | |
| return status; | |
| const GraphUtil::IMediaSeekingPtr pSeek(pMux); | |
| assert(bool(pSeek)); | |
| LONGLONG curr = 0; | |
| LONGLONG stop = 0; | |
| hr = pSeek->SetPositions( | |
| &curr, | |
| AM_SEEKING_AbsolutePositioning, | |
| &stop, | |
| AM_SEEKING_NoPositioning); | |
| assert(SUCCEEDED(hr)); | |
| //TODO: | |
| //DWORD dw; | |
| // | |
| //hr = rot->Register( | |
| // ROTFLAGS_REGISTRATIONKEEPSALIVE, | |
| // pGraph, | |
| // mon, | |
| // &dw); | |
| //assert(SUCCEEDED(hr)); | |
| status = RunGraph(pSeek); | |
| //hr = rot->Revoke(dw); | |
| //assert(SUCCEEDED(hr)); | |
| } | |
| return status; | |
| } | |
| int App::CreateMuxerGraph( | |
| bool bTwoPass, | |
| IPin* pDemuxOutpinVideo, | |
| IPin* pDemuxOutpinAudio, | |
| IBaseFilter** ppMux) | |
| { | |
| assert(bool(m_pGraph)); | |
| assert(ppMux); | |
| const GraphUtil::IGraphBuilderPtr pBuilder(m_pGraph); | |
| assert(bool(pBuilder)); | |
| const bool bVerbose = m_cmdline.GetVerbose(); | |
| HRESULT hr = CoCreateInstance( | |
| CLSID_WebmMux, | |
| 0, | |
| CLSCTX_INPROC_SERVER, | |
| __uuidof(IBaseFilter), | |
| (void**)ppMux); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to create WebmMux filter instance.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| assert(ppMux); | |
| IBaseFilter*& pMux = *ppMux; | |
| assert(GraphUtil::InpinCount(pMux) == 2); //TODO: liberalize | |
| assert(GraphUtil::OutpinCount(pMux) == 1); | |
| hr = m_pGraph->AddFilter(pMux, L"webmmux"); | |
| assert(SUCCEEDED(hr)); | |
| { | |
| _COM_SMARTPTR_TYPEDEF(IWebmMux, __uuidof(IWebmMux)); | |
| const IWebmMuxPtr pWebmMux(pMux); | |
| if (!bool(pWebmMux)) | |
| { | |
| wcout << "WebmMux filter instance does not support" | |
| << " IWebmMux interface." | |
| << endl; | |
| return 1; | |
| } | |
| //_wpgmptr | |
| wchar_t* fname; | |
| const errno_t e = _get_wpgmptr(&fname); | |
| assert(e == 0); | |
| wostringstream os; | |
| os << L"makewebm-"; | |
| VersionHandling::GetVersion(fname, os); | |
| hr = pWebmMux->SetWritingApp(os.str().c_str()); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set \"makewebm\" as WebM writing app.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| if (m_cmdline.GetLive()) | |
| { | |
| hr = pWebmMux->SetMuxMode(kWebmMuxModeLive); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to enable live mode.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| } | |
| } | |
| int nConnections = 0; | |
| if (pDemuxOutpinVideo) | |
| { | |
| const IPinPtr pMuxInpinVideo(FindInpinVideo(pMux)); | |
| assert(bool(pMuxInpinVideo)); | |
| if (IsVPX(pDemuxOutpinVideo)) | |
| { | |
| assert(!bTwoPass); | |
| //TODO(matthewjheaney): liberalize this, to allow clients | |
| //to do a re-code of VPx using different encoding config. | |
| hr = m_pGraph->ConnectDirect( | |
| pDemuxOutpinVideo, | |
| pMuxInpinVideo, | |
| 0); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to (directly) connect splitter video outpin" | |
| << " to muxer video inpin.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| } | |
| else | |
| { | |
| IBaseFilterPtr pCompressor; | |
| if (bTwoPass) | |
| { | |
| hr = m_pGraph->FindFilterByName(L"vp8enc", &pCompressor); | |
| assert(SUCCEEDED(hr)); | |
| assert(bool(pCompressor)); | |
| } | |
| else | |
| { | |
| hr = pCompressor.CreateInstance(CLSID_VP8Encoder); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to create VPX encoder filter instance.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| assert(bool(pCompressor)); | |
| } | |
| _COM_SMARTPTR_TYPEDEF(IVP8Encoder, __uuidof(IVP8Encoder)); | |
| const IVP8EncoderPtr pVP8(pCompressor); | |
| assert(bool(pVP8)); | |
| if (bTwoPass) | |
| { | |
| hr = pVP8->SetPassMode(kPassModeLastPass); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VPX encoder pass mode" | |
| << " (last pass).\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| const wchar_t* const stats_filename = | |
| m_stats_filename.c_str(); | |
| hr = m_stats_file.Open(stats_filename); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to open stats file.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| const BYTE* buf; | |
| LONGLONG len; | |
| hr = m_stats_file.GetView(buf, len); | |
| assert(SUCCEEDED(hr)); | |
| assert(buf); | |
| assert(len >= 0); | |
| hr = pVP8->SetTwoPassStatsBuf(buf, len); | |
| assert(SUCCEEDED(hr)); | |
| } | |
| else | |
| { | |
| hr = m_pGraph->AddFilter(pCompressor, L"vp8enc"); | |
| assert(SUCCEEDED(hr)); | |
| if (m_cmdline.GetEncoderKind() == kVP9Encoder) | |
| { | |
| _COM_SMARTPTR_TYPEDEF(IVPXEncoder, __uuidof(IVPXEncoder)); | |
| const IVPXEncoderPtr pVPX(pCompressor); | |
| if (!bool(pVPX)) | |
| { | |
| wcout << L"Encoder filter instance does support VP9.\n"; | |
| return 1; | |
| } | |
| hr = pVPX->SetEncoderKind(kVP9Encoder); | |
| if (FAILED(hr)) | |
| { | |
| wcout << L"Unable to set encoder kind to VP9.\n"; | |
| return 1; | |
| } | |
| } | |
| IPinPtr pVP8Inpin; | |
| hr = pCompressor->FindPin(L"input", &pVP8Inpin); | |
| assert(SUCCEEDED(hr)); | |
| assert(bool(pVP8Inpin)); | |
| hr = pBuilder->Connect(pDemuxOutpinVideo, pVP8Inpin); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to connect demux outpin to" | |
| << " VPX encoder filter inpin.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| hr = pVP8->SetPassMode(kPassModeOnePass); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VPX encoder pass mode" | |
| << " (one pass).\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| AM_MEDIA_TYPE mt; | |
| const HRESULT hrMT = pVP8Inpin->ConnectionMediaType(&mt); | |
| hr = SetVP8Options(pVP8, SUCCEEDED(hrMT) ? &mt : 0); | |
| if (SUCCEEDED(hrMT)) | |
| MediaTypeUtil::Destroy(mt); | |
| if (FAILED(hr)) | |
| return 1; | |
| } | |
| IPinPtr pVP8Outpin; | |
| hr = pCompressor->FindPin(L"output", &pVP8Outpin); | |
| assert(SUCCEEDED(hr)); | |
| assert(bool(pVP8Outpin)); | |
| hr = m_pGraph->ConnectDirect(pVP8Outpin, pMuxInpinVideo, 0); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to connect VPX encoder outpin" | |
| << " to muxer video inpin.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| } | |
| ++nConnections; | |
| if (bVerbose) | |
| DumpConnectionMediaType( | |
| pMuxInpinVideo, | |
| L"muxer video inpin", | |
| &App::DumpVideoMediaType); | |
| } | |
| if (pDemuxOutpinAudio) | |
| { | |
| const IPinPtr pMuxInpinAudio(FindInpinAudio(pMux)); | |
| assert(bool(pMuxInpinAudio)); | |
| if (!ConnectAudio(pDemuxOutpinAudio, pMuxInpinAudio)) | |
| { | |
| if (m_cmdline.GetRequireAudio()) | |
| { | |
| wcout << "Source has audio," | |
| << " but no audio stream connected to muxer." | |
| << endl; | |
| return 1; | |
| } | |
| } | |
| else | |
| { | |
| ++nConnections; | |
| if (bVerbose) | |
| DumpConnectionMediaType( | |
| pMuxInpinAudio, | |
| L"muxer audio inpin", | |
| &App::DumpAudioMediaType); | |
| } | |
| } | |
| if (nConnections <= 0) | |
| { | |
| wcout << L"No splitter outpins are connected to muxer inpins." | |
| << endl; | |
| return 1; | |
| } | |
| IBaseFilterPtr pWriter; | |
| hr = pWriter.CreateInstance(CLSID_FileWriter); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to create writer filter instance.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| assert(bool(pWriter)); | |
| assert(GraphUtil::InpinCount(pWriter) == 1); | |
| m_pGraph->AddFilter(pWriter, L"writer"); | |
| assert(SUCCEEDED(hr)); | |
| const GraphUtil::IFileSinkFilterPtr pSink(pWriter); | |
| assert(bool(pSink)); | |
| hr = pSink->SetFileName(m_cmdline.GetOutputFileName(), 0); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set output filename of file writer filter.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| hr = GraphUtil::ConnectDirect(m_pGraph, pMux, pWriter, 0); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to connect muxer to writer.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| //TODO: this needs to be a dedicated switch, | |
| //e.g. --dry-run or --build-only | |
| //if (bList) | |
| // return 1; //soft error | |
| return 0; //success | |
| } | |
| int App::CreateFirstPassGraph( | |
| IPin* pDemuxOutpinVideo, | |
| IPin** ppEncoderOutpin) | |
| { | |
| assert(bool(m_pGraph)); | |
| assert(pDemuxOutpinVideo); | |
| assert(ppEncoderOutpin); | |
| const GraphUtil::IGraphBuilderPtr pBuilder(m_pGraph); | |
| assert(bool(pBuilder)); | |
| //const bool bVerbose = m_cmdline.GetVerbose(); | |
| if (IsVPX(pDemuxOutpinVideo)) | |
| { | |
| //TODO(matthewjheaney): we should probably handle this case, | |
| //so that they can do a re-encode with different parameters. | |
| wcout << "Video demux stream is already VPx" | |
| << " -- two-pass not supported.\n"; | |
| return 1; | |
| } | |
| IBaseFilterPtr pCompressor; | |
| HRESULT hr = pCompressor.CreateInstance(CLSID_VP8Encoder); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to create VPX encoder filter instance.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| assert(bool(pCompressor)); | |
| hr = m_pGraph->AddFilter(pCompressor, L"vp8enc"); | |
| assert(SUCCEEDED(hr)); | |
| if (m_cmdline.GetEncoderKind() == kVP9Encoder) | |
| { | |
| _COM_SMARTPTR_TYPEDEF(IVPXEncoder, __uuidof(IVPXEncoder)); | |
| const IVPXEncoderPtr pVPX(pCompressor); | |
| if (!bool(pVPX)) | |
| { | |
| wcout << L"Encoder filter instance does support VP9.\n"; | |
| return 1; | |
| } | |
| hr = pVPX->SetEncoderKind(kVP9Encoder); | |
| if (FAILED(hr)) | |
| { | |
| wcout << L"Unable to set encoder kind to VP9.\n"; | |
| return 1; | |
| } | |
| } | |
| IPinPtr pVP8Inpin; | |
| hr = pCompressor->FindPin(L"input", &pVP8Inpin); | |
| assert(SUCCEEDED(hr)); | |
| assert(bool(pVP8Inpin)); | |
| hr = pBuilder->Connect(pDemuxOutpinVideo, pVP8Inpin); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to connect demux outpin to" | |
| << " VPX encoder filter inpin.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| _COM_SMARTPTR_TYPEDEF(IVP8Encoder, __uuidof(IVP8Encoder)); | |
| const IVP8EncoderPtr pVP8(pCompressor); | |
| assert(bool(pVP8)); | |
| hr = pVP8->SetPassMode(kPassModeFirstPass); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VPX encoder pass mode (first pass).\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| AM_MEDIA_TYPE mt; | |
| const HRESULT hrMT = pVP8Inpin->ConnectionMediaType(&mt); | |
| hr = SetVP8Options(pVP8, SUCCEEDED(hrMT) ? &mt : 0); | |
| if (SUCCEEDED(hrMT)) | |
| MediaTypeUtil::Destroy(mt); | |
| if (FAILED(hr)) | |
| return 1; | |
| IBaseFilterPtr pWriter; | |
| hr = pWriter.CreateInstance(CLSID_FileWriter); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to create writer filter instance.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| assert(bool(pWriter)); | |
| assert(GraphUtil::InpinCount(pWriter) == 1); | |
| m_pGraph->AddFilter(pWriter, L"writer"); | |
| assert(SUCCEEDED(hr)); | |
| const GraphUtil::IFileSinkFilterPtr pSink(pWriter); | |
| assert(bool(pSink)); | |
| const wchar_t* const filename = GetStatsFileName(); | |
| if (filename == 0) | |
| return 1; | |
| hr = pSink->SetFileName(filename, 0); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set output filename (for two-pass stats)" | |
| << " of file writer filter.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| IPin*& pEncoderOutpin = *ppEncoderOutpin; | |
| hr = pCompressor->FindPin(L"output", &pEncoderOutpin); | |
| assert(SUCCEEDED(hr)); | |
| assert(pEncoderOutpin); | |
| const GraphUtil::IPinPtr pWriterInpin = GraphUtil::FindInpin(pWriter); | |
| assert(bool(pWriterInpin)); | |
| hr = m_pGraph->ConnectDirect(pEncoderOutpin, pWriterInpin, 0); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to connect VPX encoder outpin to file writer inpin" | |
| << " (for two-pass stats).\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| return 0; //success | |
| } | |
| int App::LoadGraph() | |
| { | |
| const wchar_t* const input_filename = m_cmdline.GetInputFileName(); | |
| if (StgIsStorageFile(input_filename) != S_OK) | |
| { | |
| wcout << "Input GraphEdit file is not a storage file." << endl; | |
| return 1; | |
| } | |
| IStoragePtr pStg; | |
| HRESULT hr = StgOpenStorage( | |
| input_filename, | |
| 0, | |
| STGM_TRANSACTED | STGM_READ | STGM_SHARE_DENY_WRITE, | |
| 0, | |
| 0, | |
| &pStg); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to open GraphEdit storage file.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| IStreamPtr pStream; | |
| hr = pStg->OpenStream( | |
| L"ActiveMovieGraph", | |
| 0, | |
| STGM_READ | STGM_SHARE_EXCLUSIVE, | |
| 0, | |
| &pStream); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to open GraphEdit file stream.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| const IPersistStreamPtr pPersistStream(m_pGraph); | |
| assert(bool(pPersistStream)); | |
| hr = pPersistStream->Load(pStream); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to open load GraphEdit stream.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| return 0; //success | |
| } | |
| int App::SaveGraph() | |
| { | |
| const wchar_t* const f = m_cmdline.GetSaveGraphFile(); | |
| if (f == 0) | |
| return 0; //nothing to do here | |
| IStoragePtr pStg; | |
| HRESULT hr = StgCreateDocfile( | |
| f, | |
| STGM_CREATE | STGM_TRANSACTED | | |
| STGM_READWRITE | STGM_SHARE_EXCLUSIVE, | |
| 0, | |
| &pStg); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to create GraphEdit storage file.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| IStreamPtr pStream; | |
| hr = pStg->CreateStream( | |
| L"ActiveMovieGraph", | |
| STGM_WRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE, | |
| 0, | |
| 0, | |
| &pStream); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to create GraphEdit storage stream.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| const IPersistStreamPtr pPersistStream(m_pGraph); | |
| assert(bool(pPersistStream)); | |
| hr = pPersistStream->Save(pStream, TRUE); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to save GraphEdit storage stream.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| hr = pStg->Commit(STGC_DEFAULT); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to commit GraphEdit storage stream.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| return 0; //success | |
| } | |
| int App::RunGraph(IMediaSeeking* pSeek) | |
| { | |
| assert(bool(m_pGraph)); | |
| assert(pSeek); | |
| const GraphUtil::IMediaEventPtr pEvent(m_pGraph); | |
| assert(bool(pEvent)); | |
| HANDLE h; | |
| HRESULT hr = pEvent->GetEventHandle((OAEVENT*)&h); | |
| assert(hr == S_OK); | |
| assert(h); | |
| enum { nh = 2 }; | |
| const HANDLE ha[nh] = { g_hQuit, h }; | |
| const GraphUtil::IMediaControlPtr pControl(m_pGraph); | |
| assert(bool(pControl)); | |
| hr = pControl->Run(); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to run graph.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 1; | |
| } | |
| //int n = 1; | |
| m_progress = 0; | |
| for (;;) | |
| { | |
| MSG msg; | |
| while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) | |
| { | |
| assert(msg.message != WM_QUIT); | |
| TranslateMessage(&msg); | |
| DispatchMessage(&msg); | |
| } | |
| const DWORD dw = MsgWaitForMultipleObjects( | |
| nh, | |
| ha, | |
| 0, | |
| 100, | |
| QS_ALLINPUT); | |
| if (dw == WAIT_TIMEOUT) | |
| { | |
| DisplayProgress(pSeek, false); | |
| continue; | |
| } | |
| assert(dw >= WAIT_OBJECT_0); | |
| assert(dw <= (WAIT_OBJECT_0 + nh)); | |
| if (dw == WAIT_OBJECT_0) //quit | |
| { | |
| //wcout << "CTRL+C detected" << endl; | |
| 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 | |
| { | |
| //wcout << "EC_USERABORT" << endl; | |
| break; | |
| } | |
| if (code == EC_COMPLETE) | |
| { | |
| //wcout << "EC_COMPLETE" << endl; | |
| break; | |
| } | |
| //if (code == (EC_USER + 0x100)) //done muxing | |
| // break; | |
| } | |
| DisplayProgress(pSeek, true); | |
| if (!m_cmdline.ScriptMode()) | |
| wcout << endl; | |
| hr = pControl->Stop(); | |
| assert(SUCCEEDED(hr)); | |
| return 0; | |
| } | |
| void App::DisplayProgress(IMediaSeeking* pSeek, bool last) | |
| { | |
| assert(pSeek); | |
| //TODO: display this (or give option to) in HH:MM:SS.sss | |
| //TODO: attempt to query upstream filter for duration. | |
| __int64 curr; | |
| HRESULT hr = pSeek->GetCurrentPosition(&curr); | |
| if (FAILED(hr)) | |
| { | |
| #ifdef _DEBUG | |
| wcout << L"IMediaSeeking::GetCurrPos failed: " | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| #endif | |
| return; | |
| } | |
| assert(curr >= 0); | |
| double val = double(curr) / 10000000; | |
| wcout << std::fixed << std::setprecision(1); | |
| if (m_cmdline.ScriptMode()) | |
| wcout << "TIME=" << val; | |
| else | |
| wcout << "\rtime[sec]=" << val; | |
| __int64 d; | |
| //TODO: it's not clear whether we're allowed to call | |
| //IMediaSeeking::GetDuration directly on the mkvmux | |
| //filter, or whether we must call the graph object. | |
| #if 1 | |
| hr = pSeek->GetDuration(&d); | |
| #else | |
| const GraphUtil::IMediaSeekingPtr pSeek(m_pGraph); | |
| assert(bool(pSeek)); | |
| hr = pSeek->GetDuration(&d); | |
| #endif | |
| if (SUCCEEDED(hr)) //have duration | |
| { | |
| val = double(d) / 10000000; | |
| if (m_cmdline.ScriptMode()) | |
| wcout << " DURATION=" << val; | |
| else | |
| wcout << L'/' << val; | |
| } | |
| if (m_cmdline.ScriptMode()) | |
| { | |
| wcout << endl; | |
| return; | |
| } | |
| if (last) | |
| m_progress = 0; | |
| //TODO: there's probably a slicker way to do this: | |
| assert(m_progress >= 0); | |
| assert(m_progress <= 10); | |
| const int space = 10 - m_progress; | |
| for (int i = 0; i < m_progress; ++i) | |
| wcout << L'.'; | |
| for (int i = 0; i < space; ++i) | |
| wcout << L' '; | |
| ++m_progress; | |
| if (m_progress > 10) | |
| m_progress = 0; | |
| wcout << flush; | |
| } | |
| void App::DumpVideoMediaType(const AM_MEDIA_TYPE& mt) | |
| { | |
| wcout << "mt.subtype="; | |
| if (mt.subtype == MEDIASUBTYPE_MPEG2_VIDEO) | |
| wcout << "MPEG2_VIDEO"; | |
| else | |
| wcout << GraphUtil::ToString(mt.subtype); | |
| wcout << L'\n'; | |
| wcout << "mt.cbFormat=" << mt.cbFormat << L'\n'; | |
| if (mt.formattype == FORMAT_VideoInfo) | |
| { | |
| wcout << "mt.format=VideoInfo\n"; | |
| assert(mt.cbFormat >= sizeof(VIDEOINFOHEADER)); | |
| assert(mt.pbFormat); | |
| const VIDEOINFOHEADER& vih = (VIDEOINFOHEADER&)(*mt.pbFormat); | |
| DumpVideoInfoHeader(vih); | |
| } | |
| else if (mt.formattype == FORMAT_VideoInfo2) | |
| { | |
| wcout << "mt.format=VideoInfo2\n"; | |
| assert(mt.cbFormat >= sizeof(VIDEOINFOHEADER2)); | |
| assert(mt.pbFormat); | |
| const VIDEOINFOHEADER2& vih = (VIDEOINFOHEADER2&)(*mt.pbFormat); | |
| DumpVideoInfoHeader2(vih); | |
| } | |
| else if (mt.formattype == FORMAT_MPEG2_VIDEO) | |
| { | |
| wcout << "mt.format=MPEG2_VIDEO\n"; | |
| const MPEG2VIDEOINFO& mpg = (MPEG2VIDEOINFO&)(*mt.pbFormat); | |
| assert(mt.cbFormat >= SIZE_MPEG2VIDEOINFO(&mpg)); | |
| #if 0 | |
| const DWORD cb = mpg.cbSequenceHeader; | |
| cb; | |
| assert(cb >= 4); | |
| const BYTE* const h = MPEG2_SEQUENCE_INFO(&mpg); | |
| h; | |
| //There doesn't seem to be agreement among filters about | |
| //whether this 4-byte header should exist. These assertions | |
| //fail with the Haali splitter, for example. | |
| assert(h[0] == 0); | |
| assert(h[1] == 0); | |
| assert(h[2] == 1); | |
| assert(h[3] == 0xB3); | |
| #endif | |
| const VIDEOINFOHEADER2& vih = mpg.hdr; | |
| DumpVideoInfoHeader2(vih); | |
| } | |
| else | |
| { | |
| wcout << "mt.format=" << GraphUtil::ToString(mt.formattype) << L'\n'; | |
| } | |
| } | |
| double App::GetFramerate(const AM_MEDIA_TYPE& mt) | |
| { | |
| __int64 reftime_per_frame; | |
| if (mt.formattype == FORMAT_VideoInfo) | |
| { | |
| assert(mt.cbFormat >= sizeof(VIDEOINFOHEADER)); | |
| assert(mt.pbFormat); | |
| const VIDEOINFOHEADER& vih = (VIDEOINFOHEADER&)(*mt.pbFormat); | |
| reftime_per_frame = vih.AvgTimePerFrame; | |
| } | |
| else if (mt.formattype == FORMAT_VideoInfo2) | |
| { | |
| assert(mt.cbFormat >= sizeof(VIDEOINFOHEADER2)); | |
| assert(mt.pbFormat); | |
| const VIDEOINFOHEADER2& vih = (VIDEOINFOHEADER2&)(*mt.pbFormat); | |
| reftime_per_frame = vih.AvgTimePerFrame; | |
| } | |
| else if (mt.formattype == FORMAT_MPEG2_VIDEO) | |
| { | |
| const MPEG2VIDEOINFO& mpg = (MPEG2VIDEOINFO&)(*mt.pbFormat); | |
| assert(mt.cbFormat >= SIZE_MPEG2VIDEOINFO(&mpg)); | |
| const VIDEOINFOHEADER2& vih = mpg.hdr; | |
| reftime_per_frame = vih.AvgTimePerFrame; | |
| } | |
| else | |
| return -1; | |
| if (reftime_per_frame <= 0) | |
| return -1; | |
| const double framerate = 10000000.0 / double(reftime_per_frame); | |
| return framerate; | |
| } | |
| void App::DumpAudioMediaType(const AM_MEDIA_TYPE& mt) | |
| { | |
| using namespace VorbisTypes; | |
| wcout << "mt.subtype="; | |
| if (mt.subtype == MEDIASUBTYPE_Vorbis2) | |
| wcout << "Vorbis2 (Matroska)"; | |
| else if (mt.subtype == MEDIASUBTYPE_Vorbis2_Xiph_Lacing) | |
| wcout << "Vorbis2 (Matroska - Xiph Lacing)"; | |
| else if (mt.subtype == MEDIASUBTYPE_Vorbis) | |
| wcout << "Vorbis (Xiph)"; | |
| else if (mt.subtype == MEDIASUBTYPE_DOLBY_AC3) | |
| wcout << "DOLBY_AC3"; | |
| else | |
| wcout << GraphUtil::ToString(mt.subtype); | |
| wcout << L'\n'; | |
| wcout << "mt.cbFormat=" << mt.cbFormat << L'\n'; | |
| if (mt.formattype == FORMAT_WaveFormatEx) | |
| { | |
| wcout << "mt.format=WaveFormatEx\n"; | |
| assert(mt.cbFormat >= sizeof(WAVEFORMATEX)); | |
| assert(mt.pbFormat); | |
| const WAVEFORMATEX& wfx = (WAVEFORMATEX&)(*mt.pbFormat); | |
| wcout << "wfx.wFormatTag=0x" | |
| << setw(4) | |
| << setfill(L'0') | |
| << hex | |
| << wfx.wFormatTag | |
| << dec | |
| << L'\n'; | |
| wcout << "wfx.nChannels=" | |
| << wfx.nChannels | |
| << L'\n'; | |
| wcout << "wfx.nSamplesPerSec=" | |
| << wfx.nSamplesPerSec | |
| << L'\n'; | |
| wcout << "wfx.nAvgBytesPerSec=" | |
| << wfx.nAvgBytesPerSec | |
| << L'\n'; | |
| wcout << "wfx.nBlockAlign=" | |
| << wfx.nBlockAlign | |
| << L'\n'; | |
| wcout << "wfx.wBitsPerSample=" | |
| << wfx.wBitsPerSample | |
| << L'\n'; | |
| wcout << "wfx.cbSize=" | |
| << wfx.cbSize | |
| << L'\n'; | |
| } | |
| else if (mt.formattype == FORMAT_Vorbis2) | |
| { | |
| wcout << "mt.format=Vorbis2 (Matroska)\n"; | |
| assert(mt.cbFormat >= sizeof(VORBISFORMAT2)); | |
| assert(mt.pbFormat); | |
| const VORBISFORMAT2& fmt = (VORBISFORMAT2&)(*mt.pbFormat); | |
| wcout << "fmt.channels=" | |
| << fmt.channels | |
| << L'\n'; | |
| wcout << "fmt.samplesPerSec=" | |
| << fmt.samplesPerSec | |
| << L'\n'; | |
| wcout << "fmt.bitsPerSample=" | |
| << fmt.bitsPerSample | |
| << L'\n'; | |
| wcout << "fmt.headerSize[0=\"ident\"]=" | |
| << fmt.headerSize[0] | |
| << L'\n'; | |
| wcout << "fmt.headerSize[1=\"comment\"]=" | |
| << fmt.headerSize[1] | |
| << L'\n'; | |
| wcout << "fmt.headerSize[2=\"setup\"]=" | |
| << fmt.headerSize[2] | |
| << L'\n'; | |
| } | |
| else if (mt.formattype == FORMAT_Vorbis) | |
| { | |
| wcout << "mt.format=Vorbis (Xiph)\n"; | |
| assert(mt.cbFormat >= sizeof(VORBISFORMAT)); | |
| assert(mt.pbFormat); | |
| const VORBISFORMAT& fmt = (VORBISFORMAT&)(*mt.pbFormat); | |
| wcout << "fmt.vorbisVersion=" | |
| << fmt.vorbisVersion | |
| << L'\n'; | |
| wcout << "fmt.samplesPerSec=" | |
| << fmt.samplesPerSec | |
| << L'\n'; | |
| wcout << "fmt.minBitsPerSec=" | |
| << fmt.minBitsPerSec | |
| << L'\n'; | |
| wcout << "fmt.avgBitsPerSec=" | |
| << fmt.avgBitsPerSec | |
| << L'\n'; | |
| wcout << "fmt.maxBitsPerSec=" | |
| << fmt.maxBitsPerSec | |
| << L'\n'; | |
| wcout << "fmt.channels=" | |
| << fmt.numChannels | |
| << L'\n'; | |
| } | |
| else | |
| { | |
| wcout << "mt.format=" << GraphUtil::ToString(mt.formattype) << L'\n'; | |
| } | |
| wcout << flush; | |
| } | |
| void App::DumpVideoInfoHeader(const VIDEOINFOHEADER& vih) | |
| { | |
| wcout << "vih.AvgTimePerFrame=" << vih.AvgTimePerFrame; | |
| if (vih.AvgTimePerFrame > 0) | |
| { | |
| const double fps = 10000000 / double(vih.AvgTimePerFrame); | |
| wcout << " (fps=" | |
| << std::fixed << std::setprecision(3) << fps | |
| << ")"; | |
| } | |
| wcout << L'\n'; | |
| const BITMAPINFOHEADER& bmih = vih.bmiHeader; | |
| DumpBitMapInfoHeader(bmih); | |
| } | |
| void App::DumpVideoInfoHeader2(const VIDEOINFOHEADER2& vih) | |
| { | |
| wcout << "vih2.AvgTimePerFrame=" << vih.AvgTimePerFrame; | |
| if (vih.AvgTimePerFrame > 0) | |
| { | |
| const double fps = 10000000 / double(vih.AvgTimePerFrame); | |
| wcout << " (fps=" | |
| << std::fixed << std::setprecision(3) << fps | |
| << ")"; | |
| } | |
| wcout << L'\n'; | |
| const BITMAPINFOHEADER& bmih = vih.bmiHeader; | |
| DumpBitMapInfoHeader(bmih); | |
| } | |
| void App::DumpBitMapInfoHeader(const BITMAPINFOHEADER& bmih) | |
| { | |
| wcout << L"bmih.biSize=" << bmih.biSize << L'\n' | |
| << L"bmih.biWidth=" << bmih.biWidth << L'\n' | |
| << L"bmih.biHeight=" << bmih.biHeight << L'\n' | |
| << L"bmih.biPlanes=" << bmih.biPlanes << L'\n' | |
| << L"bmih.biBitCount=" << bmih.biBitCount << L'\n' | |
| << L"bmih.biCompression=0x" | |
| << setfill(L'0') | |
| << setw(8) | |
| << bmih.biCompression; | |
| switch (bmih.biCompression) | |
| { | |
| case BI_RGB: | |
| wcout << L" [RGB]\n"; | |
| break; | |
| case BI_RLE8: | |
| wcout << L" [RLE8]\n"; | |
| break; | |
| case BI_RLE4: | |
| wcout << L" [RLE4]\n"; | |
| break; | |
| case BI_BITFIELDS: | |
| wcout << L" [BITFIELDS]\n"; | |
| break; | |
| case BI_JPEG: | |
| wcout << L" [JPEG]\n"; | |
| break; | |
| case BI_PNG: | |
| wcout << L" [PNG]\n"; | |
| break; | |
| default: | |
| wcout << L" [" << std::flush; | |
| std::cout.write((const char*)&bmih.biCompression, 4); | |
| std::cout << std::flush; | |
| wcout << L"]\n"; | |
| break; | |
| } | |
| } | |
| GraphUtil::IBaseFilterPtr App::AddDemuxFilter( | |
| IBaseFilter* pReader, | |
| const wchar_t* name) const | |
| { | |
| assert(bool(m_pGraph)); | |
| assert(pReader); | |
| if (GraphUtil::OutpinCount(pReader) > 1) //source, not reader | |
| return pReader; | |
| const IPinPtr pPin = GraphUtil::FindOutpin(pReader); | |
| assert(bool(pPin)); | |
| GraphUtil::IEnumMediaTypesPtr e; | |
| HRESULT hr = pPin->EnumMediaTypes(&e); | |
| if (hr != S_OK) | |
| { | |
| wcout << "Unable to enumerate source filter's media types.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return 0; | |
| } | |
| assert(bool(e)); | |
| for (;;) | |
| { | |
| AM_MEDIA_TYPE* pmt; | |
| hr = e->Next(1, &pmt, 0); | |
| if (hr != S_OK) | |
| { | |
| wcout << "No acceptable media types found" | |
| << " on source filter's outpins." | |
| << endl; | |
| return 0; | |
| } | |
| assert(pmt); | |
| const GUID g = pmt->majortype; | |
| MediaTypeUtil::Free(pmt); | |
| if (g == MEDIATYPE_Stream) | |
| return FindDemuxFilter(pReader, name); | |
| if (g == MEDIATYPE_Video) | |
| return pReader; | |
| if (g == MEDIATYPE_Audio) | |
| return pReader; | |
| if (g == MEDIATYPE_NULL) //assume stream | |
| return FindDemuxFilter(pReader, name); | |
| } | |
| } | |
| GraphUtil::IBaseFilterPtr App::FindDemuxFilter( | |
| IBaseFilter* pReader, | |
| const wchar_t* name) const | |
| { | |
| assert(bool(m_pGraph)); | |
| assert(pReader); | |
| const IPinPtr pOutputPin = GraphUtil::FindOutpin(pReader); | |
| assert(bool(pOutputPin)); | |
| IBaseFilterPtr f; | |
| HRESULT hr = f.CreateInstance(WebmTypes::CLSID_WebmSplit); | |
| if (FAILED(hr)) | |
| return EnumDemuxFilters(pOutputPin, name); | |
| assert(bool(f)); | |
| const IPinPtr pInputPin = GraphUtil::FindInpin(f); | |
| assert(bool(pInputPin)); | |
| hr = m_pGraph->AddFilter(f, name); | |
| assert(SUCCEEDED(hr)); | |
| hr = m_pGraph->ConnectDirect(pOutputPin, pInputPin, 0); | |
| if (SUCCEEDED(hr)) | |
| { | |
| assert(GraphUtil::OutpinCount(f) > 0); | |
| return f; | |
| } | |
| hr = m_pGraph->RemoveFilter(f); | |
| assert(SUCCEEDED(hr)); | |
| return EnumDemuxFilters(pOutputPin, name); | |
| } | |
| GraphUtil::IBaseFilterPtr | |
| App::EnumDemuxFilters( | |
| IPin* pOutputPin, | |
| const wchar_t* name) const | |
| { | |
| const GraphUtil::IFilterMapper2Ptr pMapper(CLSID_FilterMapper2); | |
| assert(bool(pMapper)); | |
| IEnumMonikerPtr e; | |
| enum { cInputTypes = 1 }; | |
| const GUID inputTypes[2 * cInputTypes] = | |
| { | |
| MEDIATYPE_Stream, MEDIASUBTYPE_NULL | |
| }; | |
| enum { cOutputTypes = 2 }; | |
| const GUID outputTypes[2 * cOutputTypes] = | |
| { | |
| MEDIATYPE_Video, MEDIASUBTYPE_NULL, | |
| MEDIATYPE_Audio, MEDIASUBTYPE_NULL | |
| }; | |
| HRESULT hr = pMapper->EnumMatchingFilters( | |
| &e, | |
| 0, //flags | |
| FALSE, //no, we don't require exact match | |
| MERIT_DO_NOT_USE + 1, | |
| TRUE, //input needed | |
| cInputTypes, | |
| inputTypes, | |
| 0, //input medium | |
| 0, //input category | |
| FALSE, //bRender | |
| FALSE, //no output needed | |
| cOutputTypes, | |
| outputTypes, | |
| 0, //output medium | |
| 0); //output category | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to enumerate filters" | |
| << " that can demux the source filter." | |
| << endl; | |
| return 0; | |
| } | |
| for (;;) | |
| { | |
| IMonikerPtr m; | |
| hr = e->Next(1, &m, 0); | |
| if (hr != S_OK) | |
| { | |
| wcout << "No filters found" | |
| << " that can demux the source filter." | |
| << endl; | |
| return 0; | |
| } | |
| IBaseFilterPtr f; | |
| hr = m->BindToObject(0, 0, __uuidof(IBaseFilter), (void**)&f); | |
| if (FAILED(hr)) | |
| continue; | |
| assert(bool(f)); | |
| const IPinPtr pInputPin = GraphUtil::FindInpin(f); | |
| if (!bool(pInputPin)) | |
| continue; | |
| hr = m_pGraph->AddFilter(f, name); | |
| assert(SUCCEEDED(hr)); | |
| hr = m_pGraph->ConnectDirect(pOutputPin, pInputPin, 0); | |
| if (FAILED(hr)) | |
| { | |
| hr = m_pGraph->RemoveFilter(f); | |
| assert(SUCCEEDED(hr)); | |
| continue; | |
| } | |
| IPinPtr pPin = GraphUtil::FindOutpinVideo(f); | |
| if (!bool(pPin)) | |
| pPin = GraphUtil::FindOutpinAudio(f); | |
| if (!bool(pPin)) | |
| { | |
| //TODO: do we need to disconnect here? | |
| hr = m_pGraph->RemoveFilter(f); | |
| assert(SUCCEEDED(hr)); | |
| continue; | |
| } | |
| return f; | |
| } | |
| } | |
| bool App::IsVPX(IPin* pPin) | |
| { | |
| assert(pPin); | |
| GraphUtil::IEnumMediaTypesPtr e; | |
| HRESULT hr = pPin->EnumMediaTypes(&e); | |
| if (FAILED(hr)) | |
| return false; //? | |
| for (;;) | |
| { | |
| AM_MEDIA_TYPE* pmt; | |
| hr = e->Next(1, &pmt, 0); | |
| if (hr != S_OK) | |
| return false; //? | |
| const GUID major = pmt->majortype; | |
| const GUID minor = pmt->subtype; | |
| MediaTypeUtil::Free(pmt); | |
| pmt = 0; | |
| if (major != MEDIATYPE_Video) | |
| continue; | |
| if (minor == WebmTypes::MEDIASUBTYPE_VP80) | |
| return true; | |
| if (minor == WebmTypes::MEDIASUBTYPE_VP90) | |
| return true; | |
| } | |
| } | |
| GUID App::GetSubtype(IPin* pPin) | |
| { | |
| assert(pPin); | |
| GraphUtil::IEnumMediaTypesPtr e; | |
| HRESULT hr = pPin->EnumMediaTypes(&e); | |
| if (FAILED(hr)) | |
| return GUID_NULL; | |
| for (;;) | |
| { | |
| AM_MEDIA_TYPE* pmt; | |
| hr = e->Next(1, &pmt, 0); | |
| if (hr != S_OK) | |
| return GUID_NULL; | |
| const GUID major = pmt->majortype; | |
| major; | |
| const GUID minor = pmt->subtype; | |
| MediaTypeUtil::Free(pmt); | |
| pmt = 0; | |
| if (minor != GUID_NULL) | |
| return minor; | |
| } | |
| } | |
| #if 0 | |
| bool App::ConnectVideo(IPin* pDemuxOutpin, IPin* pVP8Inpin) const | |
| { | |
| const GraphUtil::IGraphBuilderPtr pBuilder(m_pGraph); | |
| assert(bool(pBuilder)); | |
| HRESULT hr = pBuilder->Connect(pDemuxOutpin, pVP8Inpin); | |
| if (SUCCEEDED(hr)) | |
| return true; //connected | |
| hr = ConnectVideoConverter(pDemuxOutpin, pVP8Inpin); | |
| if (SUCCEEDED(hr)) | |
| return true; | |
| return false; | |
| } | |
| HRESULT App::ConnectVideoConverter(IPin* pDemuxOutpin, IPin* pVP8Inpin) const | |
| { | |
| typedef std::vector<GUID> guids_t; | |
| guids_t input_guids; | |
| { | |
| GraphUtil::IEnumMediaTypesPtr e; | |
| HRESULT hr = pDemuxOutpin->EnumMediaTypes(&e); | |
| if (FAILED(hr)) | |
| return hr; | |
| for (;;) | |
| { | |
| AM_MEDIA_TYPE* pmt; | |
| hr = e->Next(1, &pmt, 0); | |
| if (hr != S_OK) | |
| break; | |
| assert(pmt); | |
| assert(pmt->majortype == MEDIATYPE_Video); | |
| assert(pmt->subtype != MEDIASUBTYPE_NULL); //? | |
| input_guids.push_back(pmt->majortype); | |
| input_guids.push_back(pmt->subtype); | |
| MediaTypeUtil::Free(pmt); | |
| pmt = 0; | |
| } | |
| if (input_guids.empty()) //weird | |
| return E_FAIL; | |
| } | |
| const guids_t::size_type cInputTypes_ = input_guids.size() / 2; | |
| const DWORD cInputTypes = static_cast<DWORD>(cInputTypes_); | |
| const GUID* const inputTypes = &input_guids[0]; | |
| const GraphUtil::IFilterMapper2Ptr pMapper(CLSID_FilterMapper2); | |
| assert(bool(pMapper)); | |
| //TODO: for now, hard-code the VP8 input pin media types. | |
| //At some point we should query for that filter's | |
| //preferred input pin media types. | |
| enum { cOutputTypes = 4 }; | |
| const GUID outputTypes[2 * cOutputTypes] = | |
| { | |
| MEDIATYPE_Video, MEDIASUBTYPE_YV12, | |
| MEDIATYPE_Video, WebmTypes::MEDIASUBTYPE_I420, | |
| MEDIATYPE_Video, MEDIASUBTYPE_YUY2, | |
| MEDIATYPE_Video, MEDIASUBTYPE_YUYV | |
| }; | |
| IEnumMonikerPtr e; | |
| HRESULT hr = pMapper->EnumMatchingFilters( | |
| &e, | |
| 0, //flags (reserved -- must be 0) | |
| FALSE, //TRUE, //yes, require exact match //? | |
| MERIT_DO_NOT_USE, | |
| TRUE, //input needed | |
| cInputTypes, | |
| inputTypes, | |
| 0, //input medium | |
| 0, //input category | |
| FALSE, //bRender | |
| TRUE, //yes, output needed | |
| cOutputTypes, | |
| outputTypes, | |
| 0, //output medium | |
| 0); //output category | |
| if (FAILED(hr)) | |
| return hr; | |
| const GraphUtil::IGraphConfigPtr pConfig(m_pGraph); | |
| assert(bool(pConfig)); | |
| const GraphUtil::IGraphBuilderPtr pBuilder(m_pGraph); | |
| assert(bool(pBuilder)); | |
| for (;;) | |
| { | |
| IMonikerPtr m; | |
| hr = e->Next(1, &m, 0); | |
| if (hr != S_OK) | |
| return VFW_E_NOT_CONNECTED; | |
| assert(bool(m)); | |
| IBaseFilterPtr f; //video converter filter | |
| hr = m->BindToObject(0, 0, __uuidof(IBaseFilter), (void**)&f); | |
| if (FAILED(hr)) | |
| continue; | |
| assert(bool(f)); | |
| #if 0 | |
| hr = pConfig->AddFilterToCache(f); | |
| assert(SUCCEEDED(hr)); | |
| const HRESULT hrConnect = pBuilder->Connect( | |
| pDemuxOutpin, | |
| pMuxInpin); | |
| hr = pConfig->RemoveFilterFromCache(f); | |
| assert(SUCCEEDED(hr)); | |
| if (SUCCEEDED(hrConnect)) | |
| return S_OK; | |
| #else | |
| hr = m_pGraph->AddFilter(f, L"video converter"); | |
| assert(SUCCEEDED(hr)); | |
| const IPinPtr pConverterInpin = GraphUtil::FindInpin(f); | |
| assert(bool(pConverterInpin)); | |
| hr = m_pGraph->ConnectDirect(pDemuxOutpin, pConverterInpin, 0); | |
| if (FAILED(hr)) | |
| hr = pBuilder->Connect(pDemuxOutpin, pConverterInpin); | |
| if (FAILED(hr)) | |
| { | |
| hr = m_pGraph->RemoveFilter(f); | |
| assert(SUCCEEDED(hr)); | |
| continue; | |
| } | |
| const IPinPtr pConverterOutpin = GraphUtil::FindOutpin(f); | |
| assert(bool(pConverterOutpin)); | |
| hr = m_pGraph->ConnectDirect(pConverterOutpin, pVP8Inpin, 0); | |
| if (SUCCEEDED(hr)) | |
| return S_OK; | |
| hr = m_pGraph->RemoveFilter(f); | |
| assert(SUCCEEDED(hr)); | |
| #endif | |
| } //end for | |
| } | |
| #endif | |
| bool App::ConnectAudio(IPin* pDemuxOutpin, IPin* pMuxInpin) const | |
| { | |
| HRESULT hr; | |
| if (m_cmdline.GetOggToWebm() > 0) | |
| { | |
| AM_MEDIA_TYPE mt; | |
| memset(&mt, 0, sizeof mt); | |
| mt.majortype = MEDIATYPE_Audio; | |
| mt.subtype = VorbisTypes::MEDIASUBTYPE_Vorbis2_Xiph_Lacing; | |
| hr = m_pGraph->ConnectDirect(pDemuxOutpin, pMuxInpin, &mt); | |
| if (FAILED(hr)) | |
| { | |
| wcout << L"Unable to connect to muxer using Xiph lacing.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return false; | |
| } | |
| return true; | |
| } | |
| hr = m_pGraph->ConnectDirect(pDemuxOutpin, pMuxInpin, 0); | |
| if (SUCCEEDED(hr)) | |
| return true; //connected | |
| hr = TranscodeAudio(pDemuxOutpin, pMuxInpin); | |
| if (SUCCEEDED(hr)) | |
| return true; //connected | |
| #if 0 //TODO: do this too? | |
| const GraphUtil::IGraphBuilderPtr pBuilder(m_pGraph); | |
| assert(bool(pBuilder)); | |
| hr = pBuilder->Connect(pDemuxOutpin, pMuxInpin); | |
| if (SUCCEEDED(hr)) | |
| return true; | |
| hr = ConnectVorbisEncoder(pDemuxOutpin, pMuxInpin); | |
| if (SUCCEEDED(hr)) | |
| return true; //connected | |
| #endif | |
| wcout << "Unable to connect audio stream to muxer." << endl; | |
| return false; | |
| } | |
| HRESULT App::TranscodeAudio(IPin* pDemuxOutpin, IPin* pMuxInpin) const | |
| { | |
| GraphUtil::IEnumMediaTypesPtr e; | |
| HRESULT hr = pDemuxOutpin->EnumMediaTypes(&e); | |
| if (FAILED(hr)) | |
| return hr; | |
| for (;;) | |
| { | |
| AM_MEDIA_TYPE* pmt; | |
| hr = e->Next(1, &pmt, 0); | |
| if (hr != S_OK) | |
| return VFW_E_NO_ACCEPTABLE_TYPES; | |
| assert(pmt); | |
| if (pmt->subtype == MEDIASUBTYPE_PCM) | |
| hr = ConnectVorbisEncoder(pDemuxOutpin, pMuxInpin); | |
| else if (pmt->formattype == FORMAT_WaveFormatEx) //TODO: liberalize | |
| { | |
| hr = TranscodeAudio( | |
| *pmt, | |
| pDemuxOutpin, | |
| pMuxInpin, | |
| MERIT_NORMAL); | |
| if (FAILED(hr)) | |
| hr = TranscodeAudio( | |
| *pmt, | |
| pDemuxOutpin, | |
| pMuxInpin, | |
| MERIT_UNLIKELY); | |
| } | |
| else | |
| hr = E_FAIL; | |
| CoTaskMemFree(pmt->pbFormat); | |
| CoTaskMemFree(pmt); | |
| if (SUCCEEDED(hr)) | |
| return S_OK; //done | |
| } | |
| } | |
| HRESULT App::TranscodeAudio( | |
| const AM_MEDIA_TYPE& mt_demux, | |
| IPin* pDemuxOutpin, | |
| IPin* pMuxInpin, | |
| DWORD dwMerit) const | |
| { | |
| assert(mt_demux.formattype == FORMAT_WaveFormatEx); | |
| assert(mt_demux.pbFormat); | |
| assert(mt_demux.cbFormat >= sizeof(WAVEFORMATEX)); | |
| const WAVEFORMATEX& wfx_demux = (WAVEFORMATEX&)(*mt_demux.pbFormat); | |
| if (wfx_demux.wFormatTag == WAVE_FORMAT_PCM) //weird | |
| return ConnectVorbisEncoder(pDemuxOutpin, pMuxInpin); | |
| const GraphUtil::IFilterMapper2Ptr pMapper(CLSID_FilterMapper2); | |
| assert(bool(pMapper)); | |
| IEnumMonikerPtr e; | |
| enum { cInputTypes = 1 }; | |
| const GUID inputTypes[2 * cInputTypes] = | |
| { | |
| mt_demux.majortype, mt_demux.subtype | |
| }; | |
| #if 0 | |
| enum { cOutputTypes = 2 }; | |
| const GUID outputTypes[2 * cOutputTypes] = | |
| { | |
| MEDIATYPE_Audio, MEDIASUBTYPE_PCM, | |
| MEDIATYPE_Audio, MEDIASUBTYPE_IEEE_FLOAT | |
| }; | |
| #else | |
| enum { cOutputTypes = 1 }; | |
| const GUID outputTypes[2 * cOutputTypes] = | |
| { | |
| MEDIATYPE_Audio, MEDIASUBTYPE_PCM | |
| }; | |
| #endif | |
| HRESULT hr = pMapper->EnumMatchingFilters( | |
| &e, | |
| 0, //flags (reserved -- must be 0) | |
| TRUE, //yes, require exact match | |
| dwMerit, | |
| TRUE, //input needed | |
| cInputTypes, | |
| inputTypes, | |
| 0, //input medium | |
| 0, //input category | |
| FALSE, //bRender | |
| TRUE, //yes, output needed | |
| cOutputTypes, | |
| outputTypes, | |
| 0, //output medium | |
| 0); //output category | |
| if (FAILED(hr)) | |
| return hr; | |
| const GraphUtil::IGraphBuilderPtr pBuilder(m_pGraph); | |
| assert(bool(pBuilder)); | |
| for (;;) | |
| { | |
| IMonikerPtr m; | |
| hr = e->Next(1, &m, 0); | |
| if (hr != S_OK) | |
| return VFW_E_NOT_CONNECTED; | |
| assert(bool(m)); | |
| IBaseFilterPtr f; | |
| hr = m->BindToObject(0, 0, __uuidof(IBaseFilter), (void**)&f); | |
| if (FAILED(hr)) | |
| continue; | |
| assert(bool(f)); //we now have our audio decoder | |
| hr = m_pGraph->AddFilter(f, L"audio decoder"); | |
| assert(SUCCEEDED(hr)); | |
| const IPinPtr pDecoderInpin = GraphUtil::FindInpin(f); | |
| assert(bool(pDecoderInpin)); | |
| hr = pBuilder->Connect(pDemuxOutpin, pDecoderInpin); | |
| if (FAILED(hr)) | |
| { | |
| hr = m_pGraph->RemoveFilter(f); | |
| assert(SUCCEEDED(hr)); | |
| continue; | |
| } | |
| const IPinPtr pDecoderOutpin = GraphUtil::FindOutpin(f); | |
| assert(bool(pDecoderOutpin)); | |
| GraphUtil::IEnumMediaTypesPtr emt; | |
| hr = pDecoderOutpin->EnumMediaTypes(&emt); | |
| if (FAILED(hr)) | |
| { | |
| hr = m_pGraph->RemoveFilter(f); | |
| assert(SUCCEEDED(hr)); | |
| continue; | |
| } | |
| for (;;) | |
| { | |
| AM_MEDIA_TYPE* pmt; | |
| hr = emt->Next(1, &pmt, 0); | |
| if (hr != S_OK) | |
| break; | |
| assert(pmt); | |
| hr = ConnectVorbisEncoder( | |
| wfx_demux, //demux outpin | |
| *pmt, //decoder outpin | |
| pDecoderOutpin, | |
| pMuxInpin); | |
| CoTaskMemFree(pmt->pbFormat); | |
| CoTaskMemFree(pmt); | |
| if (hr == S_OK) | |
| return S_OK; //done | |
| } | |
| hr = m_pGraph->RemoveFilter(f); | |
| assert(SUCCEEDED(hr)); | |
| } | |
| } | |
| HRESULT App::ConnectVorbisEncoder( | |
| const WAVEFORMATEX& wfx_demux, | |
| const AM_MEDIA_TYPE& mt_decoder, | |
| IPin* pDecoderOutpin, //PCM | |
| IPin* pMuxInpin) const | |
| { | |
| if (mt_decoder.subtype != MEDIASUBTYPE_PCM) //weird | |
| return S_FALSE; | |
| if (mt_decoder.formattype != FORMAT_WaveFormatEx) //weird | |
| return S_FALSE; | |
| assert(mt_decoder.pbFormat); | |
| assert(mt_decoder.cbFormat >= sizeof(WAVEFORMATEX)); | |
| const WAVEFORMATEX& wfx_decoder = (WAVEFORMATEX&)*mt_decoder.pbFormat; | |
| if (wfx_decoder.nChannels != wfx_demux.nChannels) | |
| return S_FALSE; | |
| if (wfx_decoder.nSamplesPerSec != wfx_demux.nSamplesPerSec) | |
| return S_FALSE; | |
| return ConnectVorbisEncoder(pDecoderOutpin, pMuxInpin); | |
| } | |
| HRESULT App::ConnectVorbisEncoder(IPin* pDecoderOutpin, IPin* pMuxInpin) const | |
| { | |
| const GraphUtil::IFilterMapper2Ptr pMapper(CLSID_FilterMapper2); | |
| assert(bool(pMapper)); | |
| IEnumMonikerPtr e; | |
| enum { cInputTypes = 1 }; | |
| const GUID inputTypes[2 * cInputTypes] = | |
| { | |
| MEDIATYPE_Audio, MEDIASUBTYPE_PCM | |
| }; | |
| enum { cOutputTypes = 2 }; | |
| const GUID outputTypes[2 * cOutputTypes] = | |
| { | |
| MEDIATYPE_Audio, VorbisTypes::MEDIASUBTYPE_Vorbis2, | |
| MEDIATYPE_Audio, VorbisTypes::MEDIASUBTYPE_Vorbis | |
| }; | |
| HRESULT hr = pMapper->EnumMatchingFilters( | |
| &e, | |
| 0, //flags (reserved -- must be 0) | |
| TRUE, //yes, require exact match | |
| MERIT_DO_NOT_USE, | |
| TRUE, //input needed | |
| cInputTypes, | |
| inputTypes, | |
| 0, //input medium | |
| 0, //input category | |
| FALSE, //bRender | |
| TRUE, //yes, output needed | |
| cOutputTypes, | |
| outputTypes, | |
| 0, //output medium | |
| 0); //output category | |
| if (FAILED(hr)) | |
| return hr; | |
| const GraphUtil::IGraphConfigPtr pConfig(m_pGraph); | |
| assert(bool(pConfig)); | |
| const GraphUtil::IGraphBuilderPtr pBuilder(m_pGraph); | |
| assert(bool(pBuilder)); | |
| for (;;) | |
| { | |
| IMonikerPtr m; | |
| hr = e->Next(1, &m, 0); | |
| if (hr != S_OK) | |
| return VFW_E_NOT_CONNECTED; | |
| assert(bool(m)); | |
| IBaseFilterPtr f; | |
| hr = m->BindToObject(0, 0, __uuidof(IBaseFilter), (void**)&f); | |
| if (FAILED(hr)) | |
| continue; | |
| assert(bool(f)); | |
| hr = m_pGraph->AddFilter(f, L"vorbis encoder"); | |
| assert(SUCCEEDED(hr)); | |
| const IPinPtr pEncoderInpin = GraphUtil::FindInpin(f); | |
| assert(bool(pEncoderInpin)); | |
| hr = pBuilder->ConnectDirect(pDecoderOutpin, pEncoderInpin, 0); | |
| if (FAILED(hr)) | |
| { | |
| hr = m_pGraph->RemoveFilter(f); | |
| assert(SUCCEEDED(hr)); | |
| continue; | |
| } | |
| const IPinPtr pEncoderOutpin = GraphUtil::FindOutpin(f); | |
| assert(bool(pEncoderOutpin)); | |
| hr = m_pGraph->ConnectDirect(pEncoderOutpin, pMuxInpin, 0); | |
| if (SUCCEEDED(hr)) | |
| return S_OK; | |
| hr = m_pGraph->RemoveFilter(f); | |
| assert(SUCCEEDED(hr)); | |
| } | |
| } | |
| void App::DumpPreferredMediaTypes( | |
| IPin* pPin, | |
| const wchar_t* id, | |
| void (*pfn)(const AM_MEDIA_TYPE&)) | |
| { | |
| GraphUtil::IEnumMediaTypesPtr e; | |
| HRESULT hr = pPin->EnumMediaTypes(&e); | |
| if (FAILED(hr)) | |
| { | |
| wcout << L"Unable to enumerate preferred media types for " | |
| << id | |
| << "." | |
| << endl; | |
| return; | |
| } | |
| wcout << "Preferred media types for " << id << ":\n"; | |
| for (;;) | |
| { | |
| AM_MEDIA_TYPE* pmt; | |
| hr = e->Next(1, &pmt, 0); | |
| if (hr != S_OK) | |
| break; | |
| (*pfn)(*pmt); | |
| wcout << L'\n'; | |
| MediaTypeUtil::Free(pmt); | |
| } | |
| wcout << endl; | |
| } | |
| void App::DumpConnectionMediaType( | |
| IPin* pPin, | |
| const wchar_t* id, | |
| void (*pfn)(const AM_MEDIA_TYPE&)) | |
| { | |
| AM_MEDIA_TYPE mt; | |
| const HRESULT hr = pPin->ConnectionMediaType(&mt); | |
| if (FAILED(hr)) | |
| { | |
| wcout << L"No connection media type for " << id << "." << endl; | |
| return; | |
| } | |
| wcout << "Media type for " << id << ":\n"; | |
| (*pfn)(mt); | |
| wcout << endl; | |
| MediaTypeUtil::Destroy(mt); | |
| } | |
| HRESULT App::SetVP8Options( | |
| IVP8Encoder* pVP8, | |
| const AM_MEDIA_TYPE* pmt) const | |
| { | |
| assert(pVP8); | |
| const int deadline = m_cmdline.GetDeadline(); | |
| if (deadline >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetDeadline(deadline); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder deadline.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int target_bitrate = m_cmdline.GetTargetBitrate(); | |
| if (target_bitrate >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetTargetBitrate(target_bitrate); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder target bitrate.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int min_quantizer = m_cmdline.GetMinQuantizer(); | |
| if (min_quantizer >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetMinQuantizer(min_quantizer); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder min quantizer.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int max_quantizer = m_cmdline.GetMaxQuantizer(); | |
| if (max_quantizer >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetMaxQuantizer(max_quantizer); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder max quantizer.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int undershoot_pct = m_cmdline.GetUndershootPct(); | |
| if (undershoot_pct >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetUndershootPct(undershoot_pct); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder undershoot pct.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int overshoot_pct = m_cmdline.GetOvershootPct(); | |
| if (overshoot_pct >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetOvershootPct(overshoot_pct); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder overshoot pct.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int decoder_buffer_size = m_cmdline.GetDecoderBufferSize(); | |
| if (decoder_buffer_size >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetDecoderBufferSize(decoder_buffer_size); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder decoder buffer size.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int decoder_buffer_initial_size = | |
| m_cmdline.GetDecoderBufferInitialSize(); | |
| if (decoder_buffer_initial_size >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetDecoderBufferInitialSize( | |
| decoder_buffer_initial_size); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder" | |
| << " decoder buffer initial size.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int decoder_buffer_optimal_size = | |
| m_cmdline.GetDecoderBufferOptimalSize(); | |
| if (decoder_buffer_optimal_size >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetDecoderBufferOptimalSize( | |
| decoder_buffer_optimal_size); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder" | |
| << " decoder buffer optimal size.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const double keyframe_frequency = m_cmdline.GetKeyframeFrequency(); | |
| if (keyframe_frequency >= 0) | |
| { | |
| if (pmt == 0) | |
| { | |
| wcout << L"Connection has no media type when" | |
| << L" keyframe-frequency switch specified." | |
| << endl; | |
| return E_FAIL; | |
| } | |
| const double framerate = GetFramerate(*pmt); | |
| if (framerate <= 0) | |
| { | |
| wcout << L"Connection has no framerate when" | |
| << L" keyframe-frequency switch specified." | |
| << endl; | |
| return E_FAIL; | |
| } | |
| const double interval__ = framerate * keyframe_frequency; | |
| const double interval_ = ceil(interval__); | |
| const int interval = static_cast<int>(interval_); | |
| HRESULT hr = pVP8->SetKeyframeMode(kKeyframeModeAuto); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder keyframe mode (to auto).\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| hr = pVP8->SetKeyframeMinInterval(interval); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder keyframe min interval.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| hr = pVP8->SetKeyframeMaxInterval(interval); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder keyframe max interval.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| else | |
| { | |
| const int keyframe_mode = m_cmdline.GetKeyframeMode(); | |
| if (keyframe_mode >= kKeyframeModeDefault) | |
| { | |
| const VP8KeyframeMode m = | |
| static_cast<VP8KeyframeMode>(keyframe_mode); | |
| const HRESULT hr = pVP8->SetKeyframeMode(m); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder keyframe mode.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int keyframe_min_interval = m_cmdline.GetKeyframeMinInterval(); | |
| if (keyframe_min_interval >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetKeyframeMinInterval( | |
| keyframe_min_interval); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder keyframe min interval.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int keyframe_max_interval = m_cmdline.GetKeyframeMaxInterval(); | |
| if (keyframe_max_interval >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetKeyframeMaxInterval( | |
| keyframe_max_interval); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder keyframe max interval.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| } | |
| const int thread_count = m_cmdline.GetThreadCount(); | |
| if (thread_count >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetThreadCount(thread_count); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder thread count.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int error_resilient = m_cmdline.GetErrorResilient(); | |
| if (error_resilient >= 0) | |
| { | |
| const boolean b = static_cast<boolean>(error_resilient); | |
| const HRESULT hr = pVP8->SetErrorResilient(b); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder error resilient value.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int end_usage = m_cmdline.GetEndUsage(); | |
| if (end_usage >= 0) | |
| { | |
| const VP8EndUsage val = static_cast<VP8EndUsage>(end_usage); | |
| const HRESULT hr = pVP8->SetEndUsage(val); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder end usage.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int lag_in_frames = m_cmdline.GetLagInFrames(); | |
| if (lag_in_frames >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetLagInFrames(lag_in_frames); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder lag in frames.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int token_partitions = m_cmdline.GetTokenPartitions(); | |
| if (token_partitions >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetTokenPartitions(token_partitions); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder token partitions.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int auto_alt_ref = m_cmdline.GetAutoAltRef(); | |
| if (auto_alt_ref >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetAutoAltRef(auto_alt_ref); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder auto alt ref.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int arnr_maxframes = m_cmdline.GetARNRMaxFrames(); | |
| if (arnr_maxframes >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetARNRMaxFrames(arnr_maxframes); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder arnr maxframes.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int arnr_strength = m_cmdline.GetARNRStrength(); | |
| if (arnr_strength >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetARNRStrength(arnr_strength); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder arnr strength.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int arnr_type = m_cmdline.GetARNRType(); | |
| if (arnr_type >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetARNRType(arnr_type); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder arnr type.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int dropframe_thresh = m_cmdline.GetDropframeThreshold(); | |
| if (dropframe_thresh >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetDropframeThreshold(dropframe_thresh); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder dropframe threshold.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int resize_allowed = m_cmdline.GetResizeAllowed(); | |
| if (resize_allowed >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetResizeAllowed(resize_allowed); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder resize allowed.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int resize_up_thresh = m_cmdline.GetResizeUpThreshold(); | |
| if (resize_up_thresh >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetResizeUpThreshold(resize_up_thresh); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder resize up threshold.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int resize_down_thresh = m_cmdline.GetResizeDownThreshold(); | |
| if (resize_down_thresh >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetResizeDownThreshold(resize_down_thresh); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder resize down threshold.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int two_pass_vbr_bias_pct = m_cmdline.GetTwoPassVbrBiasPct(); | |
| if (two_pass_vbr_bias_pct >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetTwoPassVbrBiasPct(two_pass_vbr_bias_pct); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder two-pass VBR bias pct.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int two_pass_vbr_minsection_pct = | |
| m_cmdline.GetTwoPassVbrMinsectionPct(); | |
| if (two_pass_vbr_minsection_pct >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetTwoPassVbrMinsectionPct( | |
| two_pass_vbr_minsection_pct); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder" | |
| << " two-pass VBR minsection pct.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int two_pass_vbr_maxsection_pct = | |
| m_cmdline.GetTwoPassVbrMaxsectionPct(); | |
| if (two_pass_vbr_maxsection_pct >= 0) | |
| { | |
| const HRESULT hr = pVP8->SetTwoPassVbrMaxsectionPct( | |
| two_pass_vbr_maxsection_pct); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder" | |
| << " two-pass VBR maxsection pct.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| const int cpu_used = m_cmdline.GetCPUUsed(); | |
| if (cpu_used >= -16) | |
| { | |
| const HRESULT hr = pVP8->SetCPUUsed(cpu_used); | |
| if (FAILED(hr)) | |
| { | |
| wcout << "Unable to set VP8 encoder CPU used.\n" | |
| << hrtext(hr) | |
| << L" (0x" << hex << hr << dec << L")" | |
| << endl; | |
| return hr; | |
| } | |
| } | |
| return S_OK; | |
| } | |
| const wchar_t* App::GetStatsFileName() | |
| { | |
| wstring path = CmdLine::GetPath(m_cmdline.GetOutputFileName()); | |
| const wstring::size_type pos = path.rfind(L'.'); | |
| if (pos == wstring::npos) | |
| path.append(L"-VP8STATS.DAT"); | |
| else | |
| path.replace(pos, path.length(), L"-VP8STATS.DAT"); | |
| m_stats_filename = path; | |
| return m_stats_filename.c_str(); | |
| } |