michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "DirectShowReader.h" michael@0: #include "MediaDecoderReader.h" michael@0: #include "mozilla/RefPtr.h" michael@0: #include "dshow.h" michael@0: #include "AudioSinkFilter.h" michael@0: #include "SourceFilter.h" michael@0: #include "DirectShowUtils.h" michael@0: #include "SampleSink.h" michael@0: #include "MediaResource.h" michael@0: #include "VideoUtils.h" michael@0: michael@0: namespace mozilla { michael@0: michael@0: michael@0: #ifdef PR_LOGGING michael@0: michael@0: PRLogModuleInfo* michael@0: GetDirectShowLog() { michael@0: static PRLogModuleInfo* log = nullptr; michael@0: if (!log) { michael@0: log = PR_NewLogModule("DirectShowDecoder"); michael@0: } michael@0: return log; michael@0: } michael@0: michael@0: #define LOG(...) PR_LOG(GetDirectShowLog(), PR_LOG_DEBUG, (__VA_ARGS__)) michael@0: michael@0: #else michael@0: #define LOG(...) michael@0: #endif michael@0: michael@0: DirectShowReader::DirectShowReader(AbstractMediaDecoder* aDecoder) michael@0: : MediaDecoderReader(aDecoder), michael@0: mMP3FrameParser(aDecoder->GetResource()->GetLength()), michael@0: #ifdef DEBUG michael@0: mRotRegister(0), michael@0: #endif michael@0: mNumChannels(0), michael@0: mAudioRate(0), michael@0: mBytesPerSample(0), michael@0: mDuration(0) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); michael@0: MOZ_COUNT_CTOR(DirectShowReader); michael@0: } michael@0: michael@0: DirectShowReader::~DirectShowReader() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); michael@0: MOZ_COUNT_DTOR(DirectShowReader); michael@0: #ifdef DEBUG michael@0: if (mRotRegister) { michael@0: RemoveGraphFromRunningObjectTable(mRotRegister); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: nsresult michael@0: DirectShowReader::Init(MediaDecoderReader* aCloneDonor) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Try to parse the MP3 stream to make sure this is indeed an MP3, get the michael@0: // estimated duration of the stream, and find the offset of the actual MP3 michael@0: // frames in the stream, as DirectShow doesn't like large ID3 sections. michael@0: static nsresult michael@0: ParseMP3Headers(MP3FrameParser *aParser, MediaResource *aResource) michael@0: { michael@0: const uint32_t MAX_READ_SIZE = 4096; michael@0: michael@0: uint64_t offset = 0; michael@0: while (aParser->NeedsData() && !aParser->ParsedHeaders()) { michael@0: uint32_t bytesRead; michael@0: char buffer[MAX_READ_SIZE]; michael@0: nsresult rv = aResource->ReadAt(offset, buffer, michael@0: MAX_READ_SIZE, &bytesRead); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!bytesRead) { michael@0: // End of stream. michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: aParser->Parse(buffer, bytesRead, offset); michael@0: offset += bytesRead; michael@0: } michael@0: michael@0: return aParser->IsMP3() ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Windows XP's MP3 decoder filter. This is available on XP only, on Vista michael@0: // and later we can use the DMO Wrapper filter and MP3 decoder DMO. michael@0: static const GUID CLSID_MPEG_LAYER_3_DECODER_FILTER = michael@0: { 0x38BE3000, 0xDBF4, 0x11D0, 0x86, 0x0E, 0x00, 0xA0, 0x24, 0xCF, 0xEF, 0x6D }; michael@0: michael@0: nsresult michael@0: DirectShowReader::ReadMetadata(MediaInfo* aInfo, michael@0: MetadataTags** aTags) michael@0: { michael@0: MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread."); michael@0: HRESULT hr; michael@0: nsresult rv; michael@0: michael@0: // Create the filter graph, reference it by the GraphBuilder interface, michael@0: // to make graph building more convenient. michael@0: hr = CoCreateInstance(CLSID_FilterGraph, michael@0: nullptr, michael@0: CLSCTX_INPROC_SERVER, michael@0: IID_IGraphBuilder, michael@0: reinterpret_cast(static_cast(byRef(mGraph)))); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr) && mGraph, NS_ERROR_FAILURE); michael@0: michael@0: rv = ParseMP3Headers(&mMP3FrameParser, mDecoder->GetResource()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: #ifdef DEBUG michael@0: // Add the graph to the Running Object Table so that we can connect michael@0: // to this graph with GraphEdit/GraphStudio. Note: on Vista and up you must michael@0: // also regsvr32 proppage.dll from the Windows SDK. michael@0: // See: http://msdn.microsoft.com/en-us/library/ms787252(VS.85).aspx michael@0: hr = AddGraphToRunningObjectTable(mGraph, &mRotRegister); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); michael@0: #endif michael@0: michael@0: // Extract the interface pointers we'll need from the filter graph. michael@0: hr = mGraph->QueryInterface(static_cast(byRef(mControl))); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr) && mControl, NS_ERROR_FAILURE); michael@0: michael@0: hr = mGraph->QueryInterface(static_cast(byRef(mMediaSeeking))); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr) && mMediaSeeking, NS_ERROR_FAILURE); michael@0: michael@0: // Build the graph. Create the filters we need, and connect them. We michael@0: // build the entire graph ourselves to prevent other decoders installed michael@0: // on the system being created and used. michael@0: michael@0: // Our source filters, wraps the MediaResource. michael@0: mSourceFilter = new SourceFilter(MEDIATYPE_Stream, MEDIASUBTYPE_MPEG1Audio); michael@0: NS_ENSURE_TRUE(mSourceFilter, NS_ERROR_FAILURE); michael@0: michael@0: rv = mSourceFilter->Init(mDecoder->GetResource(), mMP3FrameParser.GetMP3Offset()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: hr = mGraph->AddFilter(mSourceFilter, L"MozillaDirectShowSource"); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); michael@0: michael@0: // The MPEG demuxer. michael@0: RefPtr demuxer; michael@0: hr = CreateAndAddFilter(mGraph, michael@0: CLSID_MPEG1Splitter, michael@0: L"MPEG1Splitter", michael@0: byRef(demuxer)); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); michael@0: michael@0: // Platform MP3 decoder. michael@0: RefPtr decoder; michael@0: // Firstly try to create the MP3 decoder filter that ships with WinXP michael@0: // directly. This filter doesn't normally exist on later versions of michael@0: // Windows. michael@0: hr = CreateAndAddFilter(mGraph, michael@0: CLSID_MPEG_LAYER_3_DECODER_FILTER, michael@0: L"MPEG Layer 3 Decoder", michael@0: byRef(decoder)); michael@0: if (FAILED(hr)) { michael@0: // Failed to create MP3 decoder filter. Try to instantiate michael@0: // the MP3 decoder DMO. michael@0: hr = AddMP3DMOWrapperFilter(mGraph, byRef(decoder)); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: // Sink, captures audio samples and inserts them into our pipeline. michael@0: static const wchar_t* AudioSinkFilterName = L"MozAudioSinkFilter"; michael@0: mAudioSinkFilter = new AudioSinkFilter(AudioSinkFilterName, &hr); michael@0: NS_ENSURE_TRUE(mAudioSinkFilter && SUCCEEDED(hr), NS_ERROR_FAILURE); michael@0: hr = mGraph->AddFilter(mAudioSinkFilter, AudioSinkFilterName); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); michael@0: michael@0: // Join the filters. michael@0: hr = ConnectFilters(mGraph, mSourceFilter, demuxer); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); michael@0: michael@0: hr = ConnectFilters(mGraph, demuxer, decoder); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); michael@0: michael@0: hr = ConnectFilters(mGraph, decoder, mAudioSinkFilter); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); michael@0: michael@0: WAVEFORMATEX format; michael@0: mAudioSinkFilter->GetSampleSink()->GetAudioFormat(&format); michael@0: NS_ENSURE_TRUE(format.wFormatTag == WAVE_FORMAT_PCM, NS_ERROR_FAILURE); michael@0: michael@0: mInfo.mAudio.mChannels = mNumChannels = format.nChannels; michael@0: mInfo.mAudio.mRate = mAudioRate = format.nSamplesPerSec; michael@0: mBytesPerSample = format.wBitsPerSample / 8; michael@0: mInfo.mAudio.mHasAudio = true; michael@0: michael@0: *aInfo = mInfo; michael@0: // Note: The SourceFilter strips ID3v2 tags out of the stream. michael@0: *aTags = nullptr; michael@0: michael@0: // Begin decoding! michael@0: hr = mControl->Run(); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); michael@0: michael@0: DWORD seekCaps = 0; michael@0: hr = mMediaSeeking->GetCapabilities(&seekCaps); michael@0: bool canSeek = ((AM_SEEKING_CanSeekAbsolute & seekCaps) == AM_SEEKING_CanSeekAbsolute); michael@0: if (!canSeek) { michael@0: mDecoder->SetMediaSeekable(false); michael@0: } michael@0: michael@0: int64_t duration = mMP3FrameParser.GetDuration(); michael@0: if (SUCCEEDED(hr)) { michael@0: ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); michael@0: mDecoder->SetMediaDuration(duration); michael@0: } michael@0: michael@0: LOG("Successfully initialized DirectShow MP3 decoder."); michael@0: LOG("Channels=%u Hz=%u duration=%lld bytesPerSample=%d", michael@0: mInfo.mAudio.mChannels, michael@0: mInfo.mAudio.mRate, michael@0: RefTimeToUsecs(duration), michael@0: mBytesPerSample); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: inline float michael@0: UnsignedByteToAudioSample(uint8_t aValue) michael@0: { michael@0: return aValue * (2.0f / UINT8_MAX) - 1.0f; michael@0: } michael@0: michael@0: bool michael@0: DirectShowReader::Finish(HRESULT aStatus) michael@0: { michael@0: MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread."); michael@0: michael@0: LOG("DirectShowReader::Finish(0x%x)", aStatus); michael@0: // Notify the filter graph of end of stream. michael@0: RefPtr eventSink; michael@0: HRESULT hr = mGraph->QueryInterface(static_cast(byRef(eventSink))); michael@0: if (SUCCEEDED(hr) && eventSink) { michael@0: eventSink->Notify(EC_COMPLETE, aStatus, 0); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: class DirectShowCopy michael@0: { michael@0: public: michael@0: DirectShowCopy(uint8_t *aSource, uint32_t aBytesPerSample, michael@0: uint32_t aSamples, uint32_t aChannels) michael@0: : mSource(aSource) michael@0: , mBytesPerSample(aBytesPerSample) michael@0: , mSamples(aSamples) michael@0: , mChannels(aChannels) michael@0: , mNextSample(0) michael@0: { } michael@0: michael@0: uint32_t operator()(AudioDataValue *aBuffer, uint32_t aSamples) michael@0: { michael@0: uint32_t maxSamples = std::min(aSamples, mSamples - mNextSample); michael@0: uint32_t frames = maxSamples / mChannels; michael@0: size_t byteOffset = mNextSample * mBytesPerSample; michael@0: if (mBytesPerSample == 1) { michael@0: for (uint32_t i = 0; i < maxSamples; ++i) { michael@0: uint8_t *sample = mSource + byteOffset; michael@0: aBuffer[i] = UnsignedByteToAudioSample(*sample); michael@0: byteOffset += mBytesPerSample; michael@0: } michael@0: } else if (mBytesPerSample == 2) { michael@0: for (uint32_t i = 0; i < maxSamples; ++i) { michael@0: int16_t *sample = reinterpret_cast(mSource + byteOffset); michael@0: aBuffer[i] = AudioSampleToFloat(*sample); michael@0: byteOffset += mBytesPerSample; michael@0: } michael@0: } michael@0: mNextSample += maxSamples; michael@0: return frames; michael@0: } michael@0: michael@0: private: michael@0: uint8_t * const mSource; michael@0: const uint32_t mBytesPerSample; michael@0: const uint32_t mSamples; michael@0: const uint32_t mChannels; michael@0: uint32_t mNextSample; michael@0: }; michael@0: michael@0: bool michael@0: DirectShowReader::DecodeAudioData() michael@0: { michael@0: MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread."); michael@0: HRESULT hr; michael@0: michael@0: SampleSink* sink = mAudioSinkFilter->GetSampleSink(); michael@0: if (sink->AtEOS()) { michael@0: // End of stream. michael@0: return Finish(S_OK); michael@0: } michael@0: michael@0: // Get the next chunk of audio samples. This blocks until the sample michael@0: // arrives, or an error occurs (like the stream is shutdown). michael@0: RefPtr sample; michael@0: hr = sink->Extract(sample); michael@0: if (FAILED(hr) || hr == S_FALSE) { michael@0: return Finish(hr); michael@0: } michael@0: michael@0: int64_t start = 0, end = 0; michael@0: sample->GetMediaTime(&start, &end); michael@0: LOG("DirectShowReader::DecodeAudioData [%4.2lf-%4.2lf]", michael@0: RefTimeToSeconds(start), michael@0: RefTimeToSeconds(end)); michael@0: michael@0: LONG length = sample->GetActualDataLength(); michael@0: LONG numSamples = length / mBytesPerSample; michael@0: LONG numFrames = length / mBytesPerSample / mNumChannels; michael@0: michael@0: BYTE* data = nullptr; michael@0: hr = sample->GetPointer(&data); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr), Finish(hr)); michael@0: michael@0: mAudioCompactor.Push(mDecoder->GetResource()->Tell(), michael@0: RefTimeToUsecs(start), michael@0: mInfo.mAudio.mRate, michael@0: numFrames, michael@0: mNumChannels, michael@0: DirectShowCopy(reinterpret_cast(data), michael@0: mBytesPerSample, michael@0: numSamples, michael@0: mNumChannels)); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: DirectShowReader::DecodeVideoFrame(bool &aKeyframeSkip, michael@0: int64_t aTimeThreshold) michael@0: { michael@0: MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread."); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: DirectShowReader::HasAudio() michael@0: { michael@0: MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread."); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: DirectShowReader::HasVideo() michael@0: { michael@0: MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread."); michael@0: return false; michael@0: } michael@0: michael@0: nsresult michael@0: DirectShowReader::Seek(int64_t aTargetUs, michael@0: int64_t aStartTime, michael@0: int64_t aEndTime, michael@0: int64_t aCurrentTime) michael@0: { michael@0: HRESULT hr; michael@0: MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");\ michael@0: michael@0: LOG("DirectShowReader::Seek() target=%lld", aTargetUs); michael@0: michael@0: hr = mControl->Pause(); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); michael@0: michael@0: nsresult rv = ResetDecode(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: LONGLONG seekPosition = UsecsToRefTime(aTargetUs); michael@0: hr = mMediaSeeking->SetPositions(&seekPosition, michael@0: AM_SEEKING_AbsolutePositioning, michael@0: nullptr, michael@0: AM_SEEKING_NoPositioning); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); michael@0: michael@0: hr = mControl->Run(); michael@0: NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: DirectShowReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (!mMP3FrameParser.IsMP3()) { michael@0: return; michael@0: } michael@0: mMP3FrameParser.Parse(aBuffer, aLength, aOffset); michael@0: int64_t duration = mMP3FrameParser.GetDuration(); michael@0: if (duration != mDuration) { michael@0: mDuration = duration; michael@0: MOZ_ASSERT(mDecoder); michael@0: ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); michael@0: mDecoder->UpdateEstimatedMediaDuration(mDuration); michael@0: } michael@0: } michael@0: michael@0: } // namespace mozilla