summaryrefslogtreecommitdiffstats
path: root/dom/media/directshow/DirectShowUtils.cpp
blob: b2afa7528c9801722ee519187b27fd19beffcc7e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "DirectShowUtils.h"
#include "dmodshow.h"
#include "wmcodecdsp.h"
#include "dmoreg.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/RefPtr.h"
#include "nsPrintfCString.h"

#define WARN(...) NS_WARNING(nsPrintfCString(__VA_ARGS__).get())

namespace mozilla {

// Create a table which maps GUIDs to a string representation of the GUID.
// This is useful for debugging purposes, for logging the GUIDs of media types.
// This is only available when logging is enabled, i.e. not in release builds.
struct GuidToName {
  const char* name;
  const GUID guid;
};

#pragma push_macro("OUR_GUID_ENTRY")
#undef OUR_GUID_ENTRY
#define OUR_GUID_ENTRY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
  { #name, {l, w1, w2, {b1, b2, b3, b4, b5, b6, b7, b8}} },

static const GuidToName GuidToNameTable[] = {
#include <uuids.h>
};

#pragma pop_macro("OUR_GUID_ENTRY")

const char*
GetDirectShowGuidName(const GUID& aGuid)
{
  const size_t len = ArrayLength(GuidToNameTable);
  for (unsigned i = 0; i < len; i++) {
    if (IsEqualGUID(aGuid, GuidToNameTable[i].guid)) {
      return GuidToNameTable[i].name;
    }
  }
  return "Unknown";
}

void
RemoveGraphFromRunningObjectTable(DWORD aRotRegister)
{
  RefPtr<IRunningObjectTable> runningObjectTable;
  if (SUCCEEDED(GetRunningObjectTable(0, getter_AddRefs(runningObjectTable)))) {
    runningObjectTable->Revoke(aRotRegister);
  }
}

HRESULT
AddGraphToRunningObjectTable(IUnknown *aUnkGraph, DWORD *aOutRotRegister)
{
  HRESULT hr;

  RefPtr<IMoniker> moniker;
  RefPtr<IRunningObjectTable> runningObjectTable;

  hr = GetRunningObjectTable(0, getter_AddRefs(runningObjectTable));
  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);

  const size_t STRING_LENGTH = 256;
  WCHAR wsz[STRING_LENGTH];

  StringCchPrintfW(wsz,
                   STRING_LENGTH,
                   L"FilterGraph %08x pid %08x",
                   (DWORD_PTR)aUnkGraph,
                   GetCurrentProcessId());

  hr = CreateItemMoniker(L"!", wsz, getter_AddRefs(moniker));
  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);

  hr = runningObjectTable->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE,
                                    aUnkGraph,
                                    moniker,
                                    aOutRotRegister);
  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);

  return S_OK;
}

