|
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 |