Bug 831224: Use multi-threaded I/O for reading and processing MP3 frames, r=padenot
☠☠ backed out by 4f70ae91d84e ☠ ☠
authorThomas Zimmermann <tdz@users.sourceforge.net>
Mon, 06 May 2013 20:07:29 +0200
changeset 154765 909cddbd5af90e4110cf4a6bbf402d5d21e10060
parent 154764 e334b3139e2afc7034c2fc4bd9468e08b96d31be
child 154766 374a8aab2b49cf8b29602a8bc09e715f27d9824f
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot
bugs831224
milestone26.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 831224: Use multi-threaded I/O for reading and processing MP3 frames, r=padenot Reading large MP3 files from a slow medium, such as an SD card, takes several seconds. This is too long for interactive applications like the music app. With this patch, the OmxDecoder reads large MP3 file in smaller chunks and hands over each chunk individually to the MP3 parser. Reading the file is done in the I/O thread, which is allowed to block, parsing is done on the main thread. The current chunk size is 8 MiB, which is small enough to not cause any delay.
content/media/omx/OmxDecoder.cpp
content/media/omx/OmxDecoder.h
--- a/content/media/omx/OmxDecoder.cpp
+++ b/content/media/omx/OmxDecoder.cpp
@@ -35,32 +35,60 @@ PRLogModuleInfo *gOmxDecoderLog;
 #define LOG(x...)
 #endif
 
 using namespace MPAPI;
 using namespace mozilla;
 
 namespace mozilla {
 
+class OmxDecoderProcessCachedDataTask : public Task
+{
+public:
+  OmxDecoderProcessCachedDataTask(android::OmxDecoder* aOmxDecoder, int64_t aOffset)
+  : mOmxDecoder(aOmxDecoder),
+    mOffset(aOffset)
+  { }
+
+  void Run()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+    MOZ_ASSERT(mOmxDecoder.get());
+    mOmxDecoder->ProcessCachedData(mOffset, false);
+  }
+
+private:
+  android::sp<android::OmxDecoder> mOmxDecoder;
+  int64_t                          mOffset;
+};
+
 // When loading an MP3 stream from a file, we need to parse the file's
-// content to find its duration. We must do this from within the decode
-// thread, but parsing itself must be done in the main thread.
+// content to find its duration. Reading files of 100 Mib or more can
+// delay the player app noticably, so the file os read and decoded in
+// smaller chunks.
 //
-// After we read the file's content in the decode thread, an instance
-// of this class is scheduled to the main thread for parsing the MP3
-// stream. We then wait until it has returned.
+// We first read on the decode thread, but parsing must be done on the
+// main thread. After we read the file's initial MiBs in the decode
+// thread, an instance of this class is scheduled to the main thread for
+// parsing the MP3 stream. The decode thread waits until it has finished.
+//
+// If there is more data available from the file, the runnable dispatches
+// a task to the IO thread for retrieving the next chunk of data, and
+// the IO task dispatches a runnable to the main thread for parsing the
+// data. This goes on until all of the MP3 file has been parsed.
 
 class OmxDecoderNotifyDataArrivedRunnable : public nsRunnable
 {
 public:
-  OmxDecoderNotifyDataArrivedRunnable(android::OmxDecoder* aOmxDecoder, const char* aBuffer, uint64_t aLength, int64_t aOffset)
+  OmxDecoderNotifyDataArrivedRunnable(android::OmxDecoder* aOmxDecoder, const char* aBuffer, uint64_t aLength, int64_t aOffset, uint64_t aFullLength)
   : mOmxDecoder(aOmxDecoder),
     mBuffer(aBuffer),
     mLength(aLength),
     mOffset(aOffset),
+    mFullLength(aFullLength),
     mCompletedMonitor("OmxDecoderNotifyDataArrived.mCompleted"),
     mCompleted(false)
   {
     MOZ_ASSERT(mOmxDecoder.get());
     MOZ_ASSERT(mBuffer.get() || !mLength);
   }
 
   NS_IMETHOD Run()
@@ -73,85 +101,59 @@ public:
       uint32_t length = std::min<uint64_t>(mLength, UINT32_MAX);
       mOmxDecoder->NotifyDataArrived(mBuffer.get(), mLength, mOffset);
 
       buffer  += length;
       mLength -= length;
       mOffset += length;
     }
 
+    if (mOffset < mFullLength) {
+      // We cannot read data in the main thread because it
+      // might block for too long. Instead we post an IO task
+      // to the IO thread if there is more data available.
+      XRE_GetIOMessageLoop()->PostTask(FROM_HERE, new OmxDecoderProcessCachedDataTask(mOmxDecoder.get(), mOffset));
+    }
+
     Completed();
 
     return NS_OK;
   }
 
   void WaitForCompletion()
   {
     MOZ_ASSERT(!NS_IsMainThread());
 
     MonitorAutoLock mon(mCompletedMonitor);
     if (!mCompleted) {
       mCompletedMonitor.Wait();
     }
   }
 
-  static nsresult ProcessCachedData(android::OmxDecoder* aOmxDecoder);
-
 private:
   // Call this function at the end of Run() to notify waiting
   // threads.
   void Completed()
   {
     MonitorAutoLock mon(mCompletedMonitor);
     MOZ_ASSERT(!mCompleted);
     mCompleted = true;
     mCompletedMonitor.Notify();
   }
 
   android::sp<android::OmxDecoder> mOmxDecoder;
   nsAutoArrayPtr<const char>       mBuffer;
   uint64_t                         mLength;
   int64_t                          mOffset;
