content/media/directshow/DirectShowReader.cpp

Thu, 15 Jan 2015 15:55:04 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:55:04 +0100
branch
TOR_BUG_9701
changeset 9
a63d609f5ebe
permissions
-rw-r--r--

Back out 97036ab72558 which inappropriately compared turds to third parties.

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

mercurial