const char*
GetGraphNotifyString(long evCode)
{
#define CASE(x) case x: return #x
  switch(evCode) {
    CASE(EC_ACTIVATE); // A video window is being activated or deactivated.
    CASE(EC_BANDWIDTHCHANGE); // Not supported.
    CASE(EC_BUFFERING_DATA); // The graph is buffering data, or has stopped buffering data.
    CASE(EC_BUILT); // Send by the Video Control when a graph has been built. Not forwarded to applications.
    CASE(EC_CLOCK_CHANGED); // The reference clock has changed.
    CASE(EC_CLOCK_UNSET); // The clock provider was disconnected.
    CASE(EC_CODECAPI_EVENT); // Sent by an encoder to signal an encoding event.
    CASE(EC_COMPLETE); // All data from a particular stream has been rendered.
    CASE(EC_CONTENTPROPERTY_CHANGED); // Not supported.
    CASE(EC_DEVICE_LOST); // A Plug and Play device was removed or has become available again.
    CASE(EC_DISPLAY_CHANGED); // The display mode has changed.
    CASE(EC_END_OF_SEGMENT); // The end of a segment has been reached.
    CASE(EC_EOS_SOON); // Not supported.
    CASE(EC_ERROR_STILLPLAYING); // An asynchronous command to run the graph has failed.
    CASE(EC_ERRORABORT); // An operation was aborted because of an error.
    CASE(EC_ERRORABORTEX); // An operation was aborted because of an error.
    CASE(EC_EXTDEVICE_MODE_CHANGE); // Not supported.
    CASE(EC_FILE_CLOSED); // The source file was closed because of an unexpected event.
    CASE(EC_FULLSCREEN_LOST); // The video renderer is switching out of full-screen mode.
    CASE(EC_GRAPH_CHANGED); // The filter graph has changed.
    CASE(EC_LENGTH_CHANGED); // The length of a source has changed.
    CASE(EC_LOADSTATUS); // Notifies the application of progress when opening a network file.
    CASE(EC_MARKER_HIT); // Not supported.
    CASE(EC_NEED_RESTART); // A filter is requesting that the graph be restarted.
    CASE(EC_NEW_PIN); // Not supported.
    CASE(EC_NOTIFY_WINDOW); // Notifies a filter of the video renderer's window.
    CASE(EC_OLE_EVENT); // A filter is passing a text string to the application.
    CASE(EC_OPENING_FILE); // The graph is opening a file, or has finished opening a file.
    CASE(EC_PALETTE_CHANGED); // The video palette has changed.
    CASE(EC_PAUSED); // A pause request has completed.
    CASE(EC_PLEASE_REOPEN); // The source file has changed.
    CASE(EC_PREPROCESS_COMPLETE); // Sent by the WM ASF Writer filter when it completes the pre-processing for multipass encoding.
    CASE(EC_PROCESSING_LATENCY); // Indicates the amount of time that a component is taking to process each sample.
    CASE(EC_QUALITY_CHANGE); // The graph is dropping samples, for quality control.
    //CASE(EC_RENDER_FINISHED); // Not supported.
    CASE(EC_REPAINT); // A video renderer requires a repaint.
    CASE(EC_SAMPLE_LATENCY); // Specifies how far behind schedule a component is for processing samples.
    //CASE(EC_SAMPLE_NEEDED); // Requests a new input sample from the Enhanced Video Renderer (EVR) filter.
    CASE(EC_SCRUB_TIME); // Specifies the time stamp for the most recent frame step.
    CASE(EC_SEGMENT_STARTED); // A new segment has started.
    CASE(EC_SHUTTING_DOWN); // The filter graph is shutting down, prior to being destroyed.
    CASE(EC_SNDDEV_IN_ERROR); // A device error has occurred in an audio capture filter.
    CASE(EC_SNDDEV_OUT_ERROR); // A device error has occurred in an audio renderer filter.
    CASE(EC_STARVATION); // A filter is not receiving enough data.
    CASE(EC_STATE_CHANGE); // The filter graph has changed state.
    CASE(EC_STATUS); // Contains two arbitrary status strings.
    CASE(EC_STEP_COMPLETE); // A filter performing frame stepping has stepped the specified number of frames.
    CASE(EC_STREAM_CONTROL_STARTED); // A stream-control start command has taken effect.
    CASE(EC_STREAM_CONTROL_STOPPED); // A stream-control stop command has taken effect.
    CASE(EC_STREAM_ERROR_STILLPLAYING); // An error has occurred in a stream. The stream is still playing.
    CASE(EC_STREAM_ERROR_STOPPED); // A stream has stopped because of an error.
    CASE(EC_TIMECODE_AVAILABLE); // Not supported.
    CASE(EC_UNBUILT); // Send by the Video Control when a graph has been torn down. Not forwarded to applications.
    CASE(EC_USERABORT); // The user has terminated playback.
    CASE(EC_VIDEO_SIZE_CHANGED); // The native video size has changed.
    CASE(EC_VIDEOFRAMEREADY); // A video frame is ready for display.
    CASE(EC_VMR_RECONNECTION_FAILED); // Sent by the VMR-7 and the VMR-9 when it was unable to accept a dynamic format change request from the upstream decoder.
    CASE(EC_VMR_RENDERDEVICE_SET); // Sent when the VMR has selected its rendering mechanism.
    CASE(EC_VMR_SURFACE_FLIPPED); // Sent when the VMR-7's allocator presenter has called the DirectDraw Flip method on the surface being presented.
    CASE(EC_WINDOW_DESTROYED); // The video renderer was destroyed or removed from the graph.
    CASE(EC_WMT_EVENT); // Sent by the WM ASF Reader filter when it reads ASF files protected by digital rights management (DRM).
    CASE(EC_WMT_INDEX_EVENT); // Sent when an application uses the WM ASF Writer to index Windows Media Video files.
    CASE(S_OK); // Success.
    CASE(VFW_S_AUDIO_NOT_RENDERED); // Partial success; the audio was not rendered.
    CASE(VFW_S_DUPLICATE_NAME); // Success; the Filter Graph Manager modified a filter name to avoid duplication.
    CASE(VFW_S_PARTIAL_RENDER); // Partial success; some of the streams in this movie are in an unsupported format.
    CASE(VFW_S_VIDEO_NOT_RENDERED); // Partial success; the video was not rendered.
    CASE(E_ABORT); // Operation aborted.
    CASE(E_OUTOFMEMORY); // Insufficient memory.
    CASE(E_POINTER); // Null pointer argument.
    CASE(VFW_E_CANNOT_CONNECT); // No combination of intermediate filters could be found to make the connection.
    CASE(VFW_E_CANNOT_RENDER); // No combination of filters could be found to render the stream.
    CASE(VFW_E_NO_ACCEPTABLE_TYPES); // There is no common media type between these pins.
    CASE(VFW_E_NOT_IN_GRAPH);

    default:
      return "Unknown Code";
  };
#undef CASE
}