+  uint64_t                         mFullLength;
 
   Monitor mCompletedMonitor;
   bool    mCompleted;
 };
 
-nsresult OmxDecoderNotifyDataArrivedRunnable::ProcessCachedData(android::OmxDecoder* aOmxDecoder)
-{
-  MOZ_ASSERT(aOmxDecoder);
-
-  NS_ASSERTION(!NS_IsMainThread(), "Should not be on main thread.");
-
-  MediaResource* resource = aOmxDecoder->GetResource();
-  MOZ_ASSERT(resource);
-
-  int64_t length = resource->GetCachedDataEnd(0);
-  NS_ENSURE_TRUE(length >= 0, NS_ERROR_UNEXPECTED);
-
-  if (!length) {
-    return NS_OK; // Cache is empty, nothing to do
-  }
-
-  nsAutoArrayPtr<char> buffer(new char[length]);
-
-  nsresult rv = resource->ReadFromCache(buffer.get(), 0, length);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsRefPtr<OmxDecoderNotifyDataArrivedRunnable> runnable(
-    new OmxDecoderNotifyDataArrivedRunnable(aOmxDecoder, buffer.forget(), length, 0));
-
-  rv = NS_DispatchToMainThread(runnable.get());
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  runnable->WaitForCompletion();
-
-  return NS_OK;
-}
-
 namespace layers {
 
 VideoGraphicBuffer::VideoGraphicBuffer(const android::wp<android::OmxDecoder> aOmxDecoder,
                                        android::MediaBuffer *aBuffer,
                                        SurfaceDescriptor& aDescriptor)
   : GraphicBufferLocked(aDescriptor),
     mMediaBuffer(aBuffer),
     mOmxDecoder(aOmxDecoder)
@@ -389,19 +391,17 @@ bool OmxDecoder::TryLoad() {
     durationUs = -1;
     const char* audioMime;
     sp<MetaData> meta = mAudioTrack->getFormat();
 
     if (meta->findCString(kKeyMIMEType, &audioMime) && !strcasecmp(audioMime, AUDIO_MP3)) {
       // Feed MP3 parser with cached data. Local files will be fully
       // cached already, network streams will update with sucessive
       // calls to NotifyDataArrived.
-      nsresult rv = OmxDecoderNotifyDataArrivedRunnable::ProcessCachedData(this);
-
-      if (rv == NS_OK) {
+      if (ProcessCachedData(0, true)) {
         durationUs = mMP3FrameParser.GetDuration();
         if (durationUs > totalDurationUs) {
           totalDurationUs = durationUs;
         }
       }
     }
     if ((durationUs == -1) && meta->findInt64(kKeyDuration, &durationUs)) {
       if (durationUs > totalDurationUs) {
@@ -987,8 +987,48 @@ void OmxDecoder::ReleaseAllPendingVideoB
   for (int i = 0; i < size; i++) {
     MediaBuffer *buffer;
     buffer = releasingVideoBuffers[i];
     buffer->release();
   }
   releasingVideoBuffers.clear();
 }
 
+bool OmxDecoder::ProcessCachedData(int64_t aOffset, bool aWaitForCompletion)
+{
+  // We read data in chunks of 8 MiB. We can reduce this
+  // value if media, such as sdcards, is too slow.
+  static const int64_t sReadSize = 8 * 1024 * 1024;
+
+  NS_ASSERTION(!NS_IsMainThread(), "Should not be on main thread.");
+
+  MOZ_ASSERT(mResource);
+
+  int64_t resourceLength = mResource->GetCachedDataEnd(0);
+  NS_ENSURE_TRUE(resourceLength >= 0, false);
+
+  if (aOffset >= resourceLength) {
+    return true; // Cache is empty, nothing to do
+  }
+
+  int64_t bufferLength = std::min<int64_t>(resourceLength-aOffset, sReadSize);
+
+  nsAutoArrayPtr<char> buffer(new char[bufferLength]);
+
+  nsresult rv = mResource->ReadFromCache(buffer.get(), aOffset, bufferLength);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  nsRefPtr<OmxDecoderNotifyDataArrivedRunnable> runnable(
+    new OmxDecoderNotifyDataArrivedRunnable(this,
+                                            buffer.forget(),
+                                            bufferLength,
+                                            aOffset,
+                                            resourceLength));
+
+  rv = NS_DispatchToMainThread(runnable.get());
+  NS_ENSURE_SUCCESS(rv, false);
+
+  if (aWaitForCompletion) {
+    runnable->WaitForCompletion();
+  }
+
+  return true;
+}
--- a/content/media/omx/OmxDecoder.h
+++ b/content/media/omx/OmxDecoder.h
@@ -220,12 +220,13 @@ public:
   void Pause();
 
   // Post kNotifyPostReleaseVideoBuffer message to OmxDecoder via ALooper.
   void PostReleaseVideoBuffer(MediaBuffer *aBuffer);
   // Receive a message from AHandlerReflector.
   // Called on ALooper thread.
   void onMessageReceived(const sp<AMessage> &msg);
 
+  bool ProcessCachedData(int64_t aOffset, bool aWaitForCompletion);
 };
 
 }