content/media/directshow/DirectShowReader.cpp

Fri, 16 Jan 2015 04:50:19 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 04:50:19 +0100
branch
TOR_BUG_9701
changeset 13
44a2da4a2ab2
permissions
-rw-r--r--

Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32

     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/. */
     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"
    18 namespace mozilla {
    21 #ifdef PR_LOGGING
    23 PRLogModuleInfo*
    24 GetDirectShowLog() {
    25   static PRLogModuleInfo* log = nullptr;
    26   if (!log) {
    27     log = PR_NewLogModule("DirectShowDecoder");
    28   }
    29   return log;
    30 }
    32 #define LOG(...) PR_LOG(GetDirectShowLog(), PR_LOG_DEBUG, (__VA_ARGS__))
    34 #else
    35 #define LOG(...)
    36 #endif
    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 }
    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 }
    64 nsresult
    65 DirectShowReader::Init(MediaDecoderReader* aCloneDonor)
    66 {
    67   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
    68   return NS_OK;
    69 }
    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;
    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);
    87     if (!bytesRead) {
    88       // End of stream.
    89       return NS_ERROR_FAILURE;
    90     }
    92     aParser->Parse(buffer, bytesRead, offset);
    93     offset += bytesRead;
    94   }
    96   return aParser->IsMP3() ? NS_OK : NS_ERROR_FAILURE;
    97 }
    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 };
   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;
   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);
   121   rv = ParseMP3Headers(&mMP3FrameParser, mDecoder->GetResource());
   122   NS_ENSURE_SUCCESS(rv, rv);
   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
   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);
   137   hr = mGraph->QueryInterface(static_cast<IMediaSeeking**>(byRef(mMediaSeeking)));
   138   NS_ENSURE_TRUE(SUCCEEDED(hr) && mMediaSeeking, NS_ERROR_FAILURE);
   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.
   144   // Our source filters, wraps the MediaResource.
   145   mSourceFilter = new SourceFilter(MEDIATYPE_Stream, MEDIASUBTYPE_MPEG1Audio);
   146   NS_ENSURE_TRUE(mSourceFilter, NS_ERROR_FAILURE);
   148   rv = mSourceFilter->Init(mDecoder->GetResource(), mMP3FrameParser.GetMP3Offset());
   149   NS_ENSURE_SUCCESS(rv, rv);
   151   hr = mGraph->AddFilter(mSourceFilter, L"MozillaDirectShowSource");
   152   NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
   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);
   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   }
   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);
   185   // Join the filters.
   186   hr = ConnectFilters(mGraph, mSourceFilter, demuxer);
   187   NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
   189   hr = ConnectFilters(mGraph, demuxer, decoder);
   190   NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
   192   hr = ConnectFilters(mGraph, decoder, mAudioSinkFilter);
   193   NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
   195   WAVEFORMATEX format;
   196   mAudioSinkFilter->GetSampleSink()->GetAudioFormat(&format);
   197   NS_ENSURE_TRUE(format.wFormatTag == WAVE_FORMAT_PCM, NS_ERROR_FAILURE);
   199   mInfo.mAudio.mChannels = mNumChannels = format.nChannels;
   200   mInfo.mAudio.mRate = mAudioRate = format.nSamplesPerSec;
   201   mBytesPerSample = format.wBitsPerSample / 8;
   202   mInfo.mAudio.mHasAudio = true;
   204   *aInfo = mInfo;
   205   // Note: The SourceFilter strips ID3v2 tags out of the stream.
   206   *aTags = nullptr;
   208   // Begin decoding!
   209   hr = mControl->Run();
   210   NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
   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   }
   219   int64_t duration = mMP3FrameParser.GetDuration();
   220   if (SUCCEEDED(hr)) {
   221     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   222     mDecoder->SetMediaDuration(duration);
   223   }
   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);
   232   return NS_OK;
   233 }
   235 inline float
   236 UnsignedByteToAudioSample(uint8_t aValue)
   237 {
   238   return aValue * (2.0f / UINT8_MAX) - 1.0f;
   239 }
   241 bool
   242 DirectShowReader::Finish(HRESULT aStatus)
   243 {
   244   MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   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 }
   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   { }
   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   }
   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 };
   298 bool
   299 DirectShowReader::DecodeAudioData()
   300 {
   301   MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   302   HRESULT hr;
   304   SampleSink* sink = mAudioSinkFilter->GetSampleSink();
   305   if (sink->AtEOS()) {
   306     // End of stream.
   307     return Finish(S_OK);
   308   }
   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   }
   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));
   324   LONG length = sample->GetActualDataLength();
   325   LONG numSamples = length / mBytesPerSample;
   326   LONG numFrames = length / mBytesPerSample / mNumChannels;
   328   BYTE* data = nullptr;
   329   hr = sample->GetPointer(&data);
   330   NS_ENSURE_TRUE(SUCCEEDED(hr), Finish(hr));
   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 }
   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 }
   352 bool
   353 DirectShowReader::HasAudio()
   354 {
   355   MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   356   return true;
   357 }
   359 bool
   360 DirectShowReader::HasVideo()
   361 {
   362   MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   363   return false;
   364 }
   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.");\
   375   LOG("DirectShowReader::Seek() target=%lld", aTargetUs);
   377   hr = mControl->Pause();
   378   NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
   380   nsresult rv = ResetDecode();
   381   NS_ENSURE_SUCCESS(rv, rv);
   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);
   390   hr = mControl->Run();
   391   NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
   393   return NS_OK;
   394 }
   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 }
   413 } // namespace mozilla

mercurial