Bug 831224: Parse MP3 frame headers in OmxDecoder, r=padenot

On FirefoxOS, the Android libraries estimate the duration of MP3 streams
by examining the first MP3 frame header. This only works for streams with
constant bit rate. For streams with variable bit rate, a too short or too
long duration is computed.

This patch adds support for parsing MP3 frame headers. The decoder handles
file streams by reading them at once at the beginning and parsing them
immediately. Network streams are updated when a new fragment arrives.
This commit is contained in:
Thomas Zimmermann 2013-01-24 13:38:32 +01:00
Родитель 39f6f8472e
Коммит a9843f607d
5 изменённых файлов: 171 добавлений и 5 удалений

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

@ -519,8 +519,8 @@ public:
return functor.mResult;
}
// Only used by WebMReader for now, so stub here rather than in every
// reader than inherits from MediaDecoderReader.
// Only used by WebMReader and MediaOmxReader for now, so stub here rather
// than in every reader than inherits from MediaDecoderReader.
virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) {}
virtual MediaQueue<AudioData>& AudioQueue() { return mAudioQueue; }

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

@ -270,6 +270,15 @@ bool MediaOmxReader::DecodeVideoFrame(bool &aKeyframeSkip,
return true;
}
void MediaOmxReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset)
{
android::OmxDecoder *omxDecoder = mOmxDecoder.get();
if (omxDecoder) {
omxDecoder->NotifyDataArrived(aBuffer, aLength, aOffset);
}
}
bool MediaOmxReader::DecodeAudioData()
{
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");

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

@ -40,6 +40,8 @@ public:
virtual nsresult Init(MediaDecoderReader* aCloneDonor);
virtual nsresult ResetDecode();
virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset);
virtual bool DecodeAudioData();
virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
int64_t aTimeThreshold);

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

@ -18,6 +18,8 @@
#include "mozilla/Preferences.h"
#include "mozilla/Types.h"
#include "mozilla/Monitor.h"
#include "nsMimeTypes.h"
#include "MPAPI.h"
#include "prlog.h"
@ -37,6 +39,114 @@ using namespace MPAPI;
using namespace mozilla;
namespace mozilla {
// 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.
//
// 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.
class OmxDecoderNotifyDataArrivedRunnable : public nsRunnable
{
public:
OmxDecoderNotifyDataArrivedRunnable(android::OmxDecoder* aOmxDecoder, const char* aBuffer, uint64_t aLength, int64_t aOffset)
: mOmxDecoder(aOmxDecoder),
mBuffer(aBuffer),
mLength(aLength),
mOffset(aOffset),
mCompletedMonitor("OmxDecoderNotifyDataArrived.mCompleted"),
mCompleted(false)
{
MOZ_ASSERT(mOmxDecoder.get());
MOZ_ASSERT(mBuffer.get() || !mLength);
}
NS_IMETHOD Run()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
const char* buffer = mBuffer.get();
while (mLength) {
uint32_t length = std::min<uint64_t>(mLength, UINT32_MAX);
mOmxDecoder->NotifyDataArrived(mBuffer.get(), mLength, mOffset);
buffer += length;
mLength -= length;
mOffset += length;
}
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;
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,
@ -147,6 +257,7 @@ OmxDecoder::OmxDecoder(MediaResource *aResource,
mAudioChannels(-1),
mAudioSampleRate(-1),
mDurationUs(-1),
mMP3FrameParser(aResource->GetLength()),
mVideoBuffer(nullptr),
mAudioBuffer(nullptr),
mIsVideoSeeking(false),
@ -274,9 +385,29 @@ bool OmxDecoder::TryLoad() {
if (durationUs > totalDurationUs)
totalDurationUs = durationUs;
}
if (mAudioTrack.get() && mAudioTrack->getFormat()->findInt64(kKeyDuration, &durationUs)) {
if (durationUs > totalDurationUs)
totalDurationUs = durationUs;
if (mAudioTrack.get()) {
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) {
durationUs = mMP3FrameParser.GetDuration();
if (durationUs > totalDurationUs) {
totalDurationUs = durationUs;
}
}
}
if ((durationUs == -1) && meta->findInt64(kKeyDuration, &durationUs)) {
if (durationUs > totalDurationUs) {
totalDurationUs = durationUs;
}
}
}
mDurationUs = totalDurationUs;
@ -485,6 +616,25 @@ bool OmxDecoder::SetAudioFormat() {
return true;
}
void OmxDecoder::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset)
{
if (!mMP3FrameParser.IsMP3()) {
return;
}
mMP3FrameParser.NotifyDataArrived(aBuffer, aLength, aOffset);
int64_t durationUs = mMP3FrameParser.GetDuration();
if (durationUs != mDurationUs) {
mDurationUs = durationUs;
MOZ_ASSERT(mDecoder);
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
mDecoder->UpdateMediaDuration(mDurationUs);
}
}
void OmxDecoder::ReleaseVideoBuffer() {
if (mVideoBuffer) {
mVideoBuffer->release();

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

@ -9,6 +9,7 @@
#include "GonkNativeWindow.h"
#include "GonkNativeWindowClient.h"
#include "GrallocImages.h"
#include "MP3FrameParser.h"
#include "MPAPI.h"
#include "MediaResource.h"
#include "AbstractMediaDecoder.h"
@ -76,6 +77,7 @@ private:
class OmxDecoder : public OMXCodecProxy::EventListener {
typedef MPAPI::AudioFrame AudioFrame;
typedef MPAPI::VideoFrame VideoFrame;
typedef mozilla::MP3FrameParser MP3FrameParser;
typedef mozilla::MediaResource MediaResource;
typedef mozilla::AbstractMediaDecoder AbstractMediaDecoder;
@ -109,6 +111,7 @@ class OmxDecoder : public OMXCodecProxy::EventListener {
int64_t mDurationUs;
VideoFrame mVideoFrame;
AudioFrame mAudioFrame;
MP3FrameParser mMP3FrameParser;
// Lifetime of these should be handled by OMXCodec, as long as we release
// them after use: see ReleaseVideoBuffer(), ReleaseAudioBuffer()
@ -177,6 +180,8 @@ public:
bool SetVideoFormat();
bool SetAudioFormat();
void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset);
void GetDuration(int64_t *durationUs) {
*durationUs = mDurationUs;
}