2013-02-02 02:13:23 +04:00
|
|
|
/* -*- 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"
|
2013-09-04 07:08:11 +04:00
|
|
|
#include "BufferDecoder.h"
|
2013-08-15 23:44:14 +04:00
|
|
|
#include "mozilla/dom/AudioContextBinding.h"
|
2016-12-21 12:52:50 +03:00
|
|
|
#include "mozilla/dom/BaseAudioContextBinding.h"
|
2016-12-13 09:00:15 +03:00
|
|
|
#include "mozilla/dom/DOMException.h"
|
2014-05-09 22:51:56 +04:00
|
|
|
#include "mozilla/dom/ScriptSettings.h"
|
2016-11-29 08:03:36 +03:00
|
|
|
#include "mozilla/AbstractThread.h"
|
2013-02-02 02:13:23 +04:00
|
|
|
#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"
|
2017-01-18 03:59:03 +03:00
|
|
|
#include "MediaContainerType.h"
|
2015-10-26 19:14:47 +03:00
|
|
|
#include "nsContentUtils.h"
|
2013-02-02 02:13:23 +04:00
|
|
|
#include "nsIScriptObjectPrincipal.h"
|
|
|
|
#include "nsIScriptError.h"
|
|
|
|
#include "nsMimeTypes.h"
|
2014-12-11 01:03:56 +03:00
|
|
|
#include "VideoUtils.h"
|
2013-10-01 10:22:57 +04:00
|
|
|
#include "WebAudioUtils.h"
|
2014-10-23 14:07:48 +04:00
|
|
|
#include "mozilla/dom/Promise.h"
|
2015-11-25 02:52:48 +03:00
|
|
|
#include "mozilla/Telemetry.h"
|
|
|
|
#include "nsPrintfCString.h"
|
2013-02-02 02:13:23 +04:00
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
|
2015-11-25 02:52:48 +03:00
|
|
|
extern LazyLogModule gMediaDecoderLog;
|
|
|
|
|
2013-02-12 01:56:57 +04:00
|
|
|
using namespace dom;
|
|
|
|
|
2016-04-26 03:23:21 +03:00
|
|
|
class ReportResultTask final : public Runnable
|
2013-02-02 02:13:23 +04:00
|
|
|
{
|
|
|
|
public:
|
|
|
|
ReportResultTask(WebAudioDecodeJob& aDecodeJob,
|
|
|
|
WebAudioDecodeJob::ResultFn aFunction,
|
|
|
|
WebAudioDecodeJob::ErrorCode aErrorCode)
|
2017-06-21 14:59:26 +03:00
|
|
|
: mDecodeJob(aDecodeJob)
|
2013-02-02 02:13:23 +04:00
|
|
|
, mFunction(aFunction)
|
|
|
|
, mErrorCode(aErrorCode)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(aFunction);
|
|
|
|
}
|
|
|
|
|
2016-08-08 05:18:10 +03:00
|
|
|
NS_IMETHOD Run() override
|
2013-02-02 02:13:23 +04:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2015-04-28 09:42:00 +03:00
|
|
|
enum class PhaseEnum : int
|
|
|
|
{
|
2013-02-02 02:13:23 +04:00
|
|
|
Decode,
|
|
|
|
AllocateBuffer,
|
|
|
|
Done
|
2015-01-26 01:22:07 +03:00
|
|
|
};
|
2013-02-02 02:13:23 +04:00
|
|
|
|
2016-04-26 03:23:21 +03:00
|
|
|
class MediaDecodeTask final : public Runnable
|
2013-02-02 02:13:23 +04:00
|
|
|
{
|
|
|
|
public:
|
2017-06-21 14:59:26 +03:00
|
|
|
MediaDecodeTask(const MediaContainerType& aContainerType, uint8_t* aBuffer,
|
2013-02-02 02:13:23 +04:00
|
|
|
uint32_t aLength,
|
2014-12-11 01:03:56 +03:00
|
|
|
WebAudioDecodeJob& aDecodeJob)
|
2017-06-21 14:59:26 +03:00
|
|
|
: mContainerType(aContainerType)
|
2013-02-02 02:13:23 +04:00
|
|
|
, mBuffer(aBuffer)
|
|
|
|
, mLength(aLength)
|
|
|
|
, mDecodeJob(aDecodeJob)
|
|
|
|
, mPhase(PhaseEnum::Decode)
|
2015-05-18 09:13:20 +03:00
|
|
|
, mFirstFrameDecoded(false)
|
2013-02-02 02:13:23 +04:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(aBuffer);
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHOD Run();
|
|
|
|
bool CreateReader();
|
2014-12-11 01:03:56 +03:00
|
|
|
MediaDecoderReader* Reader() { MOZ_ASSERT(mDecoderReader); return mDecoderReader; }
|
2013-02-02 02:13:23 +04:00
|
|
|
|
|
|
|
private:
|
|
|
|
void ReportFailureOnMainThread(WebAudioDecodeJob::ErrorCode aErrorCode) {
|
|
|
|
if (NS_IsMainThread()) {
|
|
|
|
Cleanup();
|
|
|
|
mDecodeJob.OnFailure(aErrorCode);
|
|
|
|
} else {
|
|
|
|
// Take extra care to cleanup on the main thread
|
2017-06-21 14:59:26 +03:00
|
|
|
mMainThread->Dispatch(NewRunnableMethod(this, &MediaDecodeTask::Cleanup));
|
|
|
|
|
2013-02-02 02:13:23 +04:00
|
|
|
|
|
|
|
nsCOMPtr<nsIRunnable> event =
|
|
|
|
new ReportResultTask(mDecodeJob, &WebAudioDecodeJob::OnFailure, aErrorCode);
|
2017-04-04 14:47:47 +03:00
|
|
|
mMainThread->Dispatch(event.forget());
|
2013-02-02 02:13:23 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Decode();
|
2017-05-18 05:20:44 +03:00
|
|
|
void OnMetadataRead(MetadataHolder&& aMetadata);
|
2016-09-10 12:56:50 +03:00
|
|
|
void OnMetadataNotRead(const MediaResult& aError);
|
2014-12-11 01:03:56 +03:00
|
|
|
void RequestSample();
|
2017-05-11 03:30:39 +03:00
|
|
|
void SampleDecoded(RefPtr<AudioData> aData);
|
2016-09-10 09:48:53 +03:00
|
|
|
void SampleNotDecoded(const MediaResult& aError);
|
2014-12-11 01:03:56 +03:00
|
|
|
void FinishDecode();
|
2013-02-02 02:13:23 +04:00
|
|
|
void AllocateBuffer();
|
|
|
|
void CallbackTheResult();
|
|
|
|
|
|
|
|
void Cleanup()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
2014-03-10 01:27:15 +04:00
|
|
|
// MediaDecoderReader expects that BufferDecoder is alive.
|
|
|
|
// Destruct MediaDecoderReader first.
|
2013-02-02 02:13:23 +04:00
|
|
|
mDecoderReader = nullptr;
|
2014-03-10 01:27:15 +04:00
|
|
|
mBufferDecoder = nullptr;
|
2014-05-13 16:09:44 +04:00
|
|
|
JS_free(nullptr, mBuffer);
|
2013-02-02 02:13:23 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2017-01-18 03:59:03 +03:00
|
|
|
MediaContainerType mContainerType;
|
2013-02-02 02:13:23 +04:00
|
|
|
uint8_t* mBuffer;
|
|
|
|
uint32_t mLength;
|
|
|
|
WebAudioDecodeJob& mDecodeJob;
|
|
|
|
PhaseEnum mPhase;
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<BufferDecoder> mBufferDecoder;
|
|
|
|
RefPtr<MediaDecoderReader> mDecoderReader;
|
2014-12-11 01:03:56 +03:00
|
|
|
MediaInfo mMediaInfo;
|
2017-03-22 06:25:23 +03:00
|
|
|
MediaQueue<AudioData> mAudioQueue;
|
2017-04-04 14:47:47 +03:00
|
|
|
RefPtr<AbstractThread> mMainThread;
|
2015-05-18 09:13:20 +03:00
|
|
|
bool mFirstFrameDecoded;
|
2013-02-02 02:13:23 +04:00
|
|
|
};
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
|
|
|
MediaDecodeTask::Run()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(mBufferDecoder);
|
|
|
|
MOZ_ASSERT(mDecoderReader);
|
|
|
|
switch (mPhase) {
|
|
|
|
case PhaseEnum::Decode:
|
|
|
|
Decode();
|
|
|
|
break;
|
|
|
|
case PhaseEnum::AllocateBuffer:
|
|
|
|
AllocateBuffer();
|
|
|
|
break;
|
|
|
|
case PhaseEnum::Done:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
MediaDecodeTask::CreateReader()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
2016-11-29 08:03:36 +03:00
|
|
|
nsPIDOMWindowInner* parent = mDecodeJob.mContext->GetParentObject();
|
|
|
|
MOZ_ASSERT(parent);
|
2015-05-13 21:08:30 +03:00
|
|
|
|
|
|
|
nsCOMPtr<nsIPrincipal> principal;
|
2016-11-29 08:03:36 +03:00
|
|
|
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(parent);
|
2015-05-13 21:08:30 +03:00
|
|
|
if (sop) {
|
|
|
|
principal = sop->GetPrincipal();
|
|
|
|
}
|
|
|
|
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<BufferMediaResource> resource =
|
2013-02-02 02:13:23 +04:00
|
|
|
new BufferMediaResource(static_cast<uint8_t*> (mBuffer),
|
Bug 1331289 - Use MediaContainerType in MediaResource, SourceBuffer, TrackBuffersManager, and dependencies - r=jya
Continuing the work of replacing MIME strings with MediaContainerType, starting
from MediaResource and following the dependencies.
Most changes are mechanical: Just change ns*String into MediaContainerType, and
MIME string literals into MEDIAMIMETYPE("a/b").
Some checks for empty/invalid strings and lowercase comparisons can go, thanks
to the always-valid always-lowercase-MIME invariants of MediaContainerType.
One special case in is MediaSourceResource, which used to have an empty string
as its type (because its own type is not relevant, but its SourceBuffers carry
types). Because the inherited GetContentType *must* be overridden, and must
return a MediaContainerType, we needed a valid type even though it should not
be seen in the real world. I've chosen "application/x.mediasource" for that.
MozReview-Commit-ID: 1aCH75Kh2e6
--HG--
extra : rebase_source : 0d9cd9b69c264e5dcfc3845f80ee107f4bcbcd9a
2016-12-28 10:59:02 +03:00
|
|
|
mLength, principal, mContainerType);
|
2013-02-02 02:13:23 +04:00
|
|
|
|
|
|
|
MOZ_ASSERT(!mBufferDecoder);
|
2017-04-04 14:47:47 +03:00
|
|
|
mMainThread =
|
2016-11-29 08:03:36 +03:00
|
|
|
mDecodeJob.mContext->GetOwnerGlobal()->AbstractMainThreadFor(TaskCategory::Other);
|
2017-05-23 02:09:08 +03:00
|
|
|
mBufferDecoder = new BufferDecoder(resource, mMainThread);
|
2013-02-02 02:13:23 +04:00
|
|
|
|
|
|
|
// If you change this list to add support for new decoders, please consider
|
2013-03-19 16:23:54 +04:00
|
|
|
// updating HTMLMediaElement::CreateDecoder as well.
|
2013-02-02 02:13:23 +04:00
|
|
|
|
2017-01-18 03:59:03 +03:00
|
|
|
mDecoderReader = DecoderTraits::CreateReader(mContainerType, mBufferDecoder);
|
2013-02-02 02:13:23 +04:00
|
|
|
|
|
|
|
if (!mDecoderReader) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-10-13 10:28:57 +03:00
|
|
|
nsresult rv = mDecoderReader->Init();
|
2013-02-02 02:13:23 +04:00
|
|
|
if (NS_FAILED(rv)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-04-28 09:42:00 +03:00
|
|
|
class AutoResampler final
|
|
|
|
{
|
2013-05-13 08:17:00 +04:00
|
|
|
public:
|
|
|
|
AutoResampler()
|
|
|
|
: mResampler(nullptr)
|
|
|
|
{}
|
|
|
|
~AutoResampler()
|
|
|
|
{
|
|
|
|
if (mResampler) {
|
|
|
|
speex_resampler_destroy(mResampler);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
operator SpeexResamplerState*() const
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(mResampler);
|
|
|
|
return mResampler;
|
|
|
|
}
|
|
|
|
void operator=(SpeexResamplerState* aResampler)
|
|
|
|
{
|
|
|
|
mResampler = aResampler;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
SpeexResamplerState* mResampler;
|
|
|
|
};
|
|
|
|
|
2013-02-02 02:13:23 +04:00
|
|
|
void
|
|
|
|
MediaDecodeTask::Decode()
|
|
|
|
{
|
2014-04-23 16:56:42 +04:00
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
2013-02-02 02:13:23 +04:00
|
|
|
|
2015-07-16 21:31:21 +03:00
|
|
|
mBufferDecoder->BeginDecoding(mDecoderReader->OwnerThread());
|
2013-02-02 02:13:23 +04:00
|
|
|
|
2013-08-29 13:43:44 +04:00
|
|
|
// Tell the decoder reader that we are not going to play the data directly,
|
|
|
|
// and that we should not reject files with more channels than the audio
|
2015-05-18 09:13:20 +03:00
|
|
|
// backend support.
|
2013-08-29 13:43:44 +04:00
|
|
|
mDecoderReader->SetIgnoreAudioOutputFormat();
|
|
|
|
|
2015-07-16 21:31:21 +03:00
|
|
|
mDecoderReader->AsyncReadMetadata()->Then(mDecoderReader->OwnerThread(), __func__, this,
|
2015-05-18 09:13:20 +03:00
|
|
|
&MediaDecodeTask::OnMetadataRead,
|
|
|
|
&MediaDecodeTask::OnMetadataNotRead);
|
|
|
|
}
|
2013-02-02 02:13:23 +04:00
|
|
|
|
2015-05-18 09:13:20 +03:00
|
|
|
void
|
2017-05-18 05:20:44 +03:00
|
|
|
MediaDecodeTask::OnMetadataRead(MetadataHolder&& aMetadata)
|
2015-05-18 09:13:20 +03:00
|
|
|
{
|
2017-05-18 05:20:44 +03:00
|
|
|
mMediaInfo = *aMetadata.mInfo;
|
2015-05-18 09:13:20 +03:00
|
|
|
if (!mMediaInfo.HasAudio()) {
|
2014-11-25 04:09:19 +03:00
|
|
|
mDecoderReader->Shutdown();
|
2013-02-02 02:13:23 +04:00
|
|
|
ReportFailureOnMainThread(WebAudioDecodeJob::NoAudio);
|
|
|
|
return;
|
|
|
|
}
|
2015-11-25 02:52:48 +03:00
|
|
|
|
|
|
|
nsCString codec;
|
|
|
|
if (!mMediaInfo.mAudio.GetAsAudioInfo()->mMimeType.IsEmpty()) {
|
|
|
|
codec = nsPrintfCString("webaudio; %s", mMediaInfo.mAudio.GetAsAudioInfo()->mMimeType.get());
|
|
|
|
} else {
|
2016-12-22 03:57:48 +03:00
|
|
|
codec = nsPrintfCString("webaudio;resource; %s",
|
2017-01-18 03:59:03 +03:00
|
|
|
mContainerType.Type().AsString().Data());
|
2015-11-25 02:52:48 +03:00
|
|
|
}
|
|
|
|
|
2017-06-21 14:59:26 +03:00
|
|
|
nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction([codec]() -> void {
|
|
|
|
MOZ_ASSERT(!codec.IsEmpty());
|
|
|
|
MOZ_LOG(gMediaDecoderLog,
|
|
|
|
LogLevel::Debug,
|
|
|
|
("Telemetry (WebAudio) MEDIA_CODEC_USED= '%s'", codec.get()));
|
|
|
|
Telemetry::Accumulate(Telemetry::HistogramID::MEDIA_CODEC_USED, codec);
|
|
|
|
});
|
2017-03-29 05:42:48 +03:00
|
|
|
SystemGroup::Dispatch("MediaDecodeTask::OnMetadataRead()::report_telemetry",
|
|
|
|
TaskCategory::Other,
|
|
|
|
task.forget());
|
2015-11-25 02:52:48 +03:00
|
|
|
|
2014-12-11 01:03:56 +03:00
|
|
|
RequestSample();
|
|
|
|
}
|
|
|
|
|
2015-05-18 09:13:20 +03:00
|
|
|
void
|
2016-09-10 12:56:50 +03:00
|
|
|
MediaDecodeTask::OnMetadataNotRead(const MediaResult& aReason)
|
2015-05-18 09:13:20 +03:00
|
|
|
{
|
|
|
|
mDecoderReader->Shutdown();
|
|
|
|
ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
|
|
|
|
}
|
|
|
|
|
2014-12-11 01:03:56 +03:00
|
|
|
void
|
|
|
|
MediaDecodeTask::RequestSample()
|
|
|
|
{
|
2015-07-16 21:31:21 +03:00
|
|
|
mDecoderReader->RequestAudioData()->Then(mDecoderReader->OwnerThread(), __func__, this,
|
2014-12-11 01:03:56 +03:00
|
|
|
&MediaDecodeTask::SampleDecoded,
|
|
|
|
&MediaDecodeTask::SampleNotDecoded);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2017-05-11 03:30:39 +03:00
|
|
|
MediaDecodeTask::SampleDecoded(RefPtr<AudioData> aData)
|
2014-12-11 01:03:56 +03:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
mAudioQueue.Push(aData);
|
2015-05-18 09:13:20 +03:00
|
|
|
if (!mFirstFrameDecoded) {
|
|
|
|
mDecoderReader->ReadUpdatedMetadata(&mMediaInfo);
|
|
|
|
mFirstFrameDecoded = true;
|
|
|
|
}
|
2014-12-11 01:03:56 +03:00
|
|
|
RequestSample();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2016-09-10 09:48:53 +03:00
|
|
|
MediaDecodeTask::SampleNotDecoded(const MediaResult& aError)
|
2014-12-11 01:03:56 +03:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
2016-09-10 09:48:53 +03:00
|
|
|
if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
|
|
|
|
FinishDecode();
|
|
|
|
} else {
|
2014-12-11 01:03:56 +03:00
|
|
|
mDecoderReader->Shutdown();
|
|
|
|
ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
|
2013-02-02 02:13:23 +04:00
|
|
|
}
|
2014-12-11 01:03:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
MediaDecodeTask::FinishDecode()
|
|
|
|
{
|
2014-06-18 09:07:02 +04:00
|
|
|
mDecoderReader->Shutdown();
|
2013-02-02 02:13:23 +04:00
|
|
|
|
2014-12-11 01:03:56 +03:00
|
|
|
uint32_t frameCount = mAudioQueue.FrameCount();
|
|
|
|
uint32_t channelCount = mMediaInfo.mAudio.mChannels;
|
|
|
|
uint32_t sampleRate = mMediaInfo.mAudio.mRate;
|
2013-02-02 02:13:23 +04:00
|
|
|
|
|
|
|
if (!frameCount || !channelCount || !sampleRate) {
|
|
|
|
ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const uint32_t destSampleRate = mDecodeJob.mContext->SampleRate();
|
2013-05-13 08:17:00 +04:00
|
|
|
AutoResampler resampler;
|
2013-02-02 02:13:23 +04:00
|
|
|
|
2013-05-13 08:17:00 +04:00
|
|
|
uint32_t resampledFrames = frameCount;
|
|
|
|
if (sampleRate != destSampleRate) {
|
|
|
|
resampledFrames = static_cast<uint32_t>(
|
2013-02-02 02:13:23 +04:00
|
|
|
static_cast<uint64_t>(destSampleRate) *
|
|
|
|
static_cast<uint64_t>(frameCount) /
|
2013-05-13 08:17:00 +04:00
|
|
|
static_cast<uint64_t>(sampleRate)
|
2013-02-02 02:13:23 +04:00
|
|
|
);
|
|
|
|
|
2013-05-13 08:17:00 +04:00
|
|
|
resampler = speex_resampler_init(channelCount,
|
|
|
|
sampleRate,
|
2013-02-02 02:13:23 +04:00
|
|
|
destSampleRate,
|
|
|
|
SPEEX_RESAMPLER_QUALITY_DEFAULT, nullptr);
|
2013-05-13 08:17:00 +04:00
|
|
|
speex_resampler_skip_zeros(resampler);
|
|
|
|
resampledFrames += speex_resampler_get_output_latency(resampler);
|
2013-02-02 02:13:23 +04:00
|
|
|
}
|
|
|
|
|
2013-05-13 08:17:00 +04:00
|
|
|
// Allocate the channel buffers. Note that if we end up resampling, we may
|
|
|
|
// write fewer bytes than mResampledFrames to the output buffer, in which
|
|
|
|
// case mWriteIndex will tell us how many valid samples we have.
|
2015-08-28 02:15:39 +03:00
|
|
|
mDecodeJob.mBuffer = ThreadSharedFloatArrayBufferList::
|
|
|
|
Create(channelCount, resampledFrames, fallible);
|
|
|
|
if (!mDecodeJob.mBuffer) {
|
2013-05-13 08:17:00 +04:00
|
|
|
ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError);
|
|
|
|
return;
|
|
|
|
}
|
2013-02-02 02:13:23 +04:00
|
|
|
|
2017-03-22 06:25:23 +03:00
|
|
|
RefPtr<AudioData> audioData;
|
|
|
|
while ((audioData = mAudioQueue.PopFront())) {
|
2013-02-02 02:13:23 +04:00
|
|
|
audioData->EnsureAudioBuffer(); // could lead to a copy :(
|
|
|
|
AudioDataValue* bufferData = static_cast<AudioDataValue*>
|
|
|
|
(audioData->mAudioBuffer->Data());
|
|
|
|
|
2013-05-13 08:17:00 +04:00
|
|
|
if (sampleRate != destSampleRate) {
|
2013-12-03 03:09:01 +04:00
|
|
|
const uint32_t maxOutSamples = resampledFrames - mDecodeJob.mWriteIndex;
|
2013-02-02 02:13:23 +04:00
|
|
|
|
|
|
|
for (uint32_t i = 0; i < audioData->mChannels; ++i) {
|
|
|
|
uint32_t inSamples = audioData->mFrames;
|
2013-12-03 03:09:01 +04:00
|
|
|
uint32_t outSamples = maxOutSamples;
|
2015-08-28 02:15:39 +03:00
|
|
|
float* outData =
|
|
|
|
mDecodeJob.mBuffer->GetDataForWrite(i) + mDecodeJob.mWriteIndex;
|
2013-02-02 02:13:23 +04:00
|
|
|
|
2013-10-01 10:22:57 +04:00
|
|
|
WebAudioUtils::SpeexResamplerProcess(
|
|
|
|
resampler, i, &bufferData[i * audioData->mFrames], &inSamples,
|
2015-08-28 02:15:39 +03:00
|
|
|
outData, &outSamples);
|
2013-05-13 08:17:00 +04:00
|
|
|
|
|
|
|
if (i == audioData->mChannels - 1) {
|
|
|
|
mDecodeJob.mWriteIndex += outSamples;
|
|
|
|
MOZ_ASSERT(mDecodeJob.mWriteIndex <= resampledFrames);
|
2013-12-03 03:09:01 +04:00
|
|
|
MOZ_ASSERT(inSamples == audioData->mFrames);
|
2013-05-13 08:17:00 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (uint32_t i = 0; i < audioData->mChannels; ++i) {
|
2015-08-28 02:15:39 +03:00
|
|
|
float* outData =
|
|
|
|
mDecodeJob.mBuffer->GetDataForWrite(i) + mDecodeJob.mWriteIndex;
|
2013-05-13 08:17:00 +04:00
|
|
|
ConvertAudioSamples(&bufferData[i * audioData->mFrames],
|
2015-08-28 02:15:39 +03:00
|
|
|
outData, audioData->mFrames);
|
2013-05-13 08:17:00 +04:00
|
|
|
|
|
|
|
if (i == audioData->mChannels - 1) {
|
|
|
|
mDecodeJob.mWriteIndex += audioData->mFrames;
|
|
|
|
}
|
2013-02-02 02:13:23 +04:00
|
|
|
}
|
|
|
|
}
|
2013-05-13 08:17:00 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (sampleRate != destSampleRate) {
|
2013-12-03 03:09:01 +04:00
|
|
|
uint32_t inputLatency = speex_resampler_get_input_latency(resampler);
|
|
|
|
const uint32_t maxOutSamples = resampledFrames - mDecodeJob.mWriteIndex;
|
2013-05-13 08:17:00 +04:00
|
|
|
for (uint32_t i = 0; i < channelCount; ++i) {
|
|
|
|
uint32_t inSamples = inputLatency;
|
2013-12-03 03:09:01 +04:00
|
|
|
uint32_t outSamples = maxOutSamples;
|
2015-08-28 02:15:39 +03:00
|
|
|
float* outData =
|
|
|
|
mDecodeJob.mBuffer->GetDataForWrite(i) + mDecodeJob.mWriteIndex;
|
2013-05-13 08:17:00 +04:00
|
|
|
|
2013-10-01 10:22:57 +04:00
|
|
|
WebAudioUtils::SpeexResamplerProcess(
|
2013-12-03 03:07:17 +04:00
|
|
|
resampler, i, (AudioDataValue*)nullptr, &inSamples,
|
2015-08-28 02:15:39 +03:00
|
|
|
outData, &outSamples);
|
2013-05-13 08:17:00 +04:00
|
|
|
|
|
|
|
if (i == channelCount - 1) {
|
|
|
|
mDecodeJob.mWriteIndex += outSamples;
|
|
|
|
MOZ_ASSERT(mDecodeJob.mWriteIndex <= resampledFrames);
|
2013-12-03 03:09:01 +04:00
|
|
|
MOZ_ASSERT(inSamples == inputLatency);
|
2013-05-13 08:17:00 +04:00
|
|
|
}
|
2013-02-02 02:13:23 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-13 08:17:00 +04:00
|
|
|
mPhase = PhaseEnum::AllocateBuffer;
|
2017-04-04 14:47:47 +03:00
|
|
|
mMainThread->Dispatch(do_AddRef(this));
|
2013-02-02 02:13:23 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2013-05-13 08:17:00 +04:00
|
|
|
MediaDecodeTask::AllocateBuffer()
|
2013-02-02 02:13:23 +04:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
2013-05-13 08:17:00 +04:00
|
|
|
if (!mDecodeJob.AllocateBuffer()) {
|
2013-02-02 02:13:23 +04:00
|
|
|
ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError);
|
2013-05-13 08:17:00 +04:00
|
|
|
return;
|
2013-02-02 02:13:23 +04:00
|
|
|
}
|
|
|
|
|
2013-05-13 08:17:00 +04:00
|
|
|
mPhase = PhaseEnum::Done;
|
|
|
|
CallbackTheResult();
|
2013-02-02 02:13:23 +04:00
|
|
|
}
|
|
|
|
|
2013-05-13 08:17:00 +04:00
|
|
|
void
|
|
|
|
MediaDecodeTask::CallbackTheResult()
|
2013-02-02 02:13:23 +04:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
2013-05-13 08:17:00 +04:00
|
|
|
Cleanup();
|
2013-02-02 02:13:23 +04:00
|
|
|
|
2013-05-13 08:17:00 +04:00
|
|
|
// Now, we're ready to call the script back with the resulting buffer
|
|
|
|
mDecodeJob.OnSuccess(WebAudioDecodeJob::NoError);
|
2013-02-02 02:13:23 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
WebAudioDecodeJob::AllocateBuffer()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(!mOutput);
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
|
|
// Now create the AudioBuffer
|
2014-05-16 01:23:27 +04:00
|
|
|
ErrorResult rv;
|
2015-08-28 02:15:39 +03:00
|
|
|
uint32_t channelCount = mBuffer->GetChannels();
|
2017-01-11 00:30:28 +03:00
|
|
|
mOutput = AudioBuffer::Create(mContext->GetOwner(), channelCount,
|
2015-08-28 02:15:39 +03:00
|
|
|
mWriteIndex, mContext->SampleRate(),
|
2016-03-12 00:43:31 +03:00
|
|
|
mBuffer.forget(), rv);
|
2015-08-28 02:15:39 +03:00
|
|
|
return !rv.Failed();
|
2013-02-02 02:13:23 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2014-12-11 01:03:56 +03:00
|
|
|
AsyncDecodeWebAudio(const char* aContentType, uint8_t* aBuffer,
|
|
|
|
uint32_t aLength, WebAudioDecodeJob& aDecodeJob)
|
2013-02-02 02:13:23 +04:00
|
|
|
{
|
2017-01-18 03:59:03 +03:00
|
|
|
Maybe<MediaContainerType> containerType = MakeMediaContainerType(aContentType);
|
2013-02-02 02:13:23 +04:00
|
|
|
// Do not attempt to decode the media if we were not successful at sniffing
|
2017-01-18 03:59:03 +03:00
|
|
|
// the container type.
|
2013-02-02 02:13:23 +04:00
|
|
|
if (!*aContentType ||
|
2016-12-22 03:57:48 +03:00
|
|
|
strcmp(aContentType, APPLICATION_OCTET_STREAM) == 0 ||
|
2017-01-18 03:59:03 +03:00
|
|
|
!containerType) {
|
2013-02-02 02:13:23 +04:00
|
|
|
nsCOMPtr<nsIRunnable> event =
|
|
|
|
new ReportResultTask(aDecodeJob,
|
|
|
|
&WebAudioDecodeJob::OnFailure,
|
|
|
|
WebAudioDecodeJob::UnknownContent);
|
2014-06-22 22:10:08 +04:00
|
|
|
JS_free(nullptr, aBuffer);
|
2017-04-04 14:47:47 +03:00
|
|
|
aDecodeJob.mContext->Dispatch(event.forget());
|
2013-02-02 02:13:23 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<MediaDecodeTask> task =
|
2017-01-18 03:59:03 +03:00
|
|
|
new MediaDecodeTask(*containerType, aBuffer, aLength, aDecodeJob);
|
2013-02-02 02:13:23 +04:00
|
|
|
if (!task->CreateReader()) {
|
|
|
|
nsCOMPtr<nsIRunnable> event =
|
|
|
|
new ReportResultTask(aDecodeJob,
|
|
|
|
&WebAudioDecodeJob::OnFailure,
|
|
|
|
WebAudioDecodeJob::UnknownError);
|
2017-04-04 14:47:47 +03:00
|
|
|
aDecodeJob.mContext->Dispatch(event.forget());
|
2013-02-02 02:13:23 +04:00
|
|
|
} else {
|
2015-05-01 16:14:16 +03:00
|
|
|
// If we did this without a temporary:
|
2015-07-16 21:31:21 +03:00
|
|
|
// task->Reader()->OwnerThread()->Dispatch(task.forget())
|
2015-05-01 16:14:16 +03:00
|
|
|
// we might evaluate the task.forget() before calling Reader(). Enforce
|
|
|
|
// a non-crashy order-of-operations.
|
2015-07-16 21:13:49 +03:00
|
|
|
TaskQueue* taskQueue = task->Reader()->OwnerThread();
|
2015-05-01 16:14:16 +03:00
|
|
|
taskQueue->Dispatch(task.forget());
|
2014-06-18 09:24:05 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-02 02:13:23 +04:00
|
|
|
WebAudioDecodeJob::WebAudioDecodeJob(const nsACString& aContentType,
|
|
|
|
AudioContext* aContext,
|
2014-10-23 14:07:48 +04:00
|
|
|
Promise* aPromise,
|
2013-02-02 02:13:23 +04:00
|
|
|
DecodeSuccessCallback* aSuccessCallback,
|
|
|
|
DecodeErrorCallback* aFailureCallback)
|
|
|
|
: mContentType(aContentType)
|
2013-05-13 08:17:00 +04:00
|
|
|
, mWriteIndex(0)
|
2013-02-02 02:13:23 +04:00
|
|
|
, mContext(aContext)
|
2014-10-23 14:07:48 +04:00
|
|
|
, mPromise(aPromise)
|
2013-02-02 02:13:23 +04:00
|
|
|
, mSuccessCallback(aSuccessCallback)
|
|
|
|
, mFailureCallback(aFailureCallback)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(aContext);
|
|
|
|
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);
|
|
|
|
|
2014-10-23 14:07:48 +04:00
|
|
|
if (mSuccessCallback) {
|
2016-01-07 18:58:39 +03:00
|
|
|
ErrorResult rv;
|
2014-10-23 14:07:48 +04:00
|
|
|
mSuccessCallback->Call(*mOutput, rv);
|
2016-01-07 18:58:39 +03:00
|
|
|
// Ignore errors in calling the callback, since there is not much that we can
|
|
|
|
// do about it here.
|
|
|
|
rv.SuppressException();
|
2014-10-23 14:07:48 +04:00
|
|
|
}
|
|
|
|
mPromise->MaybeResolve(mOutput);
|
2013-02-02 02:13:23 +04:00
|
|
|
|
|
|
|
mContext->RemoveFromDecodeQueue(this);
|
2016-01-07 18:58:39 +03:00
|
|
|
|
2013-02-02 02:13:23 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
WebAudioDecodeJob::OnFailure(ErrorCode aErrorCode)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
|
|
|
|
const char* errorMessage;
|
|
|
|
switch (aErrorCode) {
|
|
|
|
case NoError:
|
2016-01-14 12:42:18 +03:00
|
|
|
MOZ_FALLTHROUGH_ASSERT("Who passed NoError to OnFailure?");
|
2013-02-02 02:13:23 +04:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsIDocument* doc = nullptr;
|
2016-01-30 20:05:36 +03:00
|
|
|
if (nsPIDOMWindowInner* pWindow = mContext->GetParentObject()) {
|
2013-02-02 02:13:23 +04:00
|
|
|
doc = pWindow->GetExtantDoc();
|
|
|
|
}
|
|
|
|
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
|
2013-08-21 23:28:26 +04:00
|
|
|
NS_LITERAL_CSTRING("Media"),
|
2013-02-02 02:13:23 +04:00
|
|
|
doc,
|
|
|
|
nsContentUtils::eDOM_PROPERTIES,
|
|
|
|
errorMessage);
|
|
|
|
|
|
|
|
// Ignore errors in calling the callback, since there is not much that we can
|
|
|
|
// do about it here.
|
2013-03-05 08:04:51 +04:00
|
|
|
if (mFailureCallback) {
|
2016-12-13 09:00:15 +03:00
|
|
|
nsAutoCString errorString(errorMessage);
|
|
|
|
RefPtr<DOMException> exception =
|
|
|
|
DOMException::Create(NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR,
|
|
|
|
errorString);
|
|
|
|
mFailureCallback->Call(*exception);
|
2013-03-05 08:04:51 +04:00
|
|
|
}
|
2013-02-02 02:13:23 +04:00
|
|
|
|
2014-10-23 14:07:48 +04:00
|
|
|
mPromise->MaybeReject(NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR);
|
|
|
|
|
2013-02-02 02:13:23 +04:00
|
|
|
mContext->RemoveFromDecodeQueue(this);
|
|
|
|
}
|
|
|
|
|
2014-01-04 22:15:41 +04:00
|
|
|
size_t
|
|
|
|
WebAudioDecodeJob::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
|
|
|
|
{
|
|
|
|
size_t amount = 0;
|
2015-12-02 02:36:26 +03:00
|
|
|
amount += mContentType.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
|
2014-01-04 22:15:41 +04:00
|
|
|
if (mSuccessCallback) {
|
|
|
|
amount += mSuccessCallback->SizeOfIncludingThis(aMallocSizeOf);
|
|
|
|
}
|
|
|
|
if (mFailureCallback) {
|
|
|
|
amount += mFailureCallback->SizeOfIncludingThis(aMallocSizeOf);
|
|
|
|
}
|
|
|
|
if (mOutput) {
|
|
|
|
amount += mOutput->SizeOfIncludingThis(aMallocSizeOf);
|
|
|
|
}
|
2015-11-16 22:58:48 +03:00
|
|
|
if (mBuffer) {
|
|
|
|
amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
|
|
|
|
}
|
2014-01-04 22:15:41 +04:00
|
|
|
return amount;
|
|
|
|
}
|
|
|
|
|
2014-05-01 21:37:54 +04:00
|
|
|
size_t
|
|
|
|
WebAudioDecodeJob::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
|
|
|
|
{
|
|
|
|
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
|
|
|
|
}
|
|
|
|
|
2015-07-13 18:25:42 +03:00
|
|
|
} // namespace mozilla
|