HRESULT
CreateAndAddFilter(IGraphBuilder* aGraph,
                   REFGUID aFilterClsId,
                   LPCWSTR aFilterName,
                   IBaseFilter **aOutFilter)
{
  NS_ENSURE_TRUE(aGraph, E_POINTER);
  NS_ENSURE_TRUE(aOutFilter, E_POINTER);
  HRESULT hr;

  RefPtr<IBaseFilter> filter;
  hr = CoCreateInstance(aFilterClsId,
                        nullptr,
                        CLSCTX_INPROC_SERVER,
                        IID_IBaseFilter,
                        getter_AddRefs(filter));
  if (FAILED(hr)) {
    // Object probably not available on this system.
    WARN("CoCreateInstance failed, hr=%x", hr);
    return hr;
  }

  hr = aGraph->AddFilter(filter, aFilterName);
  if (FAILED(hr)) {
    WARN("AddFilter failed, hr=%x", hr);
    return hr;
  }

  filter.forget(aOutFilter);

  return S_OK;
}

HRESULT
CreateMP3DMOWrapperFilter(IBaseFilter **aOutFilter)
{
  NS_ENSURE_TRUE(aOutFilter, E_POINTER);
  HRESULT hr;

  // Create the wrapper filter.
  RefPtr<IBaseFilter> filter;
  hr = CoCreateInstance(CLSID_DMOWrapperFilter,
                        nullptr,
                        CLSCTX_INPROC_SERVER,
                        IID_IBaseFilter,
                        getter_AddRefs(filter));
  if (FAILED(hr)) {
    WARN("CoCreateInstance failed, hr=%x", hr);
    return hr;
  }

  // Query for IDMOWrapperFilter.
  RefPtr<IDMOWrapperFilter> dmoWrapper;
  hr = filter->QueryInterface(IID_IDMOWrapperFilter,
                              getter_AddRefs(dmoWrapper));
  if (FAILED(hr)) {
    WARN("QueryInterface failed, hr=%x", hr);
    return hr;
  }

  hr = dmoWrapper->Init(CLSID_CMP3DecMediaObject, DMOCATEGORY_AUDIO_DECODER);
  if (FAILED(hr)) {
    // Can't instantiate MP3 DMO. It doesn't exist on Windows XP, we're
    // probably hitting that. Don't log warning to console, this is an
    // expected error.
    WARN("dmoWrapper Init failed, hr=%x", hr);
    return hr;
  }

  filter.forget(aOutFilter);

  return S_OK;
}

