mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 23:06:52 +00:00
b1814c6e48
- Bug 1011826 - Split Prefable into two pieces. r=bz. (115675544f) - Bug 1260653 - Shrink NativeProperties. r=bz. (a609483d76) - Bug 1255655 - Const-ify mozilla::dom::ErrorFormatString. r=baku. (8e4f4dac33) - Bug 1257389: Use fallible allocation for content-controlled string in bug 1257389. r=bz (b3670d2916) - Bug 1245650 - remove JavaScript-navigator-property category support. r=bz. (fd2be96344) - Bug 1260653 (follow-up) - Use |MOZ_CONSTEXPR| instead of |constexpr| to unbreak builds with VS 2013. r=birtles. (d76ab3c86d) - Bug 1259172 - Fix assertion in Codegen.py's InitUnforgeablePropertiesOnHolder function. r=bz (ef54b31645) - Bug 1250949. Add some better documentation to the dictionary init codegen. r=ehsan (f608f930e5) - build bits of Bug 1318955 - Upgrade to Hunspell 1.5.4 and remove stale README for non-existing patches (713d8d8329) - Bug 1250499 - prevent resource leak in mount_operation_ask_password. r=ehsan (ba9d741462) - Bug 1252902 - remove ScopedFreePtr; r=Waldo (c8978edf27) - Bug 1261452 - do_AddRef shouldn't require an rvalue-reference. r=froydnj (aec4028b9e) - Bug 1252901 - don't use ScopedFreePtrTraits in MediaEngineWrapper.h; r=jesup (d4cf3438e3) - Bug 1252198 - use UniquePtr instead of ScopedFreePtr in MediaEngineTabVideoSource; r=gerald (767055ddba) - Bug 1254102 - Add "blackout" mode. If the window is not legal, output a black square. r=jesup (4d19c584bb) - Bug 1250934: Only dig out cubeb deviceIDs immediately before use since they can be freed r=padenot (5c321f9458) - Bug 1255217: Don't require getUserMedia/cubeb device enumeration mapping if --disable-webrtc is set r=padenot (bd3efd7a23) - Bug 1257616: We can release Conduits immediately if we're on MainThread already r=drno (0b04888e2f) - Bug 1258144: Don't start a thread for SystemClockDriver if already started r=padenot (298893101c) - Bug 1258144: Flip warning sense typo in original patch for bug r=cpearce (53422b72a3) - Bug 1258144: warnings-as-errors bustage r=cpearce (9259a2e872) - Bug 1251502 - Use devid instead of device_id when getting the ID for a cubeb device. r=jesup (61bb95c418) - Bug 1258942: Lock around DeviceID access for audio inputs r=padenot (1bdd2da88d) - Bug 1250934: Don't allow switching to a clock driver when we already have a switch pending r=padenot (d35cf2791d) - Bug 1250934: Factor out AudioTracksPresent() in MediaStreamGraph, and add checks of 'pending' tracks r=padenot (1e53e16ec0) - Bug 1250934: Block GraphDriver switching if audio input is enabled (full-duplex) r=padenot (1b30daf654) - Bug 1256555: Wrap "shouldAEC" variable-usage in #ifdef for consistency & to fix --disable-webrtc build failure. r=jesup (fa701f164e) - Bug 1263251: Calculate AudioTrackPresent() correctly in MediaStreamGraph r=padenot (e4ca11bdcd) - Bug 1264195: Re-add SetMicrophoneActive() calls into the GraphDriver r=padenot (c995b0cd83) - Bug 1250934: Update some MediaStreamGraph logging r=padenot (e105642010) - Bug 1266438 - Reset the AEC and other processing when audio devices change. r=jesup (e09f6bc9e7) - Bug 1256510: When shutting down MediaStreamGraphs, if the graph hasn't started, let it start and shut down r=padenot (92ffec2c64) - Bug 1266438 - Remove workaround on OSX when switching device, not necessary with duplex mode. r=jesup (0203d61186) - Bug 1261627 - Unbreak --disable-webrtc builds. r=cpearce (38c2af874d) - Bug 1248861: P1. Add AlignedBuffer template class. r=cpearce (3b27482308) - Bug 1248861: P2. Use AlignedByteBuffer with MediaRawData. r=cpearce (0652979dbc) - Bug 1248861: P3. Use AlignedAudioBuffer object with AudioData. r=cpearce (6788c4c1cb) - Bug 1248861: P4. Add AudioConfig and introduce channel layout definition. r=cpearce (0b10c3dea0) - Bug 1248861: P5. Add AudioConverter class and relatives. r=cpearce (1492a238c5) - Bug 1248861: P6. Ensure AT decoder channel layout is always in SMPTE order. r=rillian (05db7e01d1) - Bug 1239223: [vorbis] P1. Properly calculate decoded audio sample timestamps. r=kinetik (b6088d692a) - Bug 1264500 - Include Telemetry.h to avoid build errors. r=cpearce (c019891221) - Bug 1248861: P7. Ensure vorbis decoder channel layout is always in SMPTE order. r=rillian (69bc69795c) - Bug 657791 - Update seekable range handling for cueless WebMs. r=kinetik MediaDecoder previously had 3 states within GetSeekable(), media is either seekable, seekable but not supported by transport, or not seekable. Due to changes to make cueless webms playable, a 4th option is needed: a file that is not fully seekable, but may support seeking from the transport, such as these webms, should only be seekable in the buffered range. (73132b539d) - Bug 1248861: P8. Added ChannelLayout::MappingTable method. r=gerald (e672ae0045) - Bug 1248861: P9. Use ChannelLayout::MappingTable. r=gerald (f4775517ba) - Bug 1241062: [opus] P1. Properly calculate decoded audio sample timestamps. r=kinetik (8ba6184301) - Bug 1248861: P10. Ensure opus decoder channel layout is always in SMPTE order. r=rillian (7c05d2ef52) - Bug 1248861: P11. Fix current downmixer. r=rillian Current downmixer was using vorbis channel order (which isn't surprising as it was extracted from the Ogg reader). Make it use SMPTE order as that's now what all MediaDataDecoder output. (e1f7ef6e41) - Bug 1248861: P12. Update AudioCompactor to account for alignment padding. r=rillian (7bc1630947) - Bug 1265093: Silence CID 1358535. r=gerald It was a false positive, has mData can only be null if mLength is also 0. (1dc80f251d) - Bug 1262753: P1. Add AudioConfig == and != operator. r=gerald (1470c50ee9) - Bug 1262753: P2. Add AudioDataBuffer::operator=. r=gerald (97dc392584) - Bug 1262753: P3. Add AudioConverter::InputConfig/OutputConfig. r=gerald Allow access to original constructor objects, to avoid having to store the original AudioConfig objects elsewhere. (743799c4d9) - Bug 1262746: P1. Add downmixing capabilities to AudioConverter. r=rillian (1bbb210b88) - Bug 1248513 - Fix static analysis errors in non-unified build for AudioStream.cpp. r=gerald. (ada96b559a) - Bug 1262746: P2. Use AudioConvert in AudioStream to downmix samples. r=jwwang (c1d42ba8c5) - Bug 1262746: P3. Remove DownmixAudioToStereo method. r=rillian Functionality is now provided through AudioConverter class. (df003d2602) - Bug 1248478 - Remove some obsolete code in XrayWrapper.cpp. r=bholley. (5e9fe8790d) - missing bit of (089ce6da47) - Bug 1256688 - Continue using getPropertyDescriptor for has in XrayWrapper. r=peterv (8119ce5cb0) - Bug 1260945 - Add missing isSystem check (r=bholley) (3365901bcb) - Bug 1264778 - Merge some XPConnect js::Class instances. r=mrbkap. XPC_WN_WithCall_ObjectOps and XPC_WN_NoCall_ObjectOps are both equal to JS_NULL_OBJECT_OPS. (b363936eb9) - Bug 1257892. Fix XPCConvert::JSValToXPCException to correctly detect Web IDL binding exceptions (i.e. all of them now!) when trying to check whether the value is already an XPCException. r=bholley (c48ff0620c) - Bug 1179278: GDB pretty-printers: handle encoding errors when trying to print JSObject class names. DONTBUILD r=jorendorff (e9981ce359) - Bug 1261426 - GDB Scripts: Add pretty printers for js::jit::ExecutableAllocator. r=jandem (eb5570d8c7) - Bug 1232712 - add a gdb unwinder for SpiderMonkey; r=nbp (b60ca7eac5) - Bug 1259867 - GDB Scripts: Disable SpiderMonkey's unwinder by default. r=tromey (2784da6603) - Bug 1261426 - GDB Scripts: Use the ExecutableAllocator as a fallback solution for finding JIT pages. r=tromey (b80bfc4112) - Bug 1261426 - GDB Scripts: Prevent gdb Memory.error when dereferencing null pointers. r=tromey (ce5a767db1) - Bug 1254295 - add function name to gdb frame filter; r=nbp (ba31fca7c9) - Bug 1262936 - Add missing OOM check to RegExpStack::reset. r=bhackett (3d7c4be6c6) - Bug 1257779 - Don't make scripted proxies' [[OwnPropertyKeys]] have non-linear behavior in certain cases. r=jonco, r=evilpie (3b6e95f8e5) - Bug 1266614 - Fix C4334 in OrderedHashTable.h. r=jandem (c764886bc4) - Bug 1264920: IonMonkey - DoTypeUpdateFallback is not allowed to gc, r=jandem (df8cb4d03d)
862 lines
23 KiB
C++
862 lines
23 KiB
C++
/* 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 "AndroidDecoderModule.h"
|
|
#include "AndroidBridge.h"
|
|
#include "GLBlitHelper.h"
|
|
#include "GLContext.h"
|
|
#include "GLContextEGL.h"
|
|
#include "GLContextProvider.h"
|
|
#include "GLImages.h"
|
|
#include "GLLibraryEGL.h"
|
|
|
|
#include "MediaData.h"
|
|
#include "MediaInfo.h"
|
|
#include "VPXDecoder.h"
|
|
|
|
#include "nsThreadUtils.h"
|
|
#include "nsAutoPtr.h"
|
|
#include "nsPromiseFlatString.h"
|
|
#include "nsIGfxInfo.h"
|
|
|
|
#include "prlog.h"
|
|
|
|
#include <jni.h>
|
|
|
|
static PRLogModuleInfo* AndroidDecoderModuleLog()
|
|
{
|
|
static PRLogModuleInfo* sLogModule = nullptr;
|
|
if (!sLogModule) {
|
|
sLogModule = PR_NewLogModule("AndroidDecoderModule");
|
|
}
|
|
return sLogModule;
|
|
}
|
|
|
|
#undef LOG
|
|
#define LOG(arg, ...) MOZ_LOG(AndroidDecoderModuleLog(), \
|
|
mozilla::LogLevel::Debug, ("AndroidDecoderModule(%p)::%s: " arg, \
|
|
this, __func__, ##__VA_ARGS__))
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::gl;
|
|
using namespace mozilla::widget::sdk;
|
|
using media::TimeUnit;
|
|
|
|
namespace mozilla {
|
|
|
|
#define INVOKE_CALLBACK(Func, ...) \
|
|
if (mCallback) { \
|
|
mCallback->Func(__VA_ARGS__); \
|
|
} else { \
|
|
NS_WARNING("Callback not set"); \
|
|
}
|
|
|
|
static const char*
|
|
TranslateMimeType(const nsACString& aMimeType)
|
|
{
|
|
if (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP8)) {
|
|
return "video/x-vnd.on2.vp8";
|
|
} else if (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP9)) {
|
|
return "video/x-vnd.on2.vp9";
|
|
}
|
|
return PromiseFlatCString(aMimeType).get();
|
|
}
|
|
|
|
static MediaCodec::LocalRef
|
|
CreateDecoder(const nsACString& aMimeType)
|
|
{
|
|
MediaCodec::LocalRef codec;
|
|
NS_ENSURE_SUCCESS(MediaCodec::CreateDecoderByType(TranslateMimeType(aMimeType),
|
|
&codec), nullptr);
|
|
return codec;
|
|
}
|
|
|
|
static bool
|
|
GetFeatureStatus(int32_t aFeature)
|
|
{
|
|
nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
|
|
int32_t status = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
|
|
nsCString discardFailureId;
|
|
if (!gfxInfo || NS_FAILED(gfxInfo->GetFeatureStatus(aFeature, discardFailureId, &status))) {
|
|
return false;
|
|
}
|
|
return status == nsIGfxInfo::FEATURE_STATUS_OK;
|
|
};
|
|
|
|
class VideoDataDecoder : public MediaCodecDataDecoder
|
|
{
|
|
public:
|
|
VideoDataDecoder(const VideoInfo& aConfig,
|
|
MediaFormat::Param aFormat,
|
|
MediaDataDecoderCallback* aCallback,
|
|
layers::ImageContainer* aImageContainer)
|
|
: MediaCodecDataDecoder(MediaData::Type::VIDEO_DATA, aConfig.mMimeType,
|
|
aFormat, aCallback)
|
|
, mImageContainer(aImageContainer)
|
|
, mConfig(aConfig)
|
|
{
|
|
|
|
}
|
|
|
|
const char* GetDescriptionName() const override
|
|
{
|
|
return "android video decoder";
|
|
}
|
|
|
|
RefPtr<InitPromise> Init() override
|
|
{
|
|
mSurfaceTexture = AndroidSurfaceTexture::Create();
|
|
if (!mSurfaceTexture) {
|
|
NS_WARNING("Failed to create SurfaceTexture for video decode\n");
|
|
return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
|
}
|
|
|
|
if (NS_FAILED(InitDecoder(mSurfaceTexture->JavaSurface()))) {
|
|
return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
|
|
}
|
|
|
|
return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
|
|
}
|
|
|
|
void Cleanup() override
|
|
{
|
|
mGLContext = nullptr;
|
|
}
|
|
|
|
nsresult Input(MediaRawData* aSample) override
|
|
{
|
|
return MediaCodecDataDecoder::Input(aSample);
|
|
}
|
|
|
|
bool WantCopy() const
|
|
{
|
|
// Allocating a texture is incredibly slow on PowerVR and may fail on
|
|
// emulators, see bug 1190379.
|
|
return mGLContext->Vendor() != GLVendor::Imagination &&
|
|
mGLContext->Renderer() != GLRenderer::AndroidEmulator;
|
|
}
|
|
|
|
EGLImage CopySurface(layers::Image* img)
|
|
{
|
|
mGLContext->MakeCurrent();
|
|
|
|
GLuint tex = CreateTextureForOffscreen(mGLContext, mGLContext->GetGLFormats(),
|
|
img->GetSize());
|
|
|
|
auto helper = mGLContext->BlitHelper();
|
|
const gl::OriginPos destOrigin = gl::OriginPos::TopLeft;
|
|
if (!helper->BlitImageToTexture(img, img->GetSize(), tex,
|
|
LOCAL_GL_TEXTURE_2D, destOrigin))
|
|
{
|
|
mGLContext->fDeleteTextures(1, &tex);
|
|
return nullptr;
|
|
}
|
|
|
|
EGLint attribs[] = {
|
|
LOCAL_EGL_IMAGE_PRESERVED_KHR, LOCAL_EGL_TRUE,
|
|
LOCAL_EGL_NONE, LOCAL_EGL_NONE
|
|
};
|
|
|
|
EGLContext eglContext = static_cast<GLContextEGL*>(mGLContext.get())->GetEGLContext();
|
|
EGLImage eglImage = sEGLLibrary.fCreateImage(
|
|
EGL_DISPLAY(), eglContext, LOCAL_EGL_GL_TEXTURE_2D_KHR,
|
|
reinterpret_cast<EGLClientBuffer>(tex), attribs);
|
|
mGLContext->fDeleteTextures(1, &tex);
|
|
|
|
return eglImage;
|
|
}
|
|
|
|
nsresult PostOutput(BufferInfo::Param aInfo, MediaFormat::Param aFormat,
|
|
const TimeUnit& aDuration) override
|
|
{
|
|
if (!EnsureGLContext()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
RefPtr<layers::Image> img =
|
|
new SurfaceTextureImage(mSurfaceTexture.get(), mConfig.mDisplay,
|
|
gl::OriginPos::BottomLeft);
|
|
|
|
if (WantCopy()) {
|
|
EGLImage eglImage = CopySurface(img);
|
|
if (!eglImage) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
EGLSync eglSync = nullptr;
|
|
if (sEGLLibrary.IsExtensionSupported(GLLibraryEGL::KHR_fence_sync) &&
|
|
mGLContext->IsExtensionSupported(GLContext::OES_EGL_sync))
|
|
{
|
|
MOZ_ASSERT(mGLContext->IsCurrent());
|
|
eglSync = sEGLLibrary.fCreateSync(EGL_DISPLAY(),
|
|
LOCAL_EGL_SYNC_FENCE,
|
|
nullptr);
|
|
MOZ_ASSERT(eglSync);
|
|
mGLContext->fFlush();
|
|
} else {
|
|
NS_WARNING("No EGL fence support detected, rendering artifacts may occur!");
|
|
}
|
|
|
|
img = new layers::EGLImageImage(eglImage, eglSync, mConfig.mDisplay,
|
|
gl::OriginPos::TopLeft, true /* owns */);
|
|
}
|
|
|
|
nsresult rv;
|
|
int32_t flags;
|
|
NS_ENSURE_SUCCESS(rv = aInfo->Flags(&flags), rv);
|
|
|
|
bool isSync = !!(flags & MediaCodec::BUFFER_FLAG_SYNC_FRAME);
|
|
|
|
int32_t offset;
|
|
NS_ENSURE_SUCCESS(rv = aInfo->Offset(&offset), rv);
|
|
|
|
int64_t presentationTimeUs;
|
|
NS_ENSURE_SUCCESS(rv = aInfo->PresentationTimeUs(&presentationTimeUs), rv);
|
|
|
|
RefPtr<VideoData> v =
|
|
VideoData::CreateFromImage(mConfig,
|
|
mImageContainer,
|
|
offset,
|
|
presentationTimeUs,
|
|
aDuration.ToMicroseconds(),
|
|
img,
|
|
isSync,
|
|
presentationTimeUs,
|
|
gfx::IntRect(0, 0,
|
|
mConfig.mDisplay.width,
|
|
mConfig.mDisplay.height));
|
|
INVOKE_CALLBACK(Output, v);
|
|
return NS_OK;
|
|
}
|
|
|
|
protected:
|
|
bool EnsureGLContext()
|
|
{
|
|
if (mGLContext) {
|
|
return true;
|
|
}
|
|
|
|
mGLContext = GLContextProvider::CreateHeadless(CreateContextFlags::NONE);
|
|
return mGLContext;
|
|
}
|
|
|
|
layers::ImageContainer* mImageContainer;
|
|
const VideoInfo& mConfig;
|
|
RefPtr<AndroidSurfaceTexture> mSurfaceTexture;
|
|
RefPtr<GLContext> mGLContext;
|
|
};
|
|
|
|
class AudioDataDecoder : public MediaCodecDataDecoder
|
|
{
|
|
public:
|
|
AudioDataDecoder(const AudioInfo& aConfig, MediaFormat::Param aFormat,
|
|
MediaDataDecoderCallback* aCallback)
|
|
: MediaCodecDataDecoder(MediaData::Type::AUDIO_DATA, aConfig.mMimeType,
|
|
aFormat, aCallback)
|
|
{
|
|
JNIEnv* const env = jni::GetEnvForThread();
|
|
|
|
jni::Object::LocalRef buffer(env);
|
|
NS_ENSURE_SUCCESS_VOID(aFormat->GetByteBuffer(NS_LITERAL_STRING("csd-0"),
|
|
&buffer));
|
|
|
|
if (!buffer && aConfig.mCodecSpecificConfig->Length() >= 2) {
|
|
buffer = jni::Object::LocalRef::Adopt(
|
|
env, env->NewDirectByteBuffer(aConfig.mCodecSpecificConfig->Elements(),
|
|
aConfig.mCodecSpecificConfig->Length()));
|
|
NS_ENSURE_SUCCESS_VOID(aFormat->SetByteBuffer(NS_LITERAL_STRING("csd-0"),
|
|
buffer));
|
|
}
|
|
}
|
|
|
|
const char* GetDescriptionName() const override
|
|
{
|
|
return "android audio decoder";
|
|
}
|
|
|
|
nsresult Output(BufferInfo::Param aInfo, void* aBuffer,
|
|
MediaFormat::Param aFormat, const TimeUnit& aDuration)
|
|
{
|
|
// The output on Android is always 16-bit signed
|
|
nsresult rv;
|
|
int32_t numChannels;
|
|
NS_ENSURE_SUCCESS(rv =
|
|
aFormat->GetInteger(NS_LITERAL_STRING("channel-count"), &numChannels), rv);
|
|
|
|
int32_t sampleRate;
|
|
NS_ENSURE_SUCCESS(rv =
|
|
aFormat->GetInteger(NS_LITERAL_STRING("sample-rate"), &sampleRate), rv);
|
|
|
|
int32_t size;
|
|
NS_ENSURE_SUCCESS(rv = aInfo->Size(&size), rv);
|
|
|
|
int32_t offset;
|
|
NS_ENSURE_SUCCESS(rv = aInfo->Offset(&offset), rv);
|
|
|
|
#ifdef MOZ_SAMPLE_TYPE_S16
|
|
const int32_t numSamples = size / 2;
|
|
#else
|
|
#error We only support 16-bit integer PCM
|
|
#endif
|
|
|
|
const int32_t numFrames = numSamples / numChannels;
|
|
AlignedAudioBuffer audio(numSamples);
|
|
if (!audio) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
const uint8_t* bufferStart = static_cast<uint8_t*>(aBuffer) + offset;
|
|
PodCopy(audio.get(), reinterpret_cast<const AudioDataValue*>(bufferStart),
|
|
numSamples);
|
|
|
|
int64_t presentationTimeUs;
|
|
NS_ENSURE_SUCCESS(rv = aInfo->PresentationTimeUs(&presentationTimeUs), rv);
|
|
|
|
RefPtr<AudioData> data = new AudioData(0, presentationTimeUs,
|
|
aDuration.ToMicroseconds(),
|
|
numFrames,
|
|
Move(audio),
|
|
numChannels,
|
|
sampleRate);
|
|
INVOKE_CALLBACK(Output, data);
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
bool
|
|
AndroidDecoderModule::SupportsMimeType(const nsACString& aMimeType) const
|
|
{
|
|
if (!AndroidBridge::Bridge() ||
|
|
AndroidBridge::Bridge()->GetAPIVersion() < 16) {
|
|
return false;
|
|
}
|
|
|
|
if (aMimeType.EqualsLiteral("video/mp4") ||
|
|
aMimeType.EqualsLiteral("video/avc")) {
|
|
return true;
|
|
}
|
|
|
|
// When checking "audio/x-wav", CreateDecoder can cause a JNI ERROR by
|
|
// Accessing a stale local reference leading to a SIGSEGV crash.
|
|
// To avoid this we check for wav types here.
|
|
if (aMimeType.EqualsLiteral("audio/x-wav") ||
|
|
aMimeType.EqualsLiteral("audio/wave; codecs=1") ||
|
|
aMimeType.EqualsLiteral("audio/wave; codecs=6") ||
|
|
aMimeType.EqualsLiteral("audio/wave; codecs=7") ||
|
|
aMimeType.EqualsLiteral("audio/wave; codecs=65534")) {
|
|
return false;
|
|
}
|
|
|
|
if ((VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP8) &&
|
|
!GetFeatureStatus(nsIGfxInfo::FEATURE_VP8_HW_DECODE)) ||
|
|
(VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP9) &&
|
|
!GetFeatureStatus(nsIGfxInfo::FEATURE_VP9_HW_DECODE))) {
|
|
return false;
|
|
}
|
|
|
|
return widget::HardwareCodecCapabilityUtils::FindDecoderCodecInfoForMimeType(
|
|
nsCString(TranslateMimeType(aMimeType)));
|
|
}
|
|
|
|
already_AddRefed<MediaDataDecoder>
|
|
AndroidDecoderModule::CreateVideoDecoder(
|
|
const VideoInfo& aConfig, layers::LayersBackend aLayersBackend,
|
|
layers::ImageContainer* aImageContainer, FlushableTaskQueue* aVideoTaskQueue,
|
|
MediaDataDecoderCallback* aCallback)
|
|
{
|
|
MediaFormat::LocalRef format;
|
|
|
|
NS_ENSURE_SUCCESS(MediaFormat::CreateVideoFormat(
|
|
TranslateMimeType(aConfig.mMimeType),
|
|
aConfig.mDisplay.width,
|
|
aConfig.mDisplay.height,
|
|
&format), nullptr);
|
|
|
|
RefPtr<MediaDataDecoder> decoder =
|
|
new VideoDataDecoder(aConfig, format, aCallback, aImageContainer);
|
|
|
|
return decoder.forget();
|
|
}
|
|
|
|
already_AddRefed<MediaDataDecoder>
|
|
AndroidDecoderModule::CreateAudioDecoder(
|
|
const AudioInfo& aConfig, FlushableTaskQueue* aAudioTaskQueue,
|
|
MediaDataDecoderCallback* aCallback)
|
|
{
|
|
MOZ_ASSERT(aConfig.mBitDepth == 16, "We only handle 16-bit audio!");
|
|
|
|
MediaFormat::LocalRef format;
|
|
|
|
NS_ENSURE_SUCCESS(MediaFormat::CreateAudioFormat(
|
|
aConfig.mMimeType,
|
|
aConfig.mBitDepth,
|
|
aConfig.mChannels,
|
|
&format), nullptr);
|
|
|
|
RefPtr<MediaDataDecoder> decoder =
|
|
new AudioDataDecoder(aConfig, format, aCallback);
|
|
|
|
return decoder.forget();
|
|
}
|
|
|
|
PlatformDecoderModule::ConversionRequired
|
|
AndroidDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
|
|
{
|
|
if (aConfig.IsVideo()) {
|
|
return kNeedAnnexB;
|
|
}
|
|
return kNeedNone;
|
|
}
|
|
|
|
MediaCodecDataDecoder::MediaCodecDataDecoder(MediaData::Type aType,
|
|
const nsACString& aMimeType,
|
|
MediaFormat::Param aFormat,
|
|
MediaDataDecoderCallback* aCallback)
|
|
: mType(aType)
|
|
, mMimeType(aMimeType)
|
|
, mFormat(aFormat)
|
|
, mCallback(aCallback)
|
|
, mInputBuffers(nullptr)
|
|
, mOutputBuffers(nullptr)
|
|
, mMonitor("MediaCodecDataDecoder::mMonitor")
|
|
, mState(kDecoding)
|
|
{
|
|
|
|
}
|
|
|
|
MediaCodecDataDecoder::~MediaCodecDataDecoder()
|
|
{
|
|
Shutdown();
|
|
}
|
|
|
|
RefPtr<MediaDataDecoder::InitPromise>
|
|
MediaCodecDataDecoder::Init()
|
|
{
|
|
nsresult rv = InitDecoder(nullptr);
|
|
|
|
TrackInfo::TrackType type =
|
|
(mType == MediaData::AUDIO_DATA ? TrackInfo::TrackType::kAudioTrack
|
|
: TrackInfo::TrackType::kVideoTrack);
|
|
|
|
return NS_SUCCEEDED(rv) ?
|
|
InitPromise::CreateAndResolve(type, __func__) :
|
|
InitPromise::CreateAndReject(
|
|
MediaDataDecoder::DecoderFailureReason::INIT_ERROR, __func__);
|
|
}
|
|
|
|
nsresult
|
|
MediaCodecDataDecoder::InitDecoder(Surface::Param aSurface)
|
|
{
|
|
mDecoder = CreateDecoder(mMimeType);
|
|
if (!mDecoder) {
|
|
INVOKE_CALLBACK(Error);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult rv;
|
|
NS_ENSURE_SUCCESS(rv = mDecoder->Configure(mFormat, aSurface, nullptr, 0), rv);
|
|
NS_ENSURE_SUCCESS(rv = mDecoder->Start(), rv);
|
|
|
|
NS_ENSURE_SUCCESS(rv = ResetInputBuffers(), rv);
|
|
NS_ENSURE_SUCCESS(rv = ResetOutputBuffers(), rv);
|
|
|
|
NS_NewNamedThread("MC Decoder", getter_AddRefs(mThread),
|
|
NS_NewRunnableMethod(this, &MediaCodecDataDecoder::DecoderLoop));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// This is in usec, so that's 10ms.
|
|
static const int64_t kDecoderTimeout = 10000;
|
|
|
|
#define BREAK_ON_DECODER_ERROR() \
|
|
if (NS_FAILED(res)) { \
|
|
NS_WARNING("Exiting decoder loop due to exception"); \
|
|
if (State() == kDrainDecoder) { \
|
|
INVOKE_CALLBACK(DrainComplete); \
|
|
State(kDecoding); \
|
|
} \
|
|
INVOKE_CALLBACK(Error); \
|
|
break; \
|
|
}
|
|
|
|
nsresult
|
|
MediaCodecDataDecoder::GetInputBuffer(
|
|
JNIEnv* aEnv, int aIndex, jni::Object::LocalRef* aBuffer)
|
|
{
|
|
MOZ_ASSERT(aEnv);
|
|
MOZ_ASSERT(!*aBuffer);
|
|
|
|
int numTries = 2;
|
|
|
|
while (numTries--) {
|
|
*aBuffer = jni::Object::LocalRef::Adopt(
|
|
aEnv->GetObjectArrayElement(mInputBuffers.Get(), aIndex));
|
|
if (*aBuffer) {
|
|
return NS_OK;
|
|
}
|
|
nsresult res = ResetInputBuffers();
|
|
if (NS_FAILED(res)) {
|
|
return res;
|
|
}
|
|
}
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
bool
|
|
MediaCodecDataDecoder::WaitForInput()
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
|
|
while (State() == kDecoding && mQueue.empty()) {
|
|
// Signal that we require more input.
|
|
INVOKE_CALLBACK(InputExhausted);
|
|
lock.Wait();
|
|
}
|
|
|
|
return State() != kStopping;
|
|
}
|
|
|
|
|
|
MediaRawData*
|
|
MediaCodecDataDecoder::PeekNextSample()
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
|
|
if (State() == kFlushing) {
|
|
mDecoder->Flush();
|
|
ClearQueue();
|
|
State(kDecoding);
|
|
lock.Notify();
|
|
return nullptr;
|
|
}
|
|
|
|
if (mQueue.empty()) {
|
|
if (State() == kDrainQueue) {
|
|
State(kDrainDecoder);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// We're not stopping or flushing, so try to get a sample.
|
|
return mQueue.front();
|
|
}
|
|
|
|
nsresult
|
|
MediaCodecDataDecoder::QueueSample(const MediaRawData* aSample)
|
|
{
|
|
MOZ_ASSERT(aSample);
|
|
AutoLocalJNIFrame frame(jni::GetEnvForThread(), 1);
|
|
|
|
// We have a sample, try to feed it to the decoder.
|
|
int32_t inputIndex = -1;
|
|
nsresult res = mDecoder->DequeueInputBuffer(kDecoderTimeout, &inputIndex);
|
|
if (NS_FAILED(res)) {
|
|
return res;
|
|
}
|
|
|
|
if (inputIndex < 0) {
|
|
// There is no valid input buffer available.
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
jni::Object::LocalRef buffer(frame.GetEnv());
|
|
res = GetInputBuffer(frame.GetEnv(), inputIndex, &buffer);
|
|
if (NS_FAILED(res)) {
|
|
return res;
|
|
}
|
|
|
|
void* directBuffer = frame.GetEnv()->GetDirectBufferAddress(buffer.Get());
|
|
|
|
MOZ_ASSERT(frame.GetEnv()->GetDirectBufferCapacity(buffer.Get()) >=
|
|
aSample->Size(),
|
|
"Decoder buffer is not large enough for sample");
|
|
|
|
PodCopy(static_cast<uint8_t*>(directBuffer), aSample->Data(), aSample->Size());
|
|
|
|
res = mDecoder->QueueInputBuffer(inputIndex, 0, aSample->Size(),
|
|
aSample->mTime, 0);
|
|
if (NS_FAILED(res)) {
|
|
return res;
|
|
}
|
|
|
|
mDurations.push(TimeUnit::FromMicroseconds(aSample->mDuration));
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
MediaCodecDataDecoder::QueueEOS()
|
|
{
|
|
mMonitor.AssertCurrentThreadOwns();
|
|
|
|
nsresult res = NS_OK;
|
|
int32_t inputIndex = -1;
|
|
res = mDecoder->DequeueInputBuffer(kDecoderTimeout, &inputIndex);
|
|
if (NS_FAILED(res) || inputIndex < 0) {
|
|
return res;
|
|
}
|
|
|
|
res = mDecoder->QueueInputBuffer(inputIndex, 0, 0, 0,
|
|
MediaCodec::BUFFER_FLAG_END_OF_STREAM);
|
|
if (NS_SUCCEEDED(res)) {
|
|
State(kDrainWaitEOS);
|
|
mMonitor.Notify();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void
|
|
MediaCodecDataDecoder::HandleEOS(int32_t aOutputStatus)
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
|
|
if (State() == kDrainWaitEOS) {
|
|
State(kDecoding);
|
|
mMonitor.Notify();
|
|
|
|
INVOKE_CALLBACK(DrainComplete);
|
|
}
|
|
|
|
mDecoder->ReleaseOutputBuffer(aOutputStatus, false);
|
|
}
|
|
|
|
TimeUnit
|
|
MediaCodecDataDecoder::GetOutputDuration()
|
|
{
|
|
MOZ_ASSERT(!mDurations.empty(), "Should have had a duration queued");
|
|
const TimeUnit duration = mDurations.front();
|
|
mDurations.pop();
|
|
return duration;
|
|
}
|
|
|
|
nsresult
|
|
MediaCodecDataDecoder::ProcessOutput(
|
|
BufferInfo::Param aInfo, MediaFormat::Param aFormat, int32_t aStatus)
|
|
{
|
|
AutoLocalJNIFrame frame(jni::GetEnvForThread(), 1);
|
|
|
|
const TimeUnit duration = GetOutputDuration();
|
|
const auto buffer = jni::Object::LocalRef::Adopt(
|
|
frame.GetEnv()->GetObjectArrayElement(mOutputBuffers.Get(), aStatus));
|
|
|
|
if (buffer) {
|
|
// The buffer will be null on Android L if we are decoding to a Surface.
|
|
void* directBuffer = frame.GetEnv()->GetDirectBufferAddress(buffer.Get());
|
|
Output(aInfo, directBuffer, aFormat, duration);
|
|
}
|
|
|
|
// The Surface will be updated at this point (for video).
|
|
mDecoder->ReleaseOutputBuffer(aStatus, true);
|
|
PostOutput(aInfo, aFormat, duration);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
MediaCodecDataDecoder::DecoderLoop()
|
|
{
|
|
bool isOutputDone = false;
|
|
AutoLocalJNIFrame frame(jni::GetEnvForThread(), 1);
|
|
RefPtr<MediaRawData> sample;
|
|
MediaFormat::LocalRef outputFormat(frame.GetEnv());
|
|
nsresult res = NS_OK;
|
|
|
|
while (WaitForInput()) {
|
|
sample = PeekNextSample();
|
|
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
if (State() == kDrainDecoder) {
|
|
MOZ_ASSERT(!sample, "Shouldn't have a sample when pushing EOF frame");
|
|
res = QueueEOS();
|
|
BREAK_ON_DECODER_ERROR();
|
|
}
|
|
}
|
|
|
|
if (sample) {
|
|
res = QueueSample(sample);
|
|
if (NS_SUCCEEDED(res)) {
|
|
// We've fed this into the decoder, so remove it from the queue.
|
|
MonitorAutoLock lock(mMonitor);
|
|
mQueue.pop();
|
|
isOutputDone = false;
|
|
}
|
|
}
|
|
|
|
if (isOutputDone) {
|
|
continue;
|
|
}
|
|
|
|
BufferInfo::LocalRef bufferInfo;
|
|
nsresult res = BufferInfo::New(&bufferInfo);
|
|
BREAK_ON_DECODER_ERROR();
|
|
|
|
int32_t outputStatus = -1;
|
|
res = mDecoder->DequeueOutputBuffer(bufferInfo, kDecoderTimeout,
|
|
&outputStatus);
|
|
BREAK_ON_DECODER_ERROR();
|
|
|
|
if (outputStatus == MediaCodec::INFO_TRY_AGAIN_LATER) {
|
|
// We might want to call mCallback->InputExhausted() here, but there seems
|
|
// to be some possible bad interactions here with the threading.
|
|
} else if (outputStatus == MediaCodec::INFO_OUTPUT_BUFFERS_CHANGED) {
|
|
res = ResetOutputBuffers();
|
|
BREAK_ON_DECODER_ERROR();
|
|
} else if (outputStatus == MediaCodec::INFO_OUTPUT_FORMAT_CHANGED) {
|
|
res = mDecoder->GetOutputFormat(ReturnTo(&outputFormat));
|
|
BREAK_ON_DECODER_ERROR();
|
|
} else if (outputStatus < 0) {
|
|
NS_WARNING("Unknown error from decoder!");
|
|
INVOKE_CALLBACK(Error);
|
|
// Don't break here just in case it's recoverable. If it's not, other
|
|
// stuff will fail later and we'll bail out.
|
|
} else {
|
|
// We have a valid buffer index >= 0 here.
|
|
int32_t flags;
|
|
nsresult res = bufferInfo->Flags(&flags);
|
|
BREAK_ON_DECODER_ERROR();
|
|
|
|
if (flags & MediaCodec::BUFFER_FLAG_END_OF_STREAM) {
|
|
HandleEOS(outputStatus);
|
|
isOutputDone = true;
|
|
// We only queue empty EOF frames, so we're done for now.
|
|
continue;
|
|
}
|
|
|
|
res = ProcessOutput(bufferInfo, outputFormat, outputStatus);
|
|
BREAK_ON_DECODER_ERROR();
|
|
}
|
|
}
|
|
|
|
Cleanup();
|
|
|
|
// We're done.
|
|
MonitorAutoLock lock(mMonitor);
|
|
State(kShutdown);
|
|
mMonitor.Notify();
|
|
}
|
|
|
|
const char*
|
|
MediaCodecDataDecoder::ModuleStateStr(ModuleState aState) {
|
|
static const char* kStr[] = {
|
|
"Decoding", "Flushing", "DrainQueue", "DrainDecoder", "DrainWaitEOS",
|
|
"Stopping", "Shutdown"
|
|
};
|
|
|
|
MOZ_ASSERT(aState < sizeof(kStr) / sizeof(kStr[0]));
|
|
return kStr[aState];
|
|
}
|
|
|
|
MediaCodecDataDecoder::ModuleState
|
|
MediaCodecDataDecoder::State() const
|
|
{
|
|
return mState;
|
|
}
|
|
|
|
void
|
|
MediaCodecDataDecoder::State(ModuleState aState)
|
|
{
|
|
LOG("%s -> %s", ModuleStateStr(mState), ModuleStateStr(aState));
|
|
|
|
if (aState == kDrainDecoder) {
|
|
MOZ_ASSERT(mState == kDrainQueue);
|
|
} else if (aState == kDrainWaitEOS) {
|
|
MOZ_ASSERT(mState == kDrainDecoder);
|
|
}
|
|
|
|
mState = aState;
|
|
}
|
|
|
|
void
|
|
MediaCodecDataDecoder::ClearQueue()
|
|
{
|
|
mMonitor.AssertCurrentThreadOwns();
|
|
|
|
while (!mQueue.empty()) {
|
|
mQueue.pop();
|
|
}
|
|
while (!mDurations.empty()) {
|
|
mDurations.pop();
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
MediaCodecDataDecoder::Input(MediaRawData* aSample)
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
mQueue.push(aSample);
|
|
lock.NotifyAll();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
MediaCodecDataDecoder::ResetInputBuffers()
|
|
{
|
|
return mDecoder->GetInputBuffers(ReturnTo(&mInputBuffers));
|
|
}
|
|
|
|
nsresult
|
|
MediaCodecDataDecoder::ResetOutputBuffers()
|
|
{
|
|
return mDecoder->GetOutputBuffers(ReturnTo(&mOutputBuffers));
|
|
}
|
|
|
|
nsresult
|
|
MediaCodecDataDecoder::Flush()
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
State(kFlushing);
|
|
lock.Notify();
|
|
|
|
while (State() == kFlushing) {
|
|
lock.Wait();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
MediaCodecDataDecoder::Drain()
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
if (State() == kDrainDecoder || State() == kDrainQueue) {
|
|
return NS_OK;
|
|
}
|
|
|
|
State(kDrainQueue);
|
|
lock.Notify();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
MediaCodecDataDecoder::Shutdown()
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
|
|
if (!mThread || State() == kStopping) {
|
|
// Already shutdown or in the process of doing so
|
|
return NS_OK;
|
|
}
|
|
|
|
State(kStopping);
|
|
lock.Notify();
|
|
|
|
while (State() == kStopping) {
|
|
lock.Wait();
|
|
}
|
|
|
|
mThread->Shutdown();
|
|
mThread = nullptr;
|
|
|
|
mDecoder->Stop();
|
|
mDecoder->Release();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // mozilla
|