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.

--HG--
extra : rebase_source : 4effb86db481c405a97760cd98f4218dde42104d
This commit is contained in:
Thomas Zimmermann 2013-05-06 20:07:29 +02:00
Родитель 5632b9f96e
Коммит c78497c07a
2 изменённых файлов: 84 добавлений и 43 удалений

Просмотреть файл

@ -40,22 +40,50 @@ 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)
{
@ -78,6 +106,13 @@ public:
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;
@ -93,8 +128,6 @@ public:
}
}
static nsresult ProcessCachedData(android::OmxDecoder* aOmxDecoder);
private:
// Call this function at the end of Run() to notify waiting
// threads.
@ -110,43 +143,12 @@ private:
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,
@ -394,9 +396,7 @@ bool OmxDecoder::TryLoad() {
// 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;
@ -992,3 +992,43 @@ void OmxDecoder::ReleaseAllPendingVideoBuffersLocked()
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;
}

Просмотреть файл

@ -225,6 +225,7 @@ public:
// Called on ALooper thread.
void onMessageReceived(const sp<AMessage> &msg);
bool ProcessCachedData(int64_t aOffset, bool aWaitForCompletion);
};
}