HRESULT
AddMP3DMOWrapperFilter(IGraphBuilder* aGraph,
                       IBaseFilter **aOutFilter)
{
  NS_ENSURE_TRUE(aGraph, E_POINTER);
  NS_ENSURE_TRUE(aOutFilter, E_POINTER);
  HRESULT hr;

  // Create the wrapper filter.
  RefPtr<IBaseFilter> filter;
  hr = CreateMP3DMOWrapperFilter(getter_AddRefs(filter));
  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);

  // Add the wrapper filter to graph.
  hr = aGraph->AddFilter(filter, L"MP3 Decoder DMO");
  if (FAILED(hr)) {
    WARN("AddFilter failed, hr=%x", hr);
    return hr;
  }

  filter.forget(aOutFilter);

  return S_OK;
}

bool
CanDecodeMP3UsingDirectShow()
{
  RefPtr<IBaseFilter> filter;

  // Can we create the MP3 demuxer filter?
  if (FAILED(CoCreateInstance(CLSID_MPEG1Splitter,
                              nullptr,
                              CLSCTX_INPROC_SERVER,
                              IID_IBaseFilter,
                              getter_AddRefs(filter)))) {
    return false;
  }

  // Can we create either the WinXP MP3 decoder filter or the MP3 DMO decoder?
  if (FAILED(CoCreateInstance(DirectShowReader::CLSID_MPEG_LAYER_3_DECODER_FILTER,
                              nullptr,
                              CLSCTX_INPROC_SERVER,
                              IID_IBaseFilter,
                              getter_AddRefs(filter))) &&
      FAILED(CreateMP3DMOWrapperFilter(getter_AddRefs(filter)))) {
    return false;
  }

  // Else, we can create all of the components we need. Assume
  // DirectShow is going to work...
  return true;
}

// Match a pin by pin direction and connection state.
HRESULT
MatchUnconnectedPin(IPin* aPin,
                    PIN_DIRECTION aPinDir,
                    bool *aOutMatches)
{
  NS_ENSURE_TRUE(aPin, E_POINTER);
  NS_ENSURE_TRUE(aOutMatches, E_POINTER);

  // Ensure the pin is unconnected.
  RefPtr<IPin> peer;
  HRESULT hr = aPin->ConnectedTo(getter_AddRefs(peer));
  if (hr != VFW_E_NOT_CONNECTED) {
    *aOutMatches = false;
    return hr;
  }

  // Ensure the pin is of the specified direction.
  PIN_DIRECTION pinDir;
  hr = aPin->QueryDirection(&pinDir);
  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);

  *aOutMatches = (pinDir == aPinDir);
  return S_OK;
}

// Return the first unconnected input pin or output pin.
already_AddRefed<IPin>
GetUnconnectedPin(IBaseFilter* aFilter, PIN_DIRECTION aPinDir)
{
  RefPtr<IEnumPins> enumPins;

  HRESULT hr = aFilter->EnumPins(getter_AddRefs(enumPins));
  NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr);

  // Test each pin to see if it matches the direction we're looking for.
  RefPtr<IPin> pin;
  while (S_OK == enumPins->Next(1, getter_AddRefs(pin), nullptr)) {
    bool matches = FALSE;
    if (SUCCEEDED(MatchUnconnectedPin(pin, aPinDir, &matches)) &&
        matches) {
      return pin.forget();
    }
  }

  return nullptr;
}

HRESULT
ConnectFilters(IGraphBuilder* aGraph,
               IBaseFilter* aOutputFilter,
               IBaseFilter* aInputFilter)
{
  RefPtr<IPin> output = GetUnconnectedPin(aOutputFilter, PINDIR_OUTPUT);
  NS_ENSURE_TRUE(output, E_FAIL);

  RefPtr<IPin> input = GetUnconnectedPin(aInputFilter, PINDIR_INPUT);
  NS_ENSURE_TRUE(output, E_FAIL);

  return aGraph->Connect(output, input);
}

} // namespace mozilla

// avoid redefined macro in unified build
#undef WARN