1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/directshow/DirectShowReader.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,413 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "DirectShowReader.h" 1.11 +#include "MediaDecoderReader.h" 1.12 +#include "mozilla/RefPtr.h" 1.13 +#include "dshow.h" 1.14 +#include "AudioSinkFilter.h" 1.15 +#include "SourceFilter.h" 1.16 +#include "DirectShowUtils.h" 1.17 +#include "SampleSink.h" 1.18 +#include "MediaResource.h" 1.19 +#include "VideoUtils.h" 1.20 + 1.21 +namespace mozilla { 1.22 + 1.23 + 1.24 +#ifdef PR_LOGGING 1.25 + 1.26 +PRLogModuleInfo* 1.27 +GetDirectShowLog() { 1.28 + static PRLogModuleInfo* log = nullptr; 1.29 + if (!log) { 1.30 + log = PR_NewLogModule("DirectShowDecoder"); 1.31 + } 1.32 + return log; 1.33 +} 1.34 + 1.35 +#define LOG(...) PR_LOG(GetDirectShowLog(), PR_LOG_DEBUG, (__VA_ARGS__)) 1.36 + 1.37 +#else 1.38 +#define LOG(...) 1.39 +#endif 1.40 + 1.41 +DirectShowReader::DirectShowReader(AbstractMediaDecoder* aDecoder) 1.42 + : MediaDecoderReader(aDecoder), 1.43 + mMP3FrameParser(aDecoder->GetResource()->GetLength()), 1.44 +#ifdef DEBUG 1.45 + mRotRegister(0), 1.46 +#endif 1.47 + mNumChannels(0), 1.48 + mAudioRate(0), 1.49 + mBytesPerSample(0), 1.50 + mDuration(0) 1.51 +{ 1.52 + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); 1.53 + MOZ_COUNT_CTOR(DirectShowReader); 1.54 +} 1.55 + 1.56 +DirectShowReader::~DirectShowReader() 1.57 +{ 1.58 + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); 1.59 + MOZ_COUNT_DTOR(DirectShowReader); 1.60 +#ifdef DEBUG 1.61 + if (mRotRegister) { 1.62 + RemoveGraphFromRunningObjectTable(mRotRegister); 1.63 + } 1.64 +#endif 1.65 +} 1.66 + 1.67 +nsresult 1.68 +DirectShowReader::Init(MediaDecoderReader* aCloneDonor) 1.69 +{ 1.70 + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); 1.71 + return NS_OK; 1.72 +} 1.73 + 1.74 +// Try to parse the MP3 stream to make sure this is indeed an MP3, get the 1.75 +// estimated duration of the stream, and find the offset of the actual MP3 1.76 +// frames in the stream, as DirectShow doesn't like large ID3 sections. 1.77 +static nsresult 1.78 +ParseMP3Headers(MP3FrameParser *aParser, MediaResource *aResource) 1.79 +{ 1.80 + const uint32_t MAX_READ_SIZE = 4096; 1.81 + 1.82 + uint64_t offset = 0; 1.83 + while (aParser->NeedsData() && !aParser->ParsedHeaders()) { 1.84 + uint32_t bytesRead; 1.85 + char buffer[MAX_READ_SIZE]; 1.86 + nsresult rv = aResource->ReadAt(offset, buffer, 1.87 + MAX_READ_SIZE, &bytesRead); 1.88 + NS_ENSURE_SUCCESS(rv, rv); 1.89 + 1.90 + if (!bytesRead) { 1.91 + // End of stream. 1.92 + return NS_ERROR_FAILURE; 1.93 + } 1.94 + 1.95 + aParser->Parse(buffer, bytesRead, offset); 1.96 + offset += bytesRead; 1.97 + } 1.98 + 1.99 + return aParser->IsMP3() ? NS_OK : NS_ERROR_FAILURE; 1.100 +} 1.101 + 1.102 +// Windows XP's MP3 decoder filter. This is available on XP only, on Vista 1.103 +// and later we can use the DMO Wrapper filter and MP3 decoder DMO. 1.104 +static const GUID CLSID_MPEG_LAYER_3_DECODER_FILTER = 1.105 +{ 0x38BE3000, 0xDBF4, 0x11D0, 0x86, 0x0E, 0x00, 0xA0, 0x24, 0xCF, 0xEF, 0x6D }; 1.106 + 1.107 +nsresult 1.108 +DirectShowReader::ReadMetadata(MediaInfo* aInfo, 1.109 + MetadataTags** aTags) 1.110 +{ 1.111 + MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.112 + HRESULT hr; 1.113 + nsresult rv; 1.114 + 1.115 + // Create the filter graph, reference it by the GraphBuilder interface, 1.116 + // to make graph building more convenient. 1.117 + hr = CoCreateInstance(CLSID_FilterGraph, 1.118 + nullptr, 1.119 + CLSCTX_INPROC_SERVER, 1.120 + IID_IGraphBuilder, 1.121 + reinterpret_cast<void**>(static_cast<IGraphBuilder**>(byRef(mGraph)))); 1.122 + NS_ENSURE_TRUE(SUCCEEDED(hr) && mGraph, NS_ERROR_FAILURE); 1.123 + 1.124 + rv = ParseMP3Headers(&mMP3FrameParser, mDecoder->GetResource()); 1.125 + NS_ENSURE_SUCCESS(rv, rv); 1.126 + 1.127 + #ifdef DEBUG 1.128 + // Add the graph to the Running Object Table so that we can connect 1.129 + // to this graph with GraphEdit/GraphStudio. Note: on Vista and up you must 1.130 + // also regsvr32 proppage.dll from the Windows SDK. 1.131 + // See: http://msdn.microsoft.com/en-us/library/ms787252(VS.85).aspx 1.132 + hr = AddGraphToRunningObjectTable(mGraph, &mRotRegister); 1.133 + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); 1.134 + #endif 1.135 + 1.136 + // Extract the interface pointers we'll need from the filter graph. 1.137 + hr = mGraph->QueryInterface(static_cast<IMediaControl**>(byRef(mControl))); 1.138 + NS_ENSURE_TRUE(SUCCEEDED(hr) && mControl, NS_ERROR_FAILURE); 1.139 + 1.140 + hr = mGraph->QueryInterface(static_cast<IMediaSeeking**>(byRef(mMediaSeeking))); 1.141 + NS_ENSURE_TRUE(SUCCEEDED(hr) && mMediaSeeking, NS_ERROR_FAILURE); 1.142 + 1.143 + // Build the graph. Create the filters we need, and connect them. We 1.144 + // build the entire graph ourselves to prevent other decoders installed 1.145 + // on the system being created and used. 1.146 + 1.147 + // Our source filters, wraps the MediaResource. 1.148 + mSourceFilter = new SourceFilter(MEDIATYPE_Stream, MEDIASUBTYPE_MPEG1Audio); 1.149 + NS_ENSURE_TRUE(mSourceFilter, NS_ERROR_FAILURE); 1.150 + 1.151 + rv = mSourceFilter->Init(mDecoder->GetResource(), mMP3FrameParser.GetMP3Offset()); 1.152 + NS_ENSURE_SUCCESS(rv, rv); 1.153 + 1.154 + hr = mGraph->AddFilter(mSourceFilter, L"MozillaDirectShowSource"); 1.155 + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); 1.156 + 1.157 + // The MPEG demuxer. 1.158 + RefPtr<IBaseFilter> demuxer; 1.159 + hr = CreateAndAddFilter(mGraph, 1.160 + CLSID_MPEG1Splitter, 1.161 + L"MPEG1Splitter", 1.162 + byRef(demuxer)); 1.163 + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); 1.164 + 1.165 + // Platform MP3 decoder. 1.166 + RefPtr<IBaseFilter> decoder; 1.167 + // Firstly try to create the MP3 decoder filter that ships with WinXP 1.168 + // directly. This filter doesn't normally exist on later versions of 1.169 + // Windows. 1.170 + hr = CreateAndAddFilter(mGraph, 1.171 + CLSID_MPEG_LAYER_3_DECODER_FILTER, 1.172 + L"MPEG Layer 3 Decoder", 1.173 + byRef(decoder)); 1.174 + if (FAILED(hr)) { 1.175 + // Failed to create MP3 decoder filter. Try to instantiate 1.176 + // the MP3 decoder DMO. 1.177 + hr = AddMP3DMOWrapperFilter(mGraph, byRef(decoder)); 1.178 + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); 1.179 + } 1.180 + 1.181 + // Sink, captures audio samples and inserts them into our pipeline. 1.182 + static const wchar_t* AudioSinkFilterName = L"MozAudioSinkFilter"; 1.183 + mAudioSinkFilter = new AudioSinkFilter(AudioSinkFilterName, &hr); 1.184 + NS_ENSURE_TRUE(mAudioSinkFilter && SUCCEEDED(hr), NS_ERROR_FAILURE); 1.185 + hr = mGraph->AddFilter(mAudioSinkFilter, AudioSinkFilterName); 1.186 + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); 1.187 + 1.188 + // Join the filters. 1.189 + hr = ConnectFilters(mGraph, mSourceFilter, demuxer); 1.190 + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); 1.191 + 1.192 + hr = ConnectFilters(mGraph, demuxer, decoder); 1.193 + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); 1.194 + 1.195 + hr = ConnectFilters(mGraph, decoder, mAudioSinkFilter); 1.196 + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); 1.197 + 1.198 + WAVEFORMATEX format; 1.199 + mAudioSinkFilter->GetSampleSink()->GetAudioFormat(&format); 1.200 + NS_ENSURE_TRUE(format.wFormatTag == WAVE_FORMAT_PCM, NS_ERROR_FAILURE); 1.201 + 1.202 + mInfo.mAudio.mChannels = mNumChannels = format.nChannels; 1.203 + mInfo.mAudio.mRate = mAudioRate = format.nSamplesPerSec; 1.204 + mBytesPerSample = format.wBitsPerSample / 8; 1.205 + mInfo.mAudio.mHasAudio = true; 1.206 + 1.207 + *aInfo = mInfo; 1.208 + // Note: The SourceFilter strips ID3v2 tags out of the stream. 1.209 + *aTags = nullptr; 1.210 + 1.211 + // Begin decoding! 1.212 + hr = mControl->Run(); 1.213 + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); 1.214 + 1.215 + DWORD seekCaps = 0; 1.216 + hr = mMediaSeeking->GetCapabilities(&seekCaps); 1.217 + bool canSeek = ((AM_SEEKING_CanSeekAbsolute & seekCaps) == AM_SEEKING_CanSeekAbsolute); 1.218 + if (!canSeek) { 1.219 + mDecoder->SetMediaSeekable(false); 1.220 + } 1.221 + 1.222 + int64_t duration = mMP3FrameParser.GetDuration(); 1.223 + if (SUCCEEDED(hr)) { 1.224 + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); 1.225 + mDecoder->SetMediaDuration(duration); 1.226 + } 1.227 + 1.228 + LOG("Successfully initialized DirectShow MP3 decoder."); 1.229 + LOG("Channels=%u Hz=%u duration=%lld bytesPerSample=%d", 1.230 + mInfo.mAudio.mChannels, 1.231 + mInfo.mAudio.mRate, 1.232 + RefTimeToUsecs(duration), 1.233 + mBytesPerSample); 1.234 + 1.235 + return NS_OK; 1.236 +} 1.237 + 1.238 +inline float 1.239 +UnsignedByteToAudioSample(uint8_t aValue) 1.240 +{ 1.241 + return aValue * (2.0f / UINT8_MAX) - 1.0f; 1.242 +} 1.243 + 1.244 +bool 1.245 +DirectShowReader::Finish(HRESULT aStatus) 1.246 +{ 1.247 + MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.248 + 1.249 + LOG("DirectShowReader::Finish(0x%x)", aStatus); 1.250 + // Notify the filter graph of end of stream. 1.251 + RefPtr<IMediaEventSink> eventSink; 1.252 + HRESULT hr = mGraph->QueryInterface(static_cast<IMediaEventSink**>(byRef(eventSink))); 1.253 + if (SUCCEEDED(hr) && eventSink) { 1.254 + eventSink->Notify(EC_COMPLETE, aStatus, 0); 1.255 + } 1.256 + return false; 1.257 +} 1.258 + 1.259 +class DirectShowCopy 1.260 +{ 1.261 +public: 1.262 + DirectShowCopy(uint8_t *aSource, uint32_t aBytesPerSample, 1.263 + uint32_t aSamples, uint32_t aChannels) 1.264 + : mSource(aSource) 1.265 + , mBytesPerSample(aBytesPerSample) 1.266 + , mSamples(aSamples) 1.267 + , mChannels(aChannels) 1.268 + , mNextSample(0) 1.269 + { } 1.270 + 1.271 + uint32_t operator()(AudioDataValue *aBuffer, uint32_t aSamples) 1.272 + { 1.273 + uint32_t maxSamples = std::min(aSamples, mSamples - mNextSample); 1.274 + uint32_t frames = maxSamples / mChannels; 1.275 + size_t byteOffset = mNextSample * mBytesPerSample; 1.276 + if (mBytesPerSample == 1) { 1.277 + for (uint32_t i = 0; i < maxSamples; ++i) { 1.278 + uint8_t *sample = mSource + byteOffset; 1.279 + aBuffer[i] = UnsignedByteToAudioSample(*sample); 1.280 + byteOffset += mBytesPerSample; 1.281 + } 1.282 + } else if (mBytesPerSample == 2) { 1.283 + for (uint32_t i = 0; i < maxSamples; ++i) { 1.284 + int16_t *sample = reinterpret_cast<int16_t *>(mSource + byteOffset); 1.285 + aBuffer[i] = AudioSampleToFloat(*sample); 1.286 + byteOffset += mBytesPerSample; 1.287 + } 1.288 + } 1.289 + mNextSample += maxSamples; 1.290 + return frames; 1.291 + } 1.292 + 1.293 +private: 1.294 + uint8_t * const mSource; 1.295 + const uint32_t mBytesPerSample; 1.296 + const uint32_t mSamples; 1.297 + const uint32_t mChannels; 1.298 + uint32_t mNextSample; 1.299 +}; 1.300 + 1.301 +bool 1.302 +DirectShowReader::DecodeAudioData() 1.303 +{ 1.304 + MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.305 + HRESULT hr; 1.306 + 1.307 + SampleSink* sink = mAudioSinkFilter->GetSampleSink(); 1.308 + if (sink->AtEOS()) { 1.309 + // End of stream. 1.310 + return Finish(S_OK); 1.311 + } 1.312 + 1.313 + // Get the next chunk of audio samples. This blocks until the sample 1.314 + // arrives, or an error occurs (like the stream is shutdown). 1.315 + RefPtr<IMediaSample> sample; 1.316 + hr = sink->Extract(sample); 1.317 + if (FAILED(hr) || hr == S_FALSE) { 1.318 + return Finish(hr); 1.319 + } 1.320 + 1.321 + int64_t start = 0, end = 0; 1.322 + sample->GetMediaTime(&start, &end); 1.323 + LOG("DirectShowReader::DecodeAudioData [%4.2lf-%4.2lf]", 1.324 + RefTimeToSeconds(start), 1.325 + RefTimeToSeconds(end)); 1.326 + 1.327 + LONG length = sample->GetActualDataLength(); 1.328 + LONG numSamples = length / mBytesPerSample; 1.329 + LONG numFrames = length / mBytesPerSample / mNumChannels; 1.330 + 1.331 + BYTE* data = nullptr; 1.332 + hr = sample->GetPointer(&data); 1.333 + NS_ENSURE_TRUE(SUCCEEDED(hr), Finish(hr)); 1.334 + 1.335 + mAudioCompactor.Push(mDecoder->GetResource()->Tell(), 1.336 + RefTimeToUsecs(start), 1.337 + mInfo.mAudio.mRate, 1.338 + numFrames, 1.339 + mNumChannels, 1.340 + DirectShowCopy(reinterpret_cast<uint8_t *>(data), 1.341 + mBytesPerSample, 1.342 + numSamples, 1.343 + mNumChannels)); 1.344 + return true; 1.345 +} 1.346 + 1.347 +bool 1.348 +DirectShowReader::DecodeVideoFrame(bool &aKeyframeSkip, 1.349 + int64_t aTimeThreshold) 1.350 +{ 1.351 + MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.352 + return false; 1.353 +} 1.354 + 1.355 +bool 1.356 +DirectShowReader::HasAudio() 1.357 +{ 1.358 + MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.359 + return true; 1.360 +} 1.361 + 1.362 +bool 1.363 +DirectShowReader::HasVideo() 1.364 +{ 1.365 + MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.366 + return false; 1.367 +} 1.368 + 1.369 +nsresult 1.370 +DirectShowReader::Seek(int64_t aTargetUs, 1.371 + int64_t aStartTime, 1.372 + int64_t aEndTime, 1.373 + int64_t aCurrentTime) 1.374 +{ 1.375 + HRESULT hr; 1.376 + MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");\ 1.377 + 1.378 + LOG("DirectShowReader::Seek() target=%lld", aTargetUs); 1.379 + 1.380 + hr = mControl->Pause(); 1.381 + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); 1.382 + 1.383 + nsresult rv = ResetDecode(); 1.384 + NS_ENSURE_SUCCESS(rv, rv); 1.385 + 1.386 + LONGLONG seekPosition = UsecsToRefTime(aTargetUs); 1.387 + hr = mMediaSeeking->SetPositions(&seekPosition, 1.388 + AM_SEEKING_AbsolutePositioning, 1.389 + nullptr, 1.390 + AM_SEEKING_NoPositioning); 1.391 + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); 1.392 + 1.393 + hr = mControl->Run(); 1.394 + NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); 1.395 + 1.396 + return NS_OK; 1.397 +} 1.398 + 1.399 +void 1.400 +DirectShowReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) 1.401 +{ 1.402 + MOZ_ASSERT(NS_IsMainThread()); 1.403 + if (!mMP3FrameParser.IsMP3()) { 1.404 + return; 1.405 + } 1.406 + mMP3FrameParser.Parse(aBuffer, aLength, aOffset); 1.407 + int64_t duration = mMP3FrameParser.GetDuration(); 1.408 + if (duration != mDuration) { 1.409 + mDuration = duration; 1.410 + MOZ_ASSERT(mDecoder); 1.411 + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); 1.412 + mDecoder->UpdateEstimatedMediaDuration(mDuration); 1.413 + } 1.414 +} 1.415 + 1.416 +} // namespace mozilla