mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-30 05:35:31 +00:00
879 lines
22 KiB
C++
879 lines
22 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "MediaBufferDecoder.h"
|
|
#include "AbstractMediaDecoder.h"
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/ReentrantMonitor.h"
|
|
#include <speex/speex_resampler.h>
|
|
#include "nsXPCOMCIDInternal.h"
|
|
#include "nsComponentManagerUtils.h"
|
|
#include "MediaDecoderReader.h"
|
|
#include "BufferMediaResource.h"
|
|
#include "DecoderTraits.h"
|
|
#include "AudioContext.h"
|
|
#include "AudioBuffer.h"
|
|
#include "nsIScriptGlobalObject.h"
|
|
#include "nsIScriptContext.h"
|
|
#include "nsIScriptObjectPrincipal.h"
|
|
#include "nsIScriptError.h"
|
|
#include "nsMimeTypes.h"
|
|
|
|
#ifdef MOZ_GSTREAMER
|
|
#include "GStreamerReader.h"
|
|
#endif
|
|
#ifdef MOZ_RAW
|
|
#include "RawReader.h"
|
|
#endif
|
|
#ifdef MOZ_OGG
|
|
#include "OggReader.h"
|
|
#endif
|
|
#ifdef MOZ_WAVE
|
|
#include "WaveReader.h"
|
|
#endif
|
|
#ifdef MOZ_WIDGET_GONK
|
|
#include "MediaOmxReader.h"
|
|
#endif
|
|
#ifdef MOZ_MEDIA_PLUGINS
|
|
#include "MediaDecoder.h"
|
|
#include "MediaPluginDecoder.h"
|
|
#include "MediaPluginReader.h"
|
|
#include "MediaPluginHost.h"
|
|
#endif
|
|
#ifdef MOZ_WEBM
|
|
#include "WebMReader.h"
|
|
#endif
|
|
#ifdef MOZ_WMF
|
|
#include "WMFReader.h"
|
|
#endif
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace dom;
|
|
|
|
#ifdef PR_LOGGING
|
|
extern PRLogModuleInfo* gMediaDecoderLog;
|
|
#endif
|
|
|
|
/**
|
|
* This class provides a decoder object which decodes a media file that lives in
|
|
* a memory buffer.
|
|
*/
|
|
class BufferDecoder : public AbstractMediaDecoder
|
|
{
|
|
public:
|
|
// This class holds a weak pointer to MediaResouce. It's the responsibility
|
|
// of the caller to manage the memory of the MediaResource object.
|
|
explicit BufferDecoder(MediaResource* aResource);
|
|
virtual ~BufferDecoder();
|
|
|
|
NS_DECL_ISUPPORTS
|
|
|
|
// This has to be called before decoding begins
|
|
void BeginDecoding(nsIThread* aDecodeThread)
|
|
{
|
|
MOZ_ASSERT(!mDecodeThread && aDecodeThread);
|
|
mDecodeThread = aDecodeThread;
|
|
}
|
|
|
|
virtual ReentrantMonitor& GetReentrantMonitor() MOZ_FINAL MOZ_OVERRIDE;
|
|
|
|
virtual bool IsShutdown() const MOZ_FINAL MOZ_OVERRIDE;
|
|
|
|
virtual bool OnStateMachineThread() const MOZ_FINAL MOZ_OVERRIDE;
|
|
|
|
virtual bool OnDecodeThread() const MOZ_FINAL MOZ_OVERRIDE;
|
|
|
|
virtual MediaResource* GetResource() const MOZ_FINAL MOZ_OVERRIDE;
|
|
|
|
virtual void NotifyBytesConsumed(int64_t aBytes) MOZ_FINAL MOZ_OVERRIDE;
|
|
|
|
virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) MOZ_FINAL MOZ_OVERRIDE;
|
|
|
|
virtual int64_t GetEndMediaTime() const MOZ_FINAL MOZ_OVERRIDE;
|
|
|
|
virtual int64_t GetMediaDuration() MOZ_FINAL MOZ_OVERRIDE;
|
|
|
|
virtual void SetMediaDuration(int64_t aDuration) MOZ_FINAL MOZ_OVERRIDE;
|
|
|
|
virtual void SetMediaSeekable(bool aMediaSeekable) MOZ_OVERRIDE;
|
|
|
|
virtual void SetTransportSeekable(bool aTransportSeekable) MOZ_FINAL MOZ_OVERRIDE;
|
|
|
|
virtual VideoFrameContainer* GetVideoFrameContainer() MOZ_FINAL MOZ_OVERRIDE;
|
|
virtual mozilla::layers::ImageContainer* GetImageContainer() MOZ_FINAL MOZ_OVERRIDE;
|
|
|
|
virtual bool IsTransportSeekable() MOZ_FINAL MOZ_OVERRIDE;
|
|
|
|
virtual bool IsMediaSeekable() MOZ_FINAL MOZ_OVERRIDE;
|
|
|
|
virtual void MetadataLoaded(int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags) MOZ_FINAL MOZ_OVERRIDE;
|
|
virtual void QueueMetadata(int64_t aTime, int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags) MOZ_FINAL MOZ_OVERRIDE;
|
|
|
|
virtual void SetMediaEndTime(int64_t aTime) MOZ_FINAL MOZ_OVERRIDE;
|
|
|
|
virtual void UpdatePlaybackPosition(int64_t aTime) MOZ_FINAL MOZ_OVERRIDE;
|
|
|
|
virtual void OnReadMetadataCompleted() MOZ_FINAL MOZ_OVERRIDE;
|
|
|
|
private:
|
|
// This monitor object is not really used to synchronize access to anything.
|
|
// It's just there in order for us to be able to override
|
|
// GetReentrantMonitor correctly.
|
|
ReentrantMonitor mReentrantMonitor;
|
|
nsCOMPtr<nsIThread> mDecodeThread;
|
|
nsAutoPtr<MediaResource> mResource;
|
|
};
|
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS0(BufferDecoder)
|
|
|
|
BufferDecoder::BufferDecoder(MediaResource* aResource)
|
|
: mReentrantMonitor("BufferDecoder")
|
|
, mResource(aResource)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_COUNT_CTOR(BufferDecoder);
|
|
#ifdef PR_LOGGING
|
|
if (!gMediaDecoderLog) {
|
|
gMediaDecoderLog = PR_NewLogModule("MediaDecoder");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
BufferDecoder::~BufferDecoder()
|
|
{
|
|
// The dtor may run on any thread, we cannot be sure.
|
|
MOZ_COUNT_DTOR(BufferDecoder);
|
|
}
|
|
|
|
ReentrantMonitor&
|
|
BufferDecoder::GetReentrantMonitor()
|
|
{
|
|
return mReentrantMonitor;
|
|
}
|
|
|
|
bool
|
|
BufferDecoder::IsShutdown() const
|
|
{
|
|
// BufferDecoder cannot be shut down.
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
BufferDecoder::OnStateMachineThread() const
|
|
{
|
|
// BufferDecoder doesn't have the concept of a state machine.
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
BufferDecoder::OnDecodeThread() const
|
|
{
|
|
MOZ_ASSERT(mDecodeThread, "Forgot to call BeginDecoding?");
|
|
return IsCurrentThread(mDecodeThread);
|
|
}
|
|
|
|
MediaResource*
|
|
BufferDecoder::GetResource() const
|
|
{
|
|
return mResource;
|
|
}
|
|
|
|
void
|
|
BufferDecoder::NotifyBytesConsumed(int64_t aBytes)
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
void
|
|
BufferDecoder::NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded)
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
int64_t
|
|
BufferDecoder::GetEndMediaTime() const
|
|
{
|
|
// unknown
|
|
return -1;
|
|
}
|
|
|
|
int64_t
|
|
BufferDecoder::GetMediaDuration()
|
|
{
|
|
// unknown
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
BufferDecoder::SetMediaDuration(int64_t aDuration)
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
void
|
|
BufferDecoder::SetMediaSeekable(bool aMediaSeekable)
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
void
|
|
BufferDecoder::SetTransportSeekable(bool aTransportSeekable)
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
VideoFrameContainer*
|
|
BufferDecoder::GetVideoFrameContainer()
|
|
{
|
|
// no video frame
|
|
return nullptr;
|
|
}
|
|
|
|
layers::ImageContainer*
|
|
BufferDecoder::GetImageContainer()
|
|
{
|
|
// no image container
|
|
return nullptr;
|
|
}
|
|
|
|
bool
|
|
BufferDecoder::IsTransportSeekable()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
BufferDecoder::IsMediaSeekable()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void
|
|
BufferDecoder::MetadataLoaded(int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags)
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
void
|
|
BufferDecoder::QueueMetadata(int64_t aTime, int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags)
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
void
|
|
BufferDecoder::SetMediaEndTime(int64_t aTime)
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
void
|
|
BufferDecoder::UpdatePlaybackPosition(int64_t aTime)
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
void
|
|
BufferDecoder::OnReadMetadataCompleted()
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
class ReportResultTask : public nsRunnable
|
|
{
|
|
public:
|
|
ReportResultTask(WebAudioDecodeJob& aDecodeJob,
|
|
WebAudioDecodeJob::ResultFn aFunction,
|
|
WebAudioDecodeJob::ErrorCode aErrorCode)
|
|
: mDecodeJob(aDecodeJob)
|
|
, mFunction(aFunction)
|
|
, mErrorCode(aErrorCode)
|
|
{
|
|
MOZ_ASSERT(aFunction);
|
|
}
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
(mDecodeJob.*mFunction)(mErrorCode);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
// Note that the mDecodeJob member will probably die when mFunction is run.
|
|
// Therefore, it is not safe to do anything fancy with it in this class.
|
|
// Really, this class is only used because nsRunnableMethod doesn't support
|
|
// methods accepting arguments.
|
|
WebAudioDecodeJob& mDecodeJob;
|
|
WebAudioDecodeJob::ResultFn mFunction;
|
|
WebAudioDecodeJob::ErrorCode mErrorCode;
|
|
};
|
|
|
|
MOZ_BEGIN_ENUM_CLASS(PhaseEnum, int)
|
|
Decode,
|
|
AllocateBuffer,
|
|
CopyBuffer,
|
|
Done
|
|
MOZ_END_ENUM_CLASS(PhaseEnum)
|
|
|
|
class MediaDecodeTask : public nsRunnable
|
|
{
|
|
public:
|
|
MediaDecodeTask(const char* aContentType, uint8_t* aBuffer,
|
|
uint32_t aLength,
|
|
WebAudioDecodeJob& aDecodeJob,
|
|
nsIThreadPool* aThreadPool)
|
|
: mContentType(aContentType)
|
|
, mBuffer(aBuffer)
|
|
, mLength(aLength)
|
|
, mDecodeJob(aDecodeJob)
|
|
, mPhase(PhaseEnum::Decode)
|
|
, mThreadPool(aThreadPool)
|
|
{
|
|
MOZ_ASSERT(aBuffer);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsCOMPtr<nsPIDOMWindow> pWindow = do_QueryInterface(mDecodeJob.mContext->GetParentObject());
|
|
nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
|
|
do_QueryInterface(pWindow);
|
|
if (scriptPrincipal) {
|
|
mPrincipal = scriptPrincipal->GetPrincipal();
|
|
}
|
|
}
|
|
|
|
NS_IMETHOD Run();
|
|
bool CreateReader();
|
|
|
|
private:
|
|
void ReportFailureOnMainThread(WebAudioDecodeJob::ErrorCode aErrorCode) {
|
|
if (NS_IsMainThread()) {
|
|
Cleanup();
|
|
mDecodeJob.OnFailure(aErrorCode);
|
|
} else {
|
|
// Take extra care to cleanup on the main thread
|
|
NS_DispatchToMainThread(NS_NewRunnableMethod(this, &MediaDecodeTask::Cleanup),
|
|
NS_DISPATCH_NORMAL);
|
|
|
|
nsCOMPtr<nsIRunnable> event =
|
|
new ReportResultTask(mDecodeJob, &WebAudioDecodeJob::OnFailure, aErrorCode);
|
|
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
|
}
|
|
}
|
|
|
|
void Decode();
|
|
void AllocateBuffer();
|
|
void CopyBuffer();
|
|
void CallbackTheResult();
|
|
|
|
void Cleanup()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
mBufferDecoder = nullptr;
|
|
mDecoderReader = nullptr;
|
|
}
|
|
|
|
private:
|
|
nsCString mContentType;
|
|
uint8_t* mBuffer;
|
|
uint32_t mLength;
|
|
WebAudioDecodeJob& mDecodeJob;
|
|
PhaseEnum mPhase;
|
|
nsCOMPtr<nsIThreadPool> mThreadPool;
|
|
nsCOMPtr<nsIPrincipal> mPrincipal;
|
|
nsRefPtr<BufferDecoder> mBufferDecoder;
|
|
nsAutoPtr<MediaDecoderReader> mDecoderReader;
|
|
};
|
|
|
|
NS_IMETHODIMP
|
|
MediaDecodeTask::Run()
|
|
{
|
|
MOZ_ASSERT(mBufferDecoder);
|
|
MOZ_ASSERT(mDecoderReader);
|
|
switch (mPhase) {
|
|
case PhaseEnum::Decode:
|
|
Decode();
|
|
break;
|
|
case PhaseEnum::AllocateBuffer:
|
|
AllocateBuffer();
|
|
break;
|
|
case PhaseEnum::CopyBuffer:
|
|
CopyBuffer();
|
|
break;
|
|
case PhaseEnum::Done:
|
|
CallbackTheResult();
|
|
break;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
MediaDecodeTask::CreateReader()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
BufferMediaResource* resource =
|
|
new BufferMediaResource(static_cast<uint8_t*> (mBuffer),
|
|
mLength, mPrincipal);
|
|
|
|
MOZ_ASSERT(!mBufferDecoder);
|
|
mBufferDecoder = new BufferDecoder(resource);
|
|
|
|
// If you change this list to add support for new decoders, please consider
|
|
// updating nsHTMLMediaElement::CreateDecoder as well.
|
|
|
|
#ifdef MOZ_GSTREAMER
|
|
if (DecoderTraits::IsGStreamerSupportedType(mContentType)) {
|
|
mDecoderReader = new GStreamerReader(mBufferDecoder);
|
|
} else
|
|
#endif
|
|
#ifdef MOZ_RAW
|
|
if (DecoderTraits::IsRawType(mContentType)) {
|
|
mDecoderReader = new RawReader(mBufferDecoder);
|
|
} else
|
|
#endif
|
|
#ifdef MOZ_OGG
|
|
if (DecoderTraits::IsOggType(mContentType)) {
|
|
mDecoderReader = new OggReader(mBufferDecoder);
|
|
} else
|
|
#endif
|
|
#ifdef MOZ_WAVE
|
|
if (DecoderTraits::IsWaveType(mContentType)) {
|
|
mDecoderReader = new WaveReader(mBufferDecoder);
|
|
} else
|
|
#endif
|
|
#ifdef MOZ_WIDGET_GONK
|
|
if (DecoderTraits::IsOmxSupportedType(mContentType)) {
|
|
mDecoderReader = new MediaOmxReader(mBufferDecoder);
|
|
} else
|
|
#endif
|
|
#ifdef MOZ_MEDIA_PLUGINS
|
|
if (MediaDecoder::IsMediaPluginsEnabled() &&
|
|
GetMediaPluginHost()->FindDecoder(mContentType, nullptr)) {
|
|
mDecoderReader = new MediaPluginReader(mBufferDecoder, mContentType);
|
|
} else
|
|
#endif
|
|
#ifdef MOZ_WEBM
|
|
if (DecoderTraits::IsWebMType(mContentType)) {
|
|
mDecoderReader = new WebMReader(mBufferDecoder);
|
|
} else
|
|
#endif
|
|
#ifdef MOZ_WMF
|
|
if (DecoderTraits::IsWMFSupportedType(mContentType)) {
|
|
mDecoderReader = new WMFReader(mBufferDecoder);
|
|
} else
|
|
#endif
|
|
#ifdef MOZ_DASH
|
|
// The DASH decoder is not supported.
|
|
#endif
|
|
if (false) {} // dummy if to take care of the dangling else
|
|
|
|
if (!mDecoderReader) {
|
|
return false;
|
|
}
|
|
|
|
nsresult rv = mDecoderReader->Init(nullptr);
|
|
if (NS_FAILED(rv)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
MediaDecodeTask::Decode()
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
mDecoderReader->OnDecodeThreadStart();
|
|
|
|
mBufferDecoder->BeginDecoding(NS_GetCurrentThread());
|
|
|
|
VideoInfo videoInfo;
|
|
nsAutoPtr<MetadataTags> tags;
|
|
nsresult rv = mDecoderReader->ReadMetadata(&videoInfo, getter_Transfers(tags));
|
|
if (NS_FAILED(rv)) {
|
|
ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
|
|
return;
|
|
}
|
|
|
|
if (!mDecoderReader->HasAudio()) {
|
|
ReportFailureOnMainThread(WebAudioDecodeJob::NoAudio);
|
|
return;
|
|
}
|
|
|
|
while (mDecoderReader->DecodeAudioData()) {
|
|
// consume all of the buffer
|
|
continue;
|
|
}
|
|
|
|
mDecoderReader->OnDecodeThreadFinish();
|
|
|
|
MediaQueue<AudioData>& audioQueue = mDecoderReader->AudioQueue();
|
|
uint32_t frameCount = audioQueue.FrameCount();
|
|
uint32_t channelCount = videoInfo.mAudioChannels;
|
|
uint32_t sampleRate = videoInfo.mAudioRate;
|
|
|
|
if (!frameCount || !channelCount || !sampleRate) {
|
|
ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
|
|
return;
|
|
}
|
|
|
|
// Ask the main thread to allocate a large enough typed array to fit the data
|
|
mDecodeJob.mFrames = frameCount;
|
|
mDecodeJob.mChannels = channelCount;
|
|
mDecodeJob.mSourceSampleRate = sampleRate;
|
|
|
|
const uint32_t destSampleRate = mDecodeJob.mContext->SampleRate();
|
|
|
|
mDecodeJob.mResampledFrames = mDecodeJob.mFrames;
|
|
if (mDecodeJob.mSourceSampleRate != destSampleRate) {
|
|
mDecodeJob.mResampledFrames = static_cast<uint32_t>(
|
|
static_cast<uint64_t>(destSampleRate) *
|
|
static_cast<uint64_t>(frameCount) /
|
|
static_cast<uint64_t>(mDecodeJob.mSourceSampleRate)
|
|
);
|
|
}
|
|
|
|
mPhase = PhaseEnum::AllocateBuffer;
|
|
NS_DispatchToMainThread(this);
|
|
}
|
|
|
|
void
|
|
MediaDecodeTask::AllocateBuffer()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!mDecodeJob.AllocateBuffer()) {
|
|
ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError);
|
|
return;
|
|
}
|
|
|
|
mPhase = PhaseEnum::CopyBuffer;
|
|
mThreadPool->Dispatch(this, nsIThreadPool::DISPATCH_NORMAL);
|
|
}
|
|
|
|
void
|
|
MediaDecodeTask::CopyBuffer()
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(mDecodeJob.mOutput);
|
|
MOZ_ASSERT(mDecodeJob.mChannels);
|
|
MOZ_ASSERT(mDecoderReader);
|
|
|
|
SpeexResamplerState* resampler = nullptr;
|
|
|
|
MediaQueue<AudioData>& audioQueue = mDecoderReader->AudioQueue();
|
|
|
|
const uint32_t destSampleRate = mDecodeJob.mContext->SampleRate();
|
|
if (mDecodeJob.mSourceSampleRate != destSampleRate) {
|
|
resampler = speex_resampler_init(mDecodeJob.mChannels,
|
|
mDecodeJob.mSourceSampleRate,
|
|
destSampleRate,
|
|
SPEEX_RESAMPLER_QUALITY_DEFAULT, nullptr);
|
|
}
|
|
|
|
uint32_t framesCopied = 0;
|
|
|
|
nsAutoPtr<AudioData> audioData;
|
|
while ((audioData = audioQueue.PopFront())) {
|
|
audioData->EnsureAudioBuffer(); // could lead to a copy :(
|
|
AudioDataValue* bufferData = static_cast<AudioDataValue*>
|
|
(audioData->mAudioBuffer->Data());
|
|
|
|
AudioDataValue* resampledBuffer = bufferData;
|
|
|
|
// We cannot use mDecodeJob.mResampledFrames here because we are dealing
|
|
// with only part of the audio frames, not all of it.
|
|
const uint32_t expectedOutSamples = static_cast<uint32_t>(
|
|
static_cast<uint64_t>(destSampleRate) *
|
|
static_cast<uint64_t>(audioData->mFrames) /
|
|
static_cast<uint64_t>(mDecodeJob.mSourceSampleRate)
|
|
);
|
|
if (mDecodeJob.mSourceSampleRate != destSampleRate) {
|
|
static const fallible_t fallible = fallible_t();
|
|
resampledBuffer = new(fallible) AudioDataValue[mDecodeJob.mChannels * expectedOutSamples];
|
|
|
|
if (!resampledBuffer) {
|
|
// Out of memory!
|
|
ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError);
|
|
return;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < audioData->mChannels; ++i) {
|
|
uint32_t inSamples = audioData->mFrames;
|
|
uint32_t outSamples = expectedOutSamples;
|
|
|
|
#ifdef MOZ_SAMPLE_TYPE_S16
|
|
speex_resampler_process_int(resampler, i, bufferData, &inSamples,
|
|
&resampledBuffer[i * expectedOutSamples],
|
|
&outSamples);
|
|
#else
|
|
speex_resampler_process_float(resampler, i, bufferData, &inSamples,
|
|
&resampledBuffer[i * expectedOutSamples],
|
|
&outSamples);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < audioData->mChannels; ++i) {
|
|
ConvertAudioSamples(&resampledBuffer[i * expectedOutSamples],
|
|
&mDecodeJob.mChannelBuffers[i].second[framesCopied],
|
|
expectedOutSamples);
|
|
}
|
|
framesCopied += expectedOutSamples;
|
|
|
|
if (resampledBuffer != bufferData) {
|
|
delete[] resampledBuffer;
|
|
}
|
|
}
|
|
|
|
if (resampler) {
|
|
speex_resampler_destroy(resampler);
|
|
}
|
|
|
|
mPhase = PhaseEnum::Done;
|
|
NS_DispatchToMainThread(this);
|
|
}
|
|
|
|
void
|
|
MediaDecodeTask::CallbackTheResult()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
Cleanup();
|
|
|
|
// Now, we're ready to call the script back with the resulting buffer
|
|
if (!mDecodeJob.FinalizeBufferData()) {
|
|
ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError);
|
|
}
|
|
|
|
mDecodeJob.OnSuccess(WebAudioDecodeJob::NoError);
|
|
}
|
|
|
|
bool
|
|
WebAudioDecodeJob::FinalizeBufferData()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mOutput);
|
|
MOZ_ASSERT(mChannels == mChannelBuffers.Length());
|
|
|
|
JSContext* cx = GetJSContext();
|
|
if (!cx) {
|
|
return false;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < mChannels; ++i) {
|
|
mOutput->SetChannelDataFromArrayBufferContents(cx, i, mChannelBuffers[i].first);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
JSContext*
|
|
WebAudioDecodeJob::GetJSContext() const
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsCOMPtr<nsIScriptGlobalObject> scriptGlobal =
|
|
do_QueryInterface(mContext->GetParentObject());
|
|
nsIScriptContext* scriptContext = scriptGlobal->GetContext();
|
|
if (!scriptContext) {
|
|
return nullptr;
|
|
}
|
|
return scriptContext->GetNativeContext();
|
|
}
|
|
|
|
bool
|
|
WebAudioDecodeJob::AllocateBuffer()
|
|
{
|
|
MOZ_ASSERT(!mOutput);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// First, get a JSContext
|
|
JSContext* cx = GetJSContext();
|
|
if (!cx) {
|
|
return false;
|
|
}
|
|
|
|
// Now create the AudioBuffer
|
|
mOutput = new AudioBuffer(mContext, mResampledFrames, mContext->SampleRate());
|
|
if (!mOutput->InitializeBuffers(mChannels, cx)) {
|
|
return false;
|
|
}
|
|
|
|
if (!mChannelBuffers.SetCapacity(mChannels)) {
|
|
return false;
|
|
}
|
|
for (uint32_t i = 0; i < mChannels; ++i) {
|
|
JSObject* channelObj = mOutput->GetChannelData(i);
|
|
JSObject* arrayBuffer = JS_GetArrayBufferViewBuffer(channelObj);
|
|
void* contents;
|
|
uint8_t* data;
|
|
if (JS_FALSE == JS_StealArrayBufferContents(cx, arrayBuffer, &contents, &data)) {
|
|
return false;
|
|
}
|
|
mChannelBuffers.AppendElement(
|
|
std::make_pair(contents, reinterpret_cast<float*>(data)));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
MediaBufferDecoder::AsyncDecodeMedia(const char* aContentType, uint8_t* aBuffer,
|
|
uint32_t aLength,
|
|
WebAudioDecodeJob& aDecodeJob)
|
|
{
|
|
// Do not attempt to decode the media if we were not successful at sniffing
|
|
// the content type.
|
|
if (!*aContentType ||
|
|
strcmp(aContentType, APPLICATION_OCTET_STREAM) == 0) {
|
|
nsCOMPtr<nsIRunnable> event =
|
|
new ReportResultTask(aDecodeJob,
|
|
&WebAudioDecodeJob::OnFailure,
|
|
WebAudioDecodeJob::UnknownContent);
|
|
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
|
return;
|
|
}
|
|
|
|
if (!EnsureThreadPoolInitialized()) {
|
|
nsCOMPtr<nsIRunnable> event =
|
|
new ReportResultTask(aDecodeJob,
|
|
&WebAudioDecodeJob::OnFailure,
|
|
WebAudioDecodeJob::UnknownError);
|
|
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(mThreadPool);
|
|
|
|
nsRefPtr<MediaDecodeTask> task =
|
|
new MediaDecodeTask(aContentType, aBuffer, aLength, aDecodeJob, mThreadPool);
|
|
if (!task->CreateReader()) {
|
|
nsCOMPtr<nsIRunnable> event =
|
|
new ReportResultTask(aDecodeJob,
|
|
&WebAudioDecodeJob::OnFailure,
|
|
WebAudioDecodeJob::UnknownError);
|
|
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
|
} else {
|
|
mThreadPool->Dispatch(task, nsIThreadPool::DISPATCH_NORMAL);
|
|
}
|
|
}
|
|
|
|
bool
|
|
MediaBufferDecoder::EnsureThreadPoolInitialized()
|
|
{
|
|
if (!mThreadPool) {
|
|
mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
|
|
if (!mThreadPool) {
|
|
return false;
|
|
}
|
|
mThreadPool->SetName(NS_LITERAL_CSTRING("MediaBufferDecoder"));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void
|
|
MediaBufferDecoder::Shutdown() {
|
|
if (mThreadPool) {
|
|
mThreadPool->Shutdown();
|
|
mThreadPool = nullptr;
|
|
}
|
|
MOZ_ASSERT(!mThreadPool);
|
|
}
|
|
|
|
WebAudioDecodeJob::WebAudioDecodeJob(const nsACString& aContentType,
|
|
const ArrayBuffer& aBuffer,
|
|
AudioContext* aContext,
|
|
DecodeSuccessCallback* aSuccessCallback,
|
|
DecodeErrorCallback* aFailureCallback)
|
|
: mContentType(aContentType)
|
|
, mBuffer(aBuffer.Data())
|
|
, mLength(aBuffer.Length())
|
|
, mChannels(0)
|
|
, mSourceSampleRate(0)
|
|
, mFrames(0)
|
|
, mResampledFrames(0)
|
|
, mContext(aContext)
|
|
, mSuccessCallback(aSuccessCallback)
|
|
, mFailureCallback(aFailureCallback)
|
|
{
|
|
MOZ_ASSERT(aContext);
|
|
MOZ_ASSERT(aSuccessCallback);
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_COUNT_CTOR(WebAudioDecodeJob);
|
|
}
|
|
|
|
WebAudioDecodeJob::~WebAudioDecodeJob()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_COUNT_DTOR(WebAudioDecodeJob);
|
|
}
|
|
|
|
void
|
|
WebAudioDecodeJob::OnSuccess(ErrorCode aErrorCode)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aErrorCode == NoError);
|
|
|
|
// Ignore errors in calling the callback, since there is not much that we can
|
|
// do about it here.
|
|
ErrorResult rv;
|
|
mSuccessCallback->Call(*mOutput, rv);
|
|
|
|
mContext->RemoveFromDecodeQueue(this);
|
|
}
|
|
|
|
void
|
|
WebAudioDecodeJob::OnFailure(ErrorCode aErrorCode)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
const char* errorMessage;
|
|
switch (aErrorCode) {
|
|
case NoError:
|
|
MOZ_ASSERT(false, "Who passed NoError to OnFailure?");
|
|
// Fall through to get some sort of a sane error message if this actually
|
|
// happens at runtime.
|
|
case UnknownError:
|
|
errorMessage = "MediaDecodeAudioDataUnknownError";
|
|
break;
|
|
case UnknownContent:
|
|
errorMessage = "MediaDecodeAudioDataUnknownContentType";
|
|
break;
|
|
case InvalidContent:
|
|
errorMessage = "MediaDecodeAudioDataInvalidContent";
|
|
break;
|
|
case NoAudio:
|
|
errorMessage = "MediaDecodeAudioDataNoAudio";
|
|
break;
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindow> pWindow = do_QueryInterface(mContext->GetParentObject());
|
|
nsIDocument* doc = nullptr;
|
|
if (pWindow) {
|
|
doc = pWindow->GetExtantDoc();
|
|
}
|
|
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
|
|
"Media",
|
|
doc,
|
|
nsContentUtils::eDOM_PROPERTIES,
|
|
errorMessage);
|
|
|
|
// Ignore errors in calling the callback, since there is not much that we can
|
|
// do about it here.
|
|
ErrorResult rv;
|
|
mFailureCallback->Call(rv);
|
|
|
|
mContext->RemoveFromDecodeQueue(this);
|
|
}
|
|
|
|
}
|
|
|