content/media/directshow/DirectShowReader.cpp

branch
TOR_BUG_9701
changeset 8
97036ab72558
equal deleted inserted replaced
-1:000000000000 0:534a97b6d6fb
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "DirectShowReader.h"
8 #include "MediaDecoderReader.h"
9 #include "mozilla/RefPtr.h"
10 #include "dshow.h"
11 #include "AudioSinkFilter.h"
12 #include "SourceFilter.h"
13 #include "DirectShowUtils.h"
14 #include "SampleSink.h"
15 #include "MediaResource.h"
16 #include "VideoUtils.h"
17
18 namespace mozilla {
19
20
21 #ifdef PR_LOGGING
22
23 PRLogModuleInfo*
24 GetDirectShowLog() {
25 static PRLogModuleInfo* log = nullptr;
26 if (!log) {
27 log = PR_NewLogModule("DirectShowDecoder");
28 }
29 return log;
30 }
31
32 #define LOG(...) PR_LOG(GetDirectShowLog(), PR_LOG_DEBUG, (__VA_ARGS__))
33
34 #else
35 #define LOG(...)
36 #endif
37
38 DirectShowReader::DirectShowReader(AbstractMediaDecoder* aDecoder)
39 : MediaDecoderReader(aDecoder),
40 mMP3FrameParser(aDecoder->GetResource()->GetLength()),
41 #ifdef DEBUG
42 mRotRegister(0),
43 #endif
44 mNumChannels(0),
45 mAudioRate(0),
46 mBytesPerSample(0),
47 mDuration(0)
48 {
49 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
50 MOZ_COUNT_CTOR(DirectShowReader);
51 }
52
53 DirectShowReader::~DirectShowReader()
54 {
55 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
56 MOZ_COUNT_DTOR(DirectShowReader);
57 #ifdef DEBUG
58 if (mRotRegister) {
59 RemoveGraphFromRunningObjectTable(mRotRegister);
60 }
61 #endif
62 }
63
64 nsresult
65 DirectShowReader::Init(MediaDecoderReader* aCloneDonor)
66 {
67 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
68 return NS_OK;
69 }
70
71 // Try to parse the MP3 stream to make sure this is indeed an MP3, get the
72 // estimated duration of the stream, and find the offset of the actual MP3
73 // frames in the stream, as DirectShow doesn't like large ID3 sections.
74 static nsresult
75 ParseMP3Headers(MP3FrameParser *aParser, MediaResource *aResource)
76 {
77 const uint32_t MAX_READ_SIZE = 4096;
78
79 uint64_t offset = 0;
80 while (aParser->NeedsData() && !aParser->ParsedHeaders()) {
81 uint32_t bytesRead;
82 char buffer[MAX_READ_SIZE];
83 nsresult rv = aResource->ReadAt(offset, buffer,
84 MAX_READ_SIZE, &bytesRead);
85 NS_ENSURE_SUCCESS(rv, rv);
86
87 if (!bytesRead) {
88 // End of stream.
89 return NS_ERROR_FAILURE;
90 }
91
92 aParser->Parse(buffer, bytesRead, offset);
93 offset += bytesRead;
94 }
95
96 return aParser->IsMP3() ? NS_OK : NS_ERROR_FAILURE;
97 }
98
99 // Windows XP's MP3 decoder filter. This is available on XP only, on Vista
100 // and later we can use the DMO Wrapper filter and MP3 decoder DMO.
101 static const GUID CLSID_MPEG_LAYER_3_DECODER_FILTER =
102 { 0x38BE3000, 0xDBF4, 0x11D0, 0x86, 0x0E, 0x00, 0xA0, 0x24, 0xCF, 0xEF, 0x6D };
103
104 nsresult
105 DirectShowReader::ReadMetadata(MediaInfo* aInfo,
106 MetadataTags** aTags)
107 {
108 MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
109 HRESULT hr;
110 nsresult rv;
111
112 // Create the filter graph, reference it by the GraphBuilder interface,
113 // to make graph building more convenient.
114 hr = CoCreateInstance(CLSID_FilterGraph,
115 nullptr,
116 CLSCTX_INPROC_SERVER,
117 IID_IGraphBuilder,
118 reinterpret_cast<void**>(static_cast<IGraphBuilder**>(byRef(mGraph))));
119 NS_ENSURE_TRUE(SUCCEEDED(hr) && mGraph, NS_ERROR_FAILURE);
120
121 rv = ParseMP3Headers(&mMP3FrameParser, mDecoder->GetResource());
122 NS_ENSURE_SUCCESS(rv, rv);
123
124 #ifdef DEBUG
125 // Add the graph to the Running Object Table so that we can connect
126 // to this graph with GraphEdit/GraphStudio. Note: on Vista and up you must
127 // also regsvr32 proppage.dll from the Windows SDK.
128 // See: http://msdn.microsoft.com/en-us/library/ms787252(VS.85).aspx
129 hr = AddGraphToRunningObjectTable(mGraph, &mRotRegister);
130 NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
131 #endif
132
133 // Extract the interface pointers we'll need from the filter graph.
134 hr = mGraph->QueryInterface(static_cast<IMediaControl**>(byRef(mControl)));
135 NS_ENSURE_TRUE(SUCCEEDED(hr) && mControl, NS_ERROR_FAILURE);
136
137 hr = mGraph->QueryInterface(static_cast<IMediaSeeking**>(byRef(mMediaSeeking)));
138 NS_ENSURE_TRUE(SUCCEEDED(hr) && mMediaSeeking, NS_ERROR_FAILURE);
139
140 // Build the graph. Create the filters we need, and connect them. We
141 // build the entire graph ourselves to prevent other decoders installed
142 // on the system being created and used.
143
144 // Our source filters, wraps the MediaResource.
145 mSourceFilter = new SourceFilter(MEDIATYPE_Stream, MEDIASUBTYPE_MPEG1Audio);
146 NS_ENSURE_TRUE(mSourceFilter, NS_ERROR_FAILURE);
147
148 rv = mSourceFilter->Init(mDecoder->GetResource(), mMP3FrameParser.GetMP3Offset());
149 NS_ENSURE_SUCCESS(rv, rv);
150
151 hr = mGraph->AddFilter(mSourceFilter, L"MozillaDirectShowSource");
152 NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
153
154 // The MPEG demuxer.
155 RefPtr<IBaseFilter> demuxer;
156 hr = CreateAndAddFilter(mGraph,
157 CLSID_MPEG1Splitter,
158 L"MPEG1Splitter",
159 byRef(demuxer));
160 NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
161
162 // Platform MP3 decoder.
163 RefPtr<IBaseFilter> decoder;
164 // Firstly try to create the MP3 decoder filter that ships with WinXP
165 // directly. This filter doesn't normally exist on later versions of
166 // Windows.
167 hr = CreateAndAddFilter(mGraph,
168 CLSID_MPEG_LAYER_3_DECODER_FILTER,
169 L"MPEG Layer 3 Decoder",
170 byRef(decoder));
171 if (FAILED(hr)) {
172 // Failed to create MP3 decoder filter. Try to instantiate
173 // the MP3 decoder DMO.
174 hr = AddMP3DMOWrapperFilter(mGraph, byRef(decoder));
175 NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
176 }
177
178 // Sink, captures audio samples and inserts them into our pipeline.
179 static const wchar_t* AudioSinkFilterName = L"MozAudioSinkFilter";
180 mAudioSinkFilter = new AudioSinkFilter(AudioSinkFilterName, &hr);
181 NS_ENSURE_TRUE(mAudioSinkFilter && SUCCEEDED(hr), NS_ERROR_FAILURE);
182 hr = mGraph->AddFilter(mAudioSinkFilter, AudioSinkFilterName);
183 NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
184
185 // Join the filters.
186 hr = ConnectFilters(mGraph, mSourceFilter, demuxer);
187 NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
188
189 hr = ConnectFilters(mGraph, demuxer, decoder);
190 NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
191
192 hr = ConnectFilters(mGraph, decoder, mAudioSinkFilter);
193 NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
194
195 WAVEFORMATEX format;
196 mAudioSinkFilter->GetSampleSink()->GetAudioFormat(&format);
197 NS_ENSURE_TRUE(format.wFormatTag == WAVE_FORMAT_PCM, NS_ERROR_FAILURE);
198
199 mInfo.mAudio.mChannels = mNumChannels = format.nChannels;
200 mInfo.mAudio.mRate = mAudioRate = format.nSamplesPerSec;
201 mBytesPerSample = format.wBitsPerSample / 8;
202 mInfo.mAudio.mHasAudio = true;
203
204 *aInfo = mInfo;
205 // Note: The SourceFilter strips ID3v2 tags out of the stream.
206 *aTags = nullptr;
207
208 // Begin decoding!
209 hr = mControl->Run();
210 NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
211
212 DWORD seekCaps = 0;
213 hr = mMediaSeeking->GetCapabilities(&seekCaps);
214 bool canSeek = ((AM_SEEKING_CanSeekAbsolute & seekCaps) == AM_SEEKING_CanSeekAbsolute);
215 if (!canSeek) {
216 mDecoder->SetMediaSeekable(false);
217 }
218
219 int64_t duration = mMP3FrameParser.GetDuration();
220 if (SUCCEEDED(hr)) {
221 ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
222 mDecoder->SetMediaDuration(duration);
223 }
224
225 LOG("Successfully initialized DirectShow MP3 decoder.");
226 LOG("Channels=%u Hz=%u duration=%lld bytesPerSample=%d",
227 mInfo.mAudio.mChannels,
228 mInfo.mAudio.mRate,
229 RefTimeToUsecs(duration),
230 mBytesPerSample);
231
232 return NS_OK;
233 }
234
235 inline float
236 UnsignedByteToAudioSample(uint8_t aValue)
237 {
238 return aValue * (2.0f / UINT8_MAX) - 1.0f;
239 }
240
241 bool
242 DirectShowReader::Finish(HRESULT aStatus)
243 {
244 MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
245
246 LOG("DirectShowReader::Finish(0x%x)", aStatus);
247 // Notify the filter graph of end of stream.
248 RefPtr<IMediaEventSink> eventSink;
249 HRESULT hr = mGraph->QueryInterface(static_cast<IMediaEventSink**>(byRef(eventSink)));
250 if (SUCCEEDED(hr) && eventSink) {
251 eventSink->Notify(EC_COMPLETE, aStatus, 0);
252 }
253 return false;
254 }
255
256 class DirectShowCopy
257 {
258 public:
259 DirectShowCopy(uint8_t *aSource, uint32_t aBytesPerSample,
260 uint32_t aSamples, uint32_t aChannels)
261 : mSource(aSource)
262 , mBytesPerSample(aBytesPerSample)
263 , mSamples(aSamples)
264 , mChannels(aChannels)
265 , mNextSample(0)
266 { }
267
268 uint32_t operator()(AudioDataValue *aBuffer, uint32_t aSamples)
269 {
270 uint32_t maxSamples = std::min(aSamples, mSamples - mNextSample);
271 uint32_t frames = maxSamples / mChannels;
272 size_t byteOffset = mNextSample * mBytesPerSample;
273 if (mBytesPerSample == 1) {
274 for (uint32_t i = 0; i < maxSamples; ++i) {
275 uint8_t *sample = mSource + byteOffset;
276 aBuffer[i] = UnsignedByteToAudioSample(*sample);
277 byteOffset += mBytesPerSample;
278 }
279 } else if (mBytesPerSample == 2) {
280 for (uint32_t i = 0; i < maxSamples; ++i) {
281 int16_t *sample = reinterpret_cast<int16_t *>(mSource + byteOffset);
282 aBuffer[i] = AudioSampleToFloat(*sample);
283 byteOffset += mBytesPerSample;
284 }
285 }
286 mNextSample += maxSamples;
287 return frames;
288 }
289
290 private:
291 uint8_t * const mSource;
292 const uint32_t mBytesPerSample;
293 const uint32_t mSamples;
294 const uint32_t mChannels;
295 uint32_t mNextSample;
296 };
297
298 bool
299 DirectShowReader::DecodeAudioData()
300 {
301 MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
302 HRESULT hr;
303
304 SampleSink* sink = mAudioSinkFilter->GetSampleSink();
305 if (sink->AtEOS()) {
306 // End of stream.
307 return Finish(S_OK);
308 }
309
310 // Get the next chunk of audio samples. This blocks until the sample
311 // arrives, or an error occurs (like the stream is shutdown).
312 RefPtr<IMediaSample> sample;
313 hr = sink->Extract(sample);
314 if (FAILED(hr) || hr == S_FALSE) {
315 return Finish(hr);
316 }
317
318 int64_t start = 0, end = 0;
319 sample->GetMediaTime(&start, &end);
320 LOG("DirectShowReader::DecodeAudioData [%4.2lf-%4.2lf]",
321 RefTimeToSeconds(start),
322 RefTimeToSeconds(end));
323
324 LONG length = sample->GetActualDataLength();
325 LONG numSamples = length / mBytesPerSample;
326 LONG numFrames = length / mBytesPerSample / mNumChannels;
327
328 BYTE* data = nullptr;
329 hr = sample->GetPointer(&data);
330 NS_ENSURE_TRUE(SUCCEEDED(hr), Finish(hr));
331
332 mAudioCompactor.Push(mDecoder->GetResource()->Tell(),
333 RefTimeToUsecs(start),
334 mInfo.mAudio.mRate,
335 numFrames,
336 mNumChannels,
337 DirectShowCopy(reinterpret_cast<uint8_t *>(data),
338 mBytesPerSample,
339 numSamples,
340 mNumChannels));
341 return true;
342 }
343
344 bool
345 DirectShowReader::DecodeVideoFrame(bool &aKeyframeSkip,
346 int64_t aTimeThreshold)
347 {
348 MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
349 return false;
350 }
351
352 bool
353 DirectShowReader::HasAudio()
354 {
355 MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
356 return true;
357 }
358
359 bool
360 DirectShowReader::HasVideo()
361 {
362 MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
363 return false;
364 }
365
366 nsresult
367 DirectShowReader::Seek(int64_t aTargetUs,
368 int64_t aStartTime,
369 int64_t aEndTime,
370 int64_t aCurrentTime)
371 {
372 HRESULT hr;
373 MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");\
374
375 LOG("DirectShowReader::Seek() target=%lld", aTargetUs);
376
377 hr = mControl->Pause();
378 NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
379
380 nsresult rv = ResetDecode();
381 NS_ENSURE_SUCCESS(rv, rv);
382
383 LONGLONG seekPosition = UsecsToRefTime(aTargetUs);
384 hr = mMediaSeeking->SetPositions(&seekPosition,
385 AM_SEEKING_AbsolutePositioning,
386 nullptr,
387 AM_SEEKING_NoPositioning);
388 NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
389
390 hr = mControl->Run();
391 NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
392
393 return NS_OK;
394 }
395
396 void
397 DirectShowReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset)
398 {
399 MOZ_ASSERT(NS_IsMainThread());
400 if (!mMP3FrameParser.IsMP3()) {
401 return;
402 }
403 mMP3FrameParser.Parse(aBuffer, aLength, aOffset);
404 int64_t duration = mMP3FrameParser.GetDuration();
405 if (duration != mDuration) {
406 mDuration = duration;
407 MOZ_ASSERT(mDecoder);
408 ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
409 mDecoder->UpdateEstimatedMediaDuration(mDuration);
410 }
411 }
412
413 } // namespace mozilla

mercurial