mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-27 02:47:31 +09:00
Merge remote-tracking branch 'origin/tracking' into custom
This commit is contained in:
commit
7f068ecce3
969 changed files with 96279 additions and 469 deletions
|
|
@ -14,6 +14,10 @@
|
|||
#include "webrtc/MediaEngineWebRTC.h"
|
||||
#endif
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
extern mozilla::LazyLogModule gMediaStreamGraphLog;
|
||||
#define STREAM_LOG(type, msg) MOZ_LOG(gMediaStreamGraphLog, type, msg)
|
||||
|
||||
|
|
@ -582,6 +586,32 @@ AudioCallbackDriver::~AudioCallbackDriver()
|
|||
MOZ_ASSERT(mPromisesForOperation.IsEmpty());
|
||||
}
|
||||
|
||||
bool IsMacbookOrMacbookAir()
|
||||
{
|
||||
#ifdef XP_MACOSX
|
||||
size_t len = 0;
|
||||
sysctlbyname("hw.model", NULL, &len, NULL, 0);
|
||||
if (len) {
|
||||
UniquePtr<char[]> model(new char[len]);
|
||||
// This string can be
|
||||
// MacBook%d,%d for a normal MacBook
|
||||
// MacBookPro%d,%d for a MacBook Pro
|
||||
// MacBookAir%d,%d for a Macbook Air
|
||||
sysctlbyname("hw.model", model.get(), &len, NULL, 0);
|
||||
char* substring = strstr(model.get(), "MacBook");
|
||||
if (substring) {
|
||||
const size_t offset = strlen("MacBook");
|
||||
if (strncmp(model.get() + offset, "Air", len - offset) ||
|
||||
isdigit(model[offset + 1])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
AudioCallbackDriver::Init()
|
||||
{
|
||||
|
|
@ -618,6 +648,13 @@ AudioCallbackDriver::Init()
|
|||
}
|
||||
}
|
||||
|
||||
// Macbook and MacBook air don't have enough CPU to run very low latency
|
||||
// MediaStreamGraphs, cap the minimal latency to 512 frames int this case.
|
||||
if (IsMacbookOrMacbookAir()) {
|
||||
latency_frames = std::max((uint32_t) 512, latency_frames);
|
||||
}
|
||||
|
||||
|
||||
input = output;
|
||||
input.channels = mInputChannels; // change to support optional stereo capture
|
||||
|
||||
|
|
@ -1031,6 +1068,44 @@ AudioCallbackDriver::MixerCallback(AudioDataValue* aMixedBuffer,
|
|||
NS_WARNING_ASSERTION(written == aFrames - toWrite, "Dropping frames.");
|
||||
};
|
||||
|
||||
void AudioCallbackDriver::PanOutputIfNeeded(bool aMicrophoneActive)
|
||||
{
|
||||
#ifdef XP_MACOSX
|
||||
cubeb_device* out;
|
||||
int rv;
|
||||
char name[128];
|
||||
size_t length = sizeof(name);
|
||||
|
||||
rv = sysctlbyname("hw.model", name, &length, NULL, 0);
|
||||
if (rv) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strncmp(name, "MacBookPro", 10)) {
|
||||
if (cubeb_stream_get_current_device(mAudioStream, &out) == CUBEB_OK) {
|
||||
// Check if we are currently outputing sound on external speakers.
|
||||
if (!strcmp(out->output_name, "ispk")) {
|
||||
// Pan everything to the right speaker.
|
||||
if (aMicrophoneActive) {
|
||||
if (cubeb_stream_set_panning(mAudioStream, 1.0) != CUBEB_OK) {
|
||||
NS_WARNING("Could not pan audio output to the right.");
|
||||
}
|
||||
} else {
|
||||
if (cubeb_stream_set_panning(mAudioStream, 0.0) != CUBEB_OK) {
|
||||
NS_WARNING("Could not pan audio output to the center.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (cubeb_stream_set_panning(mAudioStream, 0.0) != CUBEB_OK) {
|
||||
NS_WARNING("Could not pan audio output to the center.");
|
||||
}
|
||||
}
|
||||
cubeb_stream_device_destroy(mAudioStream, out);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
AudioCallbackDriver::DeviceChangedCallback() {
|
||||
// Tell the audio engine the device has changed, it might want to reset some
|
||||
|
|
@ -1039,6 +1114,9 @@ AudioCallbackDriver::DeviceChangedCallback() {
|
|||
if (mAudioInput) {
|
||||
mAudioInput->DeviceChanged();
|
||||
}
|
||||
#ifdef XP_MACOSX
|
||||
PanOutputIfNeeded(mMicrophoneActive);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -1047,6 +1125,10 @@ AudioCallbackDriver::SetMicrophoneActive(bool aActive)
|
|||
MonitorAutoLock mon(mGraphImpl->GetMonitor());
|
||||
|
||||
mMicrophoneActive = aActive;
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
PanOutputIfNeeded(mMicrophoneActive);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t
|
||||
|
|
|
|||
|
|
@ -456,6 +456,11 @@ public:
|
|||
|
||||
void CompleteAudioContextOperations(AsyncCubebOperation aOperation);
|
||||
private:
|
||||
/**
|
||||
* On certain MacBookPro, the microphone is located near the left speaker.
|
||||
* We need to pan the sound output to the right speaker if we are using the
|
||||
* mic and the built-in speaker, or we will have terrible echo. */
|
||||
void PanOutputIfNeeded(bool aMicrophoneActive);
|
||||
/**
|
||||
* This is called when the output device used by the cubeb stream changes. */
|
||||
void DeviceChangedCallback();
|
||||
|
|
|
|||
|
|
@ -213,6 +213,7 @@ public:
|
|||
, mCodecSpecificConfig(aOther.mCodecSpecificConfig)
|
||||
, mExtraData(aOther.mExtraData)
|
||||
, mRotation(aOther.mRotation)
|
||||
, mBitDepth(aOther.mBitDepth)
|
||||
, mImageRect(aOther.mImageRect)
|
||||
{
|
||||
}
|
||||
|
|
@ -303,6 +304,9 @@ public:
|
|||
// Describing how many degrees video frames should be rotated in clock-wise to
|
||||
// get correct view.
|
||||
Rotation mRotation;
|
||||
|
||||
// Bits per channel -- Should be 8, 10 or 12. Default value is 8.
|
||||
uint8_t mBitDepth = 8;
|
||||
|
||||
private:
|
||||
// mImage may be cropped; currently only used with the WebM container.
|
||||
|
|
|
|||
|
|
@ -223,13 +223,186 @@ already_AddRefed<SharedThreadPool> GetMediaThreadPool(MediaThreadType aType)
|
|||
return pool.forget();
|
||||
}
|
||||
|
||||
bool
|
||||
ExtractVPXCodecDetails(const nsAString& aCodec,
|
||||
uint8_t& aProfile,
|
||||
uint8_t& aLevel,
|
||||
uint8_t& aBitDepth)
|
||||
{
|
||||
uint8_t dummyChromaSubsampling = 1;
|
||||
VideoColorSpace dummyColorspace;
|
||||
return ExtractVPXCodecDetails(aCodec,
|
||||
aProfile,
|
||||
aLevel,
|
||||
aBitDepth,
|
||||
dummyChromaSubsampling,
|
||||
dummyColorspace);
|
||||
}
|
||||
|
||||
bool ExtractVPXCodecDetails(const nsAString& aCodec,
|
||||
uint8_t& aProfile,
|
||||
uint8_t& aLevel,
|
||||
uint8_t& aBitDepth,
|
||||
uint8_t& aChromaSubsampling,
|
||||
VideoColorSpace& aColorSpace)
|
||||
{
|
||||
nsTArray<nsString> fieldsArr;
|
||||
|
||||
// Assign default value.
|
||||
aChromaSubsampling = 1;
|
||||
|
||||
nsCharSeparatedTokenizer tokenizer(aCodec, '.');
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
const nsSubstring& token = tokenizer.nextToken();
|
||||
fieldsArr.AppendElement(token);
|
||||
}
|
||||
auto fourCC = fieldsArr[0];
|
||||
|
||||
if (!fourCC.EqualsLiteral("vp09") && !fourCC.EqualsLiteral("vp08")) {
|
||||
// Invalid 4CC
|
||||
return false;
|
||||
}
|
||||
uint8_t *fields[] = { &aProfile, &aLevel, &aBitDepth, &aChromaSubsampling,
|
||||
&aColorSpace.mPrimaryId, &aColorSpace.mTransferId,
|
||||
&aColorSpace.mMatrixId, &aColorSpace.mRangeId };
|
||||
int fieldsCount = 0;
|
||||
nsresult rv;
|
||||
for (int fieldsItr = 1; fieldsItr < fieldsArr.Length(); ++fieldsItr, ++fieldsCount) {
|
||||
if (fieldsCount > 7) {
|
||||
// No more than 8 fields are expected.
|
||||
return false;
|
||||
}
|
||||
*(fields[fieldsCount]) =
|
||||
static_cast<uint8_t>(fieldsArr[fieldsItr].ToInteger(&rv));
|
||||
// We got invalid field value, parsing error.
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
}
|
||||
// Mandatory Fields
|
||||
// <sample entry 4CC>.<profile>.<level>.<bitDepth>.
|
||||
// Optional Fields
|
||||
// <chromaSubsampling>.<colourPrimaries>.<transferCharacteristics>.
|
||||
// <matrixCoefficients>.<videoFullRangeFlag>
|
||||
// First three fields are mandatory(we have parsed 4CC).
|
||||
if (fieldsCount < 3) {
|
||||
// Invalid number of fields.
|
||||
return false;
|
||||
}
|
||||
// Start to validate the parsing value.
|
||||
|
||||
// profile should be 0,1,2 or 3.
|
||||
// See https://www.webmproject.org/vp9/profiles/
|
||||
// We don't support more than profile 2
|
||||
if (aProfile > 2) {
|
||||
// Invalid profile.
|
||||
return false;
|
||||
}
|
||||
|
||||
// level, See https://www.webmproject.org/vp9/mp4/#semantics_1
|
||||
switch (aLevel) {
|
||||
case 10:
|
||||
case 11:
|
||||
case 20:
|
||||
case 21:
|
||||
case 30:
|
||||
case 31:
|
||||
case 40:
|
||||
case 41:
|
||||
case 50:
|
||||
case 51:
|
||||
case 52:
|
||||
case 60:
|
||||
case 61:
|
||||
case 62:
|
||||
break;
|
||||
default:
|
||||
// Invalid level.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (aBitDepth != 8 && aBitDepth != 10 && aBitDepth != 12) {
|
||||
// Invalid bitDepth:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fieldsCount == 3) {
|
||||
// No more options.
|
||||
return true;
|
||||
}
|
||||
|
||||
// chromaSubsampling should be 0,1,2,3...4~7 are reserved.
|
||||
if (aChromaSubsampling > 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fieldsCount == 4) {
|
||||
// No more options.
|
||||
return true;
|
||||
}
|
||||
|
||||
// It is an integer that is defined by the "Colour primaries"
|
||||
// section of ISO/IEC 23001-8:2016 Table 2.
|
||||
// We treat reserved value as false case.
|
||||
const auto& primaryId = aColorSpace.mPrimaryId;
|
||||
if (primaryId == 0 || primaryId == 3 || primaryId > 22) {
|
||||
// reserved value.
|
||||
return false;
|
||||
}
|
||||
if (primaryId > 12 && primaryId < 22) {
|
||||
// 13~21 are reserved values.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fieldsCount == 5) {
|
||||
// No more options.
|
||||
return true;
|
||||
}
|
||||
|
||||
// It is an integer that is defined by the
|
||||
// "Transfer characteristics" section of ISO/IEC 23001-8:2016 Table 3.
|
||||
// We treat reserved value as false case.
|
||||
const auto& transferId = aColorSpace.mTransferId;
|
||||
if (transferId == 0 || transferId == 3 || transferId > 18) {
|
||||
// reserved value.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fieldsCount == 6) {
|
||||
// No more options.
|
||||
return true;
|
||||
}
|
||||
|
||||
// It is an integer that is defined by the
|
||||
// "Matrix coefficients" section of ISO/IEC 23001-8:2016 Table 4.
|
||||
// We treat reserved value as false case.
|
||||
const auto& matrixId = aColorSpace.mMatrixId;
|
||||
if (matrixId == 3 || matrixId > 11) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If matrixCoefficients is 0 (RGB), then chroma subsampling MUST be 3 (4:4:4).
|
||||
if (matrixId == 0 && aChromaSubsampling != 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fieldsCount == 7) {
|
||||
// No more options.
|
||||
return true;
|
||||
}
|
||||
|
||||
// videoFullRangeFlag indicates the black level and range of the luma and
|
||||
// chroma signals. 0 = legal range (e.g. 16-235 for 8 bit sample depth);
|
||||
// 1 = full range (e.g. 0-255 for 8-bit sample depth).
|
||||
const auto& rangeId = aColorSpace.mRangeId;
|
||||
return rangeId <= 1;
|
||||
}
|
||||
|
||||
bool
|
||||
ExtractH264CodecDetails(const nsAString& aCodec,
|
||||
int16_t& aProfile,
|
||||
int16_t& aLevel)
|
||||
{
|
||||
// H.264 codecs parameters have a type defined as avcN.PPCCLL, where
|
||||
// N = avc type. avc3 is avcc with SPS & PPS implicit (within stream)
|
||||
// N = avc type. avc3 is avcc with SPS & PPS implicit (within stream)
|
||||
// PP = profile_idc, CC = constraint_set flags, LL = level_idc.
|
||||
// We ignore the constraint_set flags, as it's not clear from any
|
||||
// documentation what constraints the platform decoders support.
|
||||
|
|
@ -469,15 +642,25 @@ IsAACCodecString(const nsAString& aCodec)
|
|||
bool
|
||||
IsVP8CodecString(const nsAString& aCodec)
|
||||
{
|
||||
uint8_t profile = 0;
|
||||
uint8_t level = 0;
|
||||
uint8_t bitDepth = 0;
|
||||
return aCodec.EqualsLiteral("vp8") ||
|
||||
aCodec.EqualsLiteral("vp8.0");
|
||||
aCodec.EqualsLiteral("vp8.0") ||
|
||||
(StartsWith(NS_ConvertUTF16toUTF8(aCodec), "vp08") &&
|
||||
ExtractVPXCodecDetails(aCodec, profile, level, bitDepth));
|
||||
}
|
||||
|
||||
bool
|
||||
IsVP9CodecString(const nsAString& aCodec)
|
||||
{
|
||||
uint8_t profile = 0;
|
||||
uint8_t level = 0;
|
||||
uint8_t bitDepth = 0;
|
||||
return aCodec.EqualsLiteral("vp9") ||
|
||||
aCodec.EqualsLiteral("vp9.0");
|
||||
aCodec.EqualsLiteral("vp9.0") ||
|
||||
(StartsWith(NS_ConvertUTF16toUTF8(aCodec), "vp09") &&
|
||||
ExtractVPXCodecDetails(aCodec, profile, level, bitDepth));
|
||||
}
|
||||
|
||||
#ifdef MOZ_AV1
|
||||
|
|
|
|||
|
|
@ -258,6 +258,34 @@ ExtractH264CodecDetails(const nsAString& aCodecs,
|
|||
int16_t& aProfile,
|
||||
int16_t& aLevel);
|
||||
|
||||
struct VideoColorSpace
|
||||
{
|
||||
// TODO: Define the value type as strong type enum
|
||||
// to better know the exact meaning corresponding to ISO/IEC 23001-8:2016.
|
||||
// Default value is listed https://www.webmproject.org/vp9/mp4/#optional-fields
|
||||
uint8_t mPrimaryId = 1; // Table 2
|
||||
uint8_t mTransferId = 1; // Table 3
|
||||
uint8_t mMatrixId = 1; // Table 4
|
||||
uint8_t mRangeId = 0;
|
||||
};
|
||||
|
||||
// Extracts the VPX codecs parameter string.
|
||||
// See https://www.webmproject.org/vp9/mp4/#codecs-parameter-string
|
||||
// for more details.
|
||||
// Returns false on failure.
|
||||
bool
|
||||
ExtractVPXCodecDetails(const nsAString& aCodec,
|
||||
uint8_t& aProfile,
|
||||
uint8_t& aLevel,
|
||||
uint8_t& aBitDepth);
|
||||
bool
|
||||
ExtractVPXCodecDetails(const nsAString& aCodec,
|
||||
uint8_t& aProfile,
|
||||
uint8_t& aLevel,
|
||||
uint8_t& aBitDepth,
|
||||
uint8_t& aChromaSubsampling,
|
||||
VideoColorSpace& aColorSpace);
|
||||
|
||||
// Use a cryptographic quality PRNG to generate raw random bytes
|
||||
// and convert that to a base64 string.
|
||||
nsresult
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@
|
|||
#ifdef XP_WIN
|
||||
#include "mozilla/WindowsVersion.h"
|
||||
#endif
|
||||
#ifdef XP_MACOSX
|
||||
#include "nsCocoaFeatures.h"
|
||||
#endif
|
||||
#include "nsPrintfCString.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
|
|
|||
|
|
@ -132,6 +132,19 @@ MP4Decoder::CanHandleMediaType(const MediaContentType& aType,
|
|||
NS_LITERAL_CSTRING("audio/flac"), aType));
|
||||
continue;
|
||||
}
|
||||
if (IsVP9CodecString(codec)) {
|
||||
auto trackInfo =
|
||||
CreateTrackInfoWithMIMETypeAndContentTypeExtraParameters(
|
||||
NS_LITERAL_CSTRING("video/vp9"), aType);
|
||||
uint8_t profile = 0;
|
||||
uint8_t level = 0;
|
||||
uint8_t bitDepth = 0;
|
||||
if (ExtractVPXCodecDetails(codec, profile, level, bitDepth)) {
|
||||
trackInfo->GetAsVideoInfo()->mBitDepth = bitDepth;
|
||||
}
|
||||
trackInfos.AppendElement(Move(trackInfo));
|
||||
continue;
|
||||
}
|
||||
#ifdef MOZ_AV1
|
||||
if (IsAV1CodecString(codec)) {
|
||||
trackInfos.AppendElement(
|
||||
|
|
|
|||
|
|
@ -110,12 +110,14 @@ GetPluginFile(const nsAString& aPluginPath,
|
|||
nsAutoString baseName;
|
||||
GetFileBase(aPluginPath, aLibDirectory, aLibFile, baseName);
|
||||
|
||||
#if defined(OS_POSIX)
|
||||
#if defined(XP_MACOSX)
|
||||
nsAutoString binaryName = NS_LITERAL_STRING("lib") + baseName + NS_LITERAL_STRING(".dylib");
|
||||
#elif defined(OS_POSIX)
|
||||
nsAutoString binaryName = NS_LITERAL_STRING("lib") + baseName + NS_LITERAL_STRING(".so");
|
||||
#elif defined(XP_WIN)
|
||||
nsAutoString binaryName = baseName + NS_LITERAL_STRING(".dll");
|
||||
#else
|
||||
#error Unsupported O.S.
|
||||
#error not defined
|
||||
#endif
|
||||
aLibFile->AppendRelativePath(binaryName);
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,14 @@
|
|||
#include "windows.h"
|
||||
#endif
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
#include <assert.h>
|
||||
#ifdef HASH_NODE_ID_WITH_DEVICE_ID
|
||||
#include <unistd.h>
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_vm.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // HASH_NODE_ID_WITH_DEVICE_ID
|
||||
|
||||
|
|
@ -75,6 +83,46 @@ GetStackAfterCurrentFrame(uint8_t** aOutTop, uint8_t** aOutBottom)
|
|||
}
|
||||
#endif
|
||||
|
||||
#if defined(XP_MACOSX) && defined(HASH_NODE_ID_WITH_DEVICE_ID)
|
||||
static mach_vm_address_t
|
||||
RegionContainingAddress(mach_vm_address_t aAddress)
|
||||
{
|
||||
mach_port_t task;
|
||||
kern_return_t kr = task_for_pid(mach_task_self(), getpid(), &task);
|
||||
if (kr != KERN_SUCCESS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
mach_vm_address_t address = aAddress;
|
||||
mach_vm_size_t size;
|
||||
vm_region_basic_info_data_64_t info;
|
||||
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
|
||||
mach_port_t object_name;
|
||||
kr = mach_vm_region(task, &address, &size, VM_REGION_BASIC_INFO_64,
|
||||
reinterpret_cast<vm_region_info_t>(&info), &count,
|
||||
&object_name);
|
||||
if (kr != KERN_SUCCESS || size == 0
|
||||
|| address > aAddress || address + size <= aAddress) {
|
||||
// mach_vm_region failed, or couldn't find region at given address.
|
||||
return 0;
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
MOZ_NEVER_INLINE
|
||||
static bool
|
||||
GetStackAfterCurrentFrame(uint8_t** aOutTop, uint8_t** aOutBottom)
|
||||
{
|
||||
mach_vm_address_t stackFrame =
|
||||
reinterpret_cast<mach_vm_address_t>(__builtin_frame_address(0));
|
||||
*aOutTop = reinterpret_cast<uint8_t*>(stackFrame);
|
||||
// Kernel code shows that stack is always a single region.
|
||||
*aOutBottom = reinterpret_cast<uint8_t*>(RegionContainingAddress(stackFrame));
|
||||
return *aOutBottom && (*aOutBottom < *aOutTop);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HASH_NODE_ID_WITH_DEVICE_ID
|
||||
static void SecureMemset(void* start, uint8_t value, size_t size)
|
||||
{
|
||||
|
|
|
|||
111
dom/media/mediasource/gtest/TestExtractVPXCodecDetails.cpp
Normal file
111
dom/media/mediasource/gtest/TestExtractVPXCodecDetails.cpp
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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 <gtest/gtest.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "VideoUtils.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
struct TestData
|
||||
{
|
||||
const char16_t* const mCodecParameterString;
|
||||
const bool mExpectedValue;
|
||||
};
|
||||
|
||||
TEST(ExtractVPXCodecDetails, TestDataLength) {
|
||||
TestData tests[] =
|
||||
{
|
||||
{u"vp09.00.11.08", true }, // valid case
|
||||
{u"vp09.00.11.08.00", true }, // valid case, have extra optional field
|
||||
{u"vp09.02.10.10.01.09.16.09.01", true}, // maximum length valid case
|
||||
{u"vp09", false }, // lack of mandatory fields
|
||||
{u"vp09.00", false }, // lack of mandatory fields
|
||||
{u"vp09.00.11", false }, // lack of mandatory fields
|
||||
{u"vp09.02.10.10.01.09.16.09.01.00", false} // more than 9 fields, invalid case.
|
||||
};
|
||||
|
||||
for (const auto& data : tests) {
|
||||
uint8_t profile = 0;
|
||||
uint8_t level = 0;
|
||||
uint8_t bitDepth = 0;
|
||||
bool result = ExtractVPXCodecDetails(nsString(data.mCodecParameterString), profile, level, bitDepth);
|
||||
EXPECT_EQ(result, data.mExpectedValue) << NS_ConvertUTF16toUTF8(data.mCodecParameterString).get();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ExtractVPXCodecDetails, TestInputData) {
|
||||
TestData tests[] =
|
||||
{
|
||||
{u"vp09.02..08", false}, // malformed
|
||||
{u"vp9.02.10.08", false}, // invalid 4CC
|
||||
{u"vp09.03.11.08", false }, // profile should < 3
|
||||
{u"vp09.00.63.08.00", false }, // invalid level
|
||||
{u"vp09.02.10.13", false}, // invalid bitDepth
|
||||
{u"vp09.02.10.10.04", false}, // invalid chromasubsampling, should < 4
|
||||
{u"vp09.02.10.10.01.00", false}, // invalid Colour primaries, should not be 0,3 or < 23.
|
||||
{u"vp09.02.10.10.01.03", false}, // invalid Colour primaries.
|
||||
{u"vp09.02.10.10.01.23", false}, // invalid Colour primaries.
|
||||
{u"vp09.02.10.10.01.09.00", false}, // invalid Transfer characteristics, should not be 0,3 or < 19.
|
||||
{u"vp09.02.10.10.01.09.03", false}, // invalid Transfer characteristics.
|
||||
{u"vp09.02.10.10.01.09.19", false}, // invalid Transfer characteristics.
|
||||
{u"vp09.02.10.10.01.09.16.12", false}, // invalid Matrix coefficients, should not be 3 or < 12.
|
||||
{u"vp09.02.10.10.01.09.16.03", false}, // invalid matrix.
|
||||
{u"vp09.02.10.10.01.09.16.09.02", false}, // invalid range, should < 2.
|
||||
// Test if matrixCoefficients is 0 (RGB), then chroma subsampling MUST be 3 (4:4:4).
|
||||
{u"vp09.02.10.08.03.09.16.00.00", true} // invalid combination.
|
||||
};
|
||||
|
||||
for (const auto& data : tests) {
|
||||
uint8_t profile = 0;
|
||||
uint8_t level = 0;
|
||||
uint8_t bitDepth = 0;
|
||||
bool result = ExtractVPXCodecDetails(nsString(data.mCodecParameterString), profile, level, bitDepth);
|
||||
EXPECT_EQ(result, data.mExpectedValue) << NS_ConvertUTF16toUTF8(data.mCodecParameterString).get();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ExtractVPXCodecDetails, TestParsingOutput) {
|
||||
uint8_t profile = 0;
|
||||
uint8_t level = 0;
|
||||
uint8_t bitDepth = 0;
|
||||
uint8_t chromaSubsampling = 0;
|
||||
VideoColorSpace colorSpace;
|
||||
auto data = u"vp09.01.11.08";
|
||||
bool result = ExtractVPXCodecDetails(nsString(data),
|
||||
profile,
|
||||
level,
|
||||
bitDepth,
|
||||
chromaSubsampling,
|
||||
colorSpace);
|
||||
EXPECT_EQ(result, true);
|
||||
EXPECT_EQ(profile, 1);
|
||||
EXPECT_EQ(level, 11);
|
||||
EXPECT_EQ(bitDepth, 8);
|
||||
// Should keep spec defined default value.
|
||||
EXPECT_EQ(chromaSubsampling, 1);
|
||||
EXPECT_EQ(colorSpace.mPrimaryId, 1);
|
||||
EXPECT_EQ(colorSpace.mTransferId, 1);
|
||||
EXPECT_EQ(colorSpace.mMatrixId, 1);
|
||||
EXPECT_EQ(colorSpace.mRangeId, 0);
|
||||
|
||||
data = u"vp09.02.10.10.01.09.16.09.01";
|
||||
result = ExtractVPXCodecDetails(nsString(data),
|
||||
profile,
|
||||
level,
|
||||
bitDepth,
|
||||
chromaSubsampling,
|
||||
colorSpace);
|
||||
EXPECT_EQ(result, true);
|
||||
EXPECT_EQ(profile, 2);
|
||||
EXPECT_EQ(level, 10);
|
||||
EXPECT_EQ(bitDepth, 10);
|
||||
EXPECT_EQ(chromaSubsampling, 1);
|
||||
EXPECT_EQ(colorSpace.mPrimaryId, 9);
|
||||
EXPECT_EQ(colorSpace.mTransferId, 16);
|
||||
EXPECT_EQ(colorSpace.mMatrixId, 9);
|
||||
EXPECT_EQ(colorSpace.mRangeId, 1);
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
UNIFIED_SOURCES += [
|
||||
'TestContainerParser.cpp',
|
||||
'TestExtractVPXCodecDetails.cpp',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
|
|
|
|||
|
|
@ -130,9 +130,11 @@ public:
|
|||
virtual bool Supports(const TrackInfo& aTrackInfo,
|
||||
DecoderDoctorDiagnostics* aDiagnostics) const
|
||||
{
|
||||
// By default, fall back to SupportsMimeType with just the MIME string.
|
||||
// (So PDMs do not need to override this method -- yet.)
|
||||
return SupportsMimeType(aTrackInfo.mMimeType, aDiagnostics);
|
||||
if (!SupportsMimeType(aTrackInfo.mMimeType, aDiagnostics)) {
|
||||
return false;
|
||||
}
|
||||
const auto videoInfo = aTrackInfo.GetAsVideoInfo();
|
||||
return !videoInfo || SupportsBitDepth(videoInfo->mBitDepth, aDiagnostics);
|
||||
}
|
||||
|
||||
enum class ConversionRequired : uint8_t {
|
||||
|
|
@ -154,6 +156,15 @@ protected:
|
|||
friend class PDMFactory;
|
||||
friend class dom::RemoteDecoderModule;
|
||||
|
||||
// Indicates if the PlatformDecoderModule supports decoding of aBitDepth.
|
||||
// Should override this method when the platform can support bitDepth != 8.
|
||||
virtual bool SupportsBitDepth(const uint8_t aBitDepth,
|
||||
DecoderDoctorDiagnostics* aDiagnostics) const
|
||||
{
|
||||
return aBitDepth == 8;
|
||||
}
|
||||
|
||||
|
||||
// Creates a Video decoder. The layers backend is passed in so that
|
||||
// decoders can determine whether hardware accelerated decoding can be used.
|
||||
// Asynchronous decoding of video should be done in runnables dispatched
|
||||
|
|
|
|||
|
|
@ -77,6 +77,21 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
bool SupportsBitDepth(const uint8_t aBitDepth,
|
||||
DecoderDoctorDiagnostics* aDiagnostics) const override
|
||||
{
|
||||
// We don't support bitDepth > 8 when compositor backend is D3D11.
|
||||
// But we don't have KnowsCompositor or any object
|
||||
// that we can ask for the layersbackend type.
|
||||
// We should remove this restriction until
|
||||
// we solve the D3D11 compositor backend issue.
|
||||
#if defined(XP_LINUX) || defined(XP_MACOSX)
|
||||
return true;
|
||||
#endif
|
||||
return aBitDepth == 8;
|
||||
}
|
||||
|
||||
private:
|
||||
FFmpegLibWrapper* mLib;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -26,6 +26,14 @@ public:
|
|||
static FFmpegLibWrapper sLibAV;
|
||||
|
||||
static const char* sLibs[] = {
|
||||
#if defined(XP_DARWIN)
|
||||
"libavcodec.58.dylib",
|
||||
"libavcodec.57.dylib",
|
||||
"libavcodec.56.dylib",
|
||||
"libavcodec.55.dylib",
|
||||
"libavcodec.54.dylib",
|
||||
"libavcodec.53.dylib",
|
||||
#else
|
||||
"libavcodec.so.58",
|
||||
"libavcodec-ffmpeg.so.58",
|
||||
"libavcodec-ffmpeg.so.57",
|
||||
|
|
@ -35,6 +43,7 @@ static const char* sLibs[] = {
|
|||
"libavcodec.so.55",
|
||||
"libavcodec.so.54",
|
||||
"libavcodec.so.53",
|
||||
#endif
|
||||
};
|
||||
|
||||
/* static */ bool
|
||||
|
|
|
|||
|
|
@ -205,6 +205,14 @@ bool
|
|||
WMFDecoderModule::Supports(const TrackInfo& aTrackInfo,
|
||||
DecoderDoctorDiagnostics* aDiagnostics) const
|
||||
{
|
||||
// Check bit depth of video.
|
||||
// XXXMC: This is here in case we want to start accepting HDR video. Do we?
|
||||
// This currently defaults to a fail if video bitdepth != 8
|
||||
const auto videoInfo = aTrackInfo.GetAsVideoInfo();
|
||||
if (videoInfo && !SupportsBitDepth(videoInfo->mBitDepth, aDiagnostics)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((aTrackInfo.mMimeType.EqualsLiteral("audio/mp4a-latm") ||
|
||||
aTrackInfo.mMimeType.EqualsLiteral("audio/mp4")) &&
|
||||
WMFDecoderModule::HasAAC()) {
|
||||
|
|
@ -221,7 +229,7 @@ WMFDecoderModule::Supports(const TrackInfo& aTrackInfo,
|
|||
return false;
|
||||
}
|
||||
} else {
|
||||
// Windows <=7 supports at most 1920x1088.
|
||||
// Windows 7 supports at most 1920x1088.
|
||||
if (videoInfo->mImage.width > 1920 || videoInfo->mImage.height > 1088) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ SOURCES += [
|
|||
'../VideoSegment.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
|
||||
UNIFIED_SOURCES += ['../systemservices/OSXRunLoopSingleton.cpp']
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/caps',
|
||||
'/dom/base',
|
||||
|
|
|
|||
|
|
@ -31,6 +31,12 @@
|
|||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
#include <mach/mach_host.h>
|
||||
#include <mach/mach_init.h>
|
||||
#include <mach/host_info.h>
|
||||
#endif
|
||||
|
||||
#if defined(__DragonFly__) || defined(__FreeBSD__) \
|
||||
|| defined(__NetBSD__) || defined(__OpenBSD__)
|
||||
#include <sys/sysctl.h>
|
||||
|
|
@ -404,6 +410,27 @@ nsresult RTCLoadInfo::UpdateSystemLoad()
|
|||
const uint64_t cpu_times = nice + system + user;
|
||||
const uint64_t total_times = cpu_times + idle;
|
||||
|
||||
UpdateCpuLoad(mTicksPerInterval,
|
||||
total_times,
|
||||
cpu_times,
|
||||
&mSystemLoad);
|
||||
return NS_OK;
|
||||
#elif defined(XP_MACOSX)
|
||||
mach_msg_type_number_t info_cnt = HOST_CPU_LOAD_INFO_COUNT;
|
||||
host_cpu_load_info_data_t load_info;
|
||||
kern_return_t rv = host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO,
|
||||
(host_info_t)(&load_info), &info_cnt);
|
||||
|
||||
if (rv != KERN_SUCCESS || info_cnt != HOST_CPU_LOAD_INFO_COUNT) {
|
||||
LOG(("Error from mach/host_statistics call"));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
const uint64_t cpu_times = load_info.cpu_ticks[CPU_STATE_NICE]
|
||||
+ load_info.cpu_ticks[CPU_STATE_SYSTEM]
|
||||
+ load_info.cpu_ticks[CPU_STATE_USER];
|
||||
const uint64_t total_times = cpu_times + load_info.cpu_ticks[CPU_STATE_IDLE];
|
||||
|
||||
UpdateCpuLoad(mTicksPerInterval,
|
||||
total_times,
|
||||
cpu_times,
|
||||
|
|
|
|||
44
dom/media/systemservices/OSXRunLoopSingleton.cpp
Normal file
44
dom/media/systemservices/OSXRunLoopSingleton.cpp
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
|
||||
/* 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 "OSXRunLoopSingleton.h"
|
||||
#include <mozilla/StaticMutex.h>
|
||||
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include <CoreAudio/AudioHardware.h>
|
||||
#include <CoreAudio/HostTime.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
static bool gRunLoopSet = false;
|
||||
static mozilla::StaticMutex gMutex;
|
||||
|
||||
void mozilla_set_coreaudio_notification_runloop_if_needed()
|
||||
{
|
||||
mozilla::StaticMutexAutoLock lock(gMutex);
|
||||
if (gRunLoopSet) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* This is needed so that AudioUnit listeners get called on this thread, and
|
||||
* not the main thread. If we don't do that, they are not called, or a crash
|
||||
* occur, depending on the OSX version. */
|
||||
AudioObjectPropertyAddress runloop_address = {
|
||||
kAudioHardwarePropertyRunLoop,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMaster
|
||||
};
|
||||
|
||||
CFRunLoopRef run_loop = nullptr;
|
||||
|
||||
OSStatus r;
|
||||
r = AudioObjectSetPropertyData(kAudioObjectSystemObject,
|
||||
&runloop_address,
|
||||
0, NULL, sizeof(CFRunLoopRef), &run_loop);
|
||||
if (r != noErr) {
|
||||
NS_WARNING("Could not make global CoreAudio notifications use their own thread.");
|
||||
}
|
||||
|
||||
gRunLoopSet = true;
|
||||
}
|
||||
25
dom/media/systemservices/OSXRunLoopSingleton.h
Normal file
25
dom/media/systemservices/OSXRunLoopSingleton.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
|
||||
/* 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/. */
|
||||
|
||||
#ifndef OSXRUNLOOPSINGLETON_H_
|
||||
#define OSXRUNLOOPSINGLETON_H_
|
||||
|
||||
#include <mozilla/Types.h>
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* This function tells CoreAudio to use its own thread for device change
|
||||
* notifications, and can be called from any thread without external
|
||||
* synchronization. */
|
||||
void MOZ_EXPORT
|
||||
mozilla_set_coreaudio_notification_runloop_if_needed();
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // OSXRUNLOOPSINGLETON_H_
|
||||
|
|
@ -45,6 +45,14 @@ if CONFIG['OS_TARGET'] == 'Android':
|
|||
'OpenSLESProvider.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
|
||||
EXPORTS += [
|
||||
'OSXRunLoopSingleton.h'
|
||||
]
|
||||
SOURCES += [
|
||||
'OSXRunLoopSingleton.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['_MSC_VER']:
|
||||
DEFINES['__PRETTY_FUNCTION__'] = '__FUNCSIG__'
|
||||
|
||||
|
|
|
|||
|
|
@ -873,6 +873,7 @@ AudioContext::OnStateChanged(void* aPromise, AudioContextState aNewState)
|
|||
}
|
||||
|
||||
#ifndef WIN32 // Bug 1170547
|
||||
#ifndef XP_MACOSX
|
||||
#ifdef DEBUG
|
||||
|
||||
if (!((mAudioContextState == AudioContextState::Suspended &&
|
||||
|
|
@ -891,6 +892,7 @@ AudioContext::OnStateChanged(void* aPromise, AudioContextState aNewState)
|
|||
}
|
||||
|
||||
#endif // DEBUG
|
||||
#endif // XP_MACOSX
|
||||
#endif // WIN32
|
||||
|
||||
MOZ_ASSERT(
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include "MediaDecoderStateMachine.h"
|
||||
#include "WebMDemuxer.h"
|
||||
#include "WebMDecoder.h"
|
||||
#include "PDMFactory.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "nsContentTypeParser.h"
|
||||
|
||||
|
|
@ -66,11 +67,30 @@ WebMDecoder::CanHandleMediaType(const nsACString& aMIMETypeExcludingCodecs,
|
|||
}
|
||||
// Note: Only accept VP8/VP9 in a video content type, not in an audio
|
||||
// content type.
|
||||
if ((isWebMVideo || isMatroskaVideo) &&
|
||||
(codec.EqualsLiteral("vp8") || codec.EqualsLiteral("vp8.0") ||
|
||||
codec.EqualsLiteral("vp9") || codec.EqualsLiteral("vp9.0"))) {
|
||||
|
||||
continue;
|
||||
if (isWebMVideo || isMatroskaVideo) {
|
||||
UniquePtr<TrackInfo> trackInfo;
|
||||
if (IsVP9CodecString(codec)) {
|
||||
trackInfo = CreateTrackInfoWithMIMEType(
|
||||
NS_LITERAL_CSTRING("video/vp9"));
|
||||
} else if (IsVP8CodecString(codec)) {
|
||||
trackInfo = CreateTrackInfoWithMIMEType(
|
||||
NS_LITERAL_CSTRING("video/vp8"));
|
||||
}
|
||||
// If it is vp8 or vp9, check the bit depth.
|
||||
if (trackInfo) {
|
||||
uint8_t profile = 0;
|
||||
uint8_t level = 0;
|
||||
uint8_t bitDepth = 0;
|
||||
if (ExtractVPXCodecDetails(codec, profile, level, bitDepth)) {
|
||||
trackInfo->GetAsVideoInfo()->mBitDepth = bitDepth;
|
||||
}
|
||||
// Verify that we have a PDM that supports this bit depth.
|
||||
RefPtr<PDMFactory> platform = new PDMFactory();
|
||||
if (!platform->Supports(*trackInfo, nullptr)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#ifdef MOZ_AV1
|
||||
if (MediaPrefs::AV1Enabled() && IsAV1CodecString(codec)) {
|
||||
|
|
|
|||
|
|
@ -339,6 +339,13 @@ MediaEngineCameraVideoSource::SetName(nsString aName)
|
|||
facingMode = VideoFacingModeEnum::User;
|
||||
}
|
||||
#endif // ANDROID
|
||||
#ifdef XP_MACOSX
|
||||
// Kludge to test user-facing cameras on OSX.
|
||||
if (aName.Find(NS_LITERAL_STRING("Face")) != -1) {
|
||||
hasFacingMode = true;
|
||||
facingMode = VideoFacingModeEnum::User;
|
||||
}
|
||||
#endif
|
||||
#ifdef XP_WIN
|
||||
// The cameras' name of Surface book are "Microsoft Camera Front" and
|
||||
// "Microsoft Camera Rear" respectively.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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 "mozilla/ModuleUtils.h"
|
||||
#include "nsIClassInfoImpl.h"
|
||||
|
||||
#include "OSXSpeechSynthesizerService.h"
|
||||
|
||||
using namespace mozilla::dom;
|
||||
|
||||
#define OSXSPEECHSYNTHESIZERSERVICE_CID \
|
||||
{0x914e73b4, 0x6337, 0x4bef, {0x97, 0xf3, 0x4d, 0x06, 0x9e, 0x05, 0x3a, 0x12}}
|
||||
|
||||
#define OSXSPEECHSYNTHESIZERSERVICE_CONTRACTID "@mozilla.org/synthsystem;1"
|
||||
|
||||
// Defines OSXSpeechSynthesizerServiceConstructor
|
||||
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(OSXSpeechSynthesizerService,
|
||||
OSXSpeechSynthesizerService::GetInstanceForService)
|
||||
|
||||
// Defines kOSXSERVICE_CID
|
||||
NS_DEFINE_NAMED_CID(OSXSPEECHSYNTHESIZERSERVICE_CID);
|
||||
|
||||
static const mozilla::Module::CIDEntry kCIDs[] = {
|
||||
{ &kOSXSPEECHSYNTHESIZERSERVICE_CID, true, nullptr, OSXSpeechSynthesizerServiceConstructor },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module::ContractIDEntry kContracts[] = {
|
||||
{ OSXSPEECHSYNTHESIZERSERVICE_CONTRACTID, &kOSXSPEECHSYNTHESIZERSERVICE_CID },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module::CategoryEntry kCategories[] = {
|
||||
{ "speech-synth-started", "OSX Speech Synth", OSXSPEECHSYNTHESIZERSERVICE_CONTRACTID },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static void
|
||||
UnloadOSXSpeechSynthesizerModule()
|
||||
{
|
||||
OSXSpeechSynthesizerService::Shutdown();
|
||||
}
|
||||
|
||||
static const mozilla::Module kModule = {
|
||||
mozilla::Module::kVersion,
|
||||
kCIDs,
|
||||
kContracts,
|
||||
kCategories,
|
||||
nullptr,
|
||||
nullptr,
|
||||
UnloadOSXSpeechSynthesizerModule
|
||||
};
|
||||
|
||||
NSMODULE_DEFN(osxsynth) = &kModule;
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_dom_OsxSpeechSynthesizerService_h
|
||||
#define mozilla_dom_OsxSpeechSynthesizerService_h
|
||||
|
||||
#include "nsISpeechService.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class OSXSpeechSynthesizerService final : public nsISpeechService
|
||||
, public nsIObserver
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSISPEECHSERVICE
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
bool Init();
|
||||
|
||||
static OSXSpeechSynthesizerService* GetInstance();
|
||||
static already_AddRefed<OSXSpeechSynthesizerService> GetInstanceForService();
|
||||
static void Shutdown();
|
||||
|
||||
private:
|
||||
OSXSpeechSynthesizerService();
|
||||
virtual ~OSXSpeechSynthesizerService();
|
||||
|
||||
bool RegisterVoices();
|
||||
|
||||
bool mInitialized;
|
||||
static mozilla::StaticRefPtr<OSXSpeechSynthesizerService> sSingleton;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
498
dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
Normal file
498
dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
Normal file
|
|
@ -0,0 +1,498 @@
|
|||
/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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 "nsISupports.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsObjCExceptions.h"
|
||||
#include "nsCocoaUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "mozilla/dom/nsSynthVoiceRegistry.h"
|
||||
#include "mozilla/dom/nsSpeechTask.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "OSXSpeechSynthesizerService.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
// We can escape the default delimiters ("[[" and "]]") by temporarily
|
||||
// changing the delimiters just before they appear, and changing them back
|
||||
// just after.
|
||||
#define DLIM_ESCAPE_START "[[dlim (( ))]]"
|
||||
#define DLIM_ESCAPE_END "((dlim [[ ]]))"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
class SpeechTaskCallback final : public nsISpeechTaskCallback
|
||||
{
|
||||
public:
|
||||
SpeechTaskCallback(nsISpeechTask* aTask,
|
||||
NSSpeechSynthesizer* aSynth,
|
||||
const nsTArray<size_t>& aOffsets)
|
||||
: mTask(aTask)
|
||||
, mSpeechSynthesizer(aSynth)
|
||||
, mOffsets(aOffsets)
|
||||
{
|
||||
mStartingTime = TimeStamp::Now();
|
||||
}
|
||||
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(SpeechTaskCallback, nsISpeechTaskCallback)
|
||||
|
||||
NS_DECL_NSISPEECHTASKCALLBACK
|
||||
|
||||
void OnWillSpeakWord(uint32_t aIndex);
|
||||
void OnError(uint32_t aIndex);
|
||||
void OnDidFinishSpeaking();
|
||||
|
||||
private:
|
||||
virtual ~SpeechTaskCallback()
|
||||
{
|
||||
[mSpeechSynthesizer release];
|
||||
}
|
||||
|
||||
float GetTimeDurationFromStart();
|
||||
|
||||
nsCOMPtr<nsISpeechTask> mTask;
|
||||
NSSpeechSynthesizer* mSpeechSynthesizer;
|
||||
TimeStamp mStartingTime;
|
||||
uint32_t mCurrentIndex;
|
||||
nsTArray<size_t> mOffsets;
|
||||
};
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION(SpeechTaskCallback, mTask);
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SpeechTaskCallback)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISpeechTaskCallback)
|
||||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTaskCallback)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(SpeechTaskCallback)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(SpeechTaskCallback)
|
||||
|
||||
NS_IMETHODIMP
|
||||
SpeechTaskCallback::OnCancel()
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
||||
|
||||
[mSpeechSynthesizer stopSpeaking];
|
||||
return NS_OK;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
SpeechTaskCallback::OnPause()
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
||||
|
||||
[mSpeechSynthesizer pauseSpeakingAtBoundary:NSSpeechImmediateBoundary];
|
||||
if (!mTask) {
|
||||
// When calling pause() on child porcess, it may not receive end event
|
||||
// from chrome process yet.
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mTask->DispatchPause(GetTimeDurationFromStart(), mCurrentIndex);
|
||||
return NS_OK;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
SpeechTaskCallback::OnResume()
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
||||
|
||||
[mSpeechSynthesizer continueSpeaking];
|
||||
if (!mTask) {
|
||||
// When calling resume() on child porcess, it may not receive end event
|
||||
// from chrome process yet.
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mTask->DispatchResume(GetTimeDurationFromStart(), mCurrentIndex);
|
||||
return NS_OK;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
SpeechTaskCallback::OnVolumeChanged(float aVolume)
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
||||
|
||||
[mSpeechSynthesizer setObject:[NSNumber numberWithFloat:aVolume]
|
||||
forProperty:NSSpeechVolumeProperty error:nil];
|
||||
return NS_OK;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
||||
}
|
||||
|
||||
float
|
||||
SpeechTaskCallback::GetTimeDurationFromStart()
|
||||
{
|
||||
TimeDuration duration = TimeStamp::Now() - mStartingTime;
|
||||
return duration.ToMilliseconds();
|
||||
}
|
||||
|
||||
void
|
||||
SpeechTaskCallback::OnWillSpeakWord(uint32_t aIndex)
|
||||
{
|
||||
mCurrentIndex = aIndex < mOffsets.Length() ? mOffsets[aIndex] : mCurrentIndex;
|
||||
if (!mTask) {
|
||||
return;
|
||||
}
|
||||
mTask->DispatchBoundary(NS_LITERAL_STRING("word"),
|
||||
GetTimeDurationFromStart(), mCurrentIndex);
|
||||
}
|
||||
|
||||
void
|
||||
SpeechTaskCallback::OnError(uint32_t aIndex)
|
||||
{
|
||||
if (!mTask) {
|
||||
return;
|
||||
}
|
||||
mTask->DispatchError(GetTimeDurationFromStart(), aIndex);
|
||||
}
|
||||
|
||||
void
|
||||
SpeechTaskCallback::OnDidFinishSpeaking()
|
||||
{
|
||||
mTask->DispatchEnd(GetTimeDurationFromStart(), mCurrentIndex);
|
||||
// no longer needed
|
||||
[mSpeechSynthesizer setDelegate:nil];
|
||||
mTask = nullptr;
|
||||
}
|
||||
|
||||
@interface SpeechDelegate : NSObject<NSSpeechSynthesizerDelegate>
|
||||
{
|
||||
@private
|
||||
SpeechTaskCallback* mCallback;
|
||||
}
|
||||
|
||||
- (id)initWithCallback:(SpeechTaskCallback*)aCallback;
|
||||
@end
|
||||
|
||||
@implementation SpeechDelegate
|
||||
- (id)initWithCallback:(SpeechTaskCallback*)aCallback
|
||||
{
|
||||
[super init];
|
||||
mCallback = aCallback;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)speechSynthesizer:(NSSpeechSynthesizer *)aSender
|
||||
willSpeakWord:(NSRange)aRange ofString:(NSString*)aString
|
||||
{
|
||||
mCallback->OnWillSpeakWord(aRange.location);
|
||||
}
|
||||
|
||||
- (void)speechSynthesizer:(NSSpeechSynthesizer *)aSender
|
||||
didFinishSpeaking:(BOOL)aFinishedSpeaking
|
||||
{
|
||||
mCallback->OnDidFinishSpeaking();
|
||||
}
|
||||
|
||||
- (void)speechSynthesizer:(NSSpeechSynthesizer*)aSender
|
||||
didEncounterErrorAtIndex:(NSUInteger)aCharacterIndex
|
||||
ofString:(NSString*)aString
|
||||
message:(NSString*)aMessage
|
||||
{
|
||||
mCallback->OnError(aCharacterIndex);
|
||||
}
|
||||
@end
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
struct OSXVoice
|
||||
{
|
||||
OSXVoice() : mIsDefault(false)
|
||||
{
|
||||
}
|
||||
|
||||
nsString mUri;
|
||||
nsString mName;
|
||||
nsString mLocale;
|
||||
bool mIsDefault;
|
||||
};
|
||||
|
||||
class RegisterVoicesRunnable final : public Runnable
|
||||
{
|
||||
public:
|
||||
RegisterVoicesRunnable(OSXSpeechSynthesizerService* aSpeechService,
|
||||
nsTArray<OSXVoice>& aList)
|
||||
: mSpeechService(aSpeechService)
|
||||
, mVoices(aList)
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHOD Run() override;
|
||||
|
||||
private:
|
||||
~RegisterVoicesRunnable()
|
||||
{
|
||||
}
|
||||
|
||||
// This runnable always use sync mode. It is unnecesarry to reference object
|
||||
OSXSpeechSynthesizerService* mSpeechService;
|
||||
nsTArray<OSXVoice>& mVoices;
|
||||
};
|
||||
|
||||
NS_IMETHODIMP
|
||||
RegisterVoicesRunnable::Run()
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsISynthVoiceRegistry> registry =
|
||||
do_GetService(NS_SYNTHVOICEREGISTRY_CONTRACTID, &rv);
|
||||
if (!registry) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
for (OSXVoice voice : mVoices) {
|
||||
rv = registry->AddVoice(mSpeechService, voice.mUri, voice.mName, voice.mLocale, true, false);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (voice.mIsDefault) {
|
||||
registry->SetDefaultVoice(voice.mUri, true);
|
||||
}
|
||||
}
|
||||
|
||||
registry->NotifyVoicesChanged();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
class EnumVoicesRunnable final : public Runnable
|
||||
{
|
||||
public:
|
||||
explicit EnumVoicesRunnable(OSXSpeechSynthesizerService* aSpeechService)
|
||||
: mSpeechService(aSpeechService)
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHOD Run() override;
|
||||
|
||||
private:
|
||||
~EnumVoicesRunnable()
|
||||
{
|
||||
}
|
||||
|
||||
RefPtr<OSXSpeechSynthesizerService> mSpeechService;
|
||||
};
|
||||
|
||||
NS_IMETHODIMP
|
||||
EnumVoicesRunnable::Run()
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
||||
|
||||
AutoTArray<OSXVoice, 64> list;
|
||||
|
||||
NSArray* voices = [NSSpeechSynthesizer availableVoices];
|
||||
NSString* defaultVoice = [NSSpeechSynthesizer defaultVoice];
|
||||
|
||||
for (NSString* voice in voices) {
|
||||
OSXVoice item;
|
||||
|
||||
NSDictionary* attr = [NSSpeechSynthesizer attributesForVoice:voice];
|
||||
|
||||
nsAutoString identifier;
|
||||
nsCocoaUtils::GetStringForNSString([attr objectForKey:NSVoiceIdentifier],
|
||||
identifier);
|
||||
|
||||
nsCocoaUtils::GetStringForNSString([attr objectForKey:NSVoiceName], item.mName);
|
||||
|
||||
nsCocoaUtils::GetStringForNSString(
|
||||
[attr objectForKey:NSVoiceLocaleIdentifier], item.mLocale);
|
||||
item.mLocale.ReplaceChar('_', '-');
|
||||
|
||||
item.mUri.AssignLiteral("urn:moz-tts:osx:");
|
||||
item.mUri.Append(identifier);
|
||||
|
||||
if ([voice isEqualToString:defaultVoice]) {
|
||||
item.mIsDefault = true;
|
||||
}
|
||||
|
||||
list.AppendElement(item);
|
||||
}
|
||||
|
||||
RefPtr<RegisterVoicesRunnable> runnable = new RegisterVoicesRunnable(mSpeechService, list);
|
||||
NS_DispatchToMainThread(runnable, NS_DISPATCH_SYNC);
|
||||
|
||||
return NS_OK;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
||||
}
|
||||
|
||||
StaticRefPtr<OSXSpeechSynthesizerService> OSXSpeechSynthesizerService::sSingleton;
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN(OSXSpeechSynthesizerService)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISpeechService)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIObserver)
|
||||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechService)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
NS_IMPL_ADDREF(OSXSpeechSynthesizerService)
|
||||
NS_IMPL_RELEASE(OSXSpeechSynthesizerService)
|
||||
|
||||
OSXSpeechSynthesizerService::OSXSpeechSynthesizerService()
|
||||
: mInitialized(false)
|
||||
{
|
||||
}
|
||||
|
||||
OSXSpeechSynthesizerService::~OSXSpeechSynthesizerService()
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
OSXSpeechSynthesizerService::Init()
|
||||
{
|
||||
if (Preferences::GetBool("media.webspeech.synth.test") ||
|
||||
!Preferences::GetBool("media.webspeech.synth.enabled")) {
|
||||
// When test is enabled, we shouldn't add OS backend (Bug 1160844)
|
||||
return false;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIThread> thread;
|
||||
if (NS_FAILED(NS_NewNamedThread("SpeechWorker", getter_AddRefs(thread)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get all the voices and register in the SynthVoiceRegistry
|
||||
nsCOMPtr<nsIRunnable> runnable = new EnumVoicesRunnable(this);
|
||||
thread->Dispatch(runnable, NS_DISPATCH_NORMAL);
|
||||
|
||||
mInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
OSXSpeechSynthesizerService::Speak(const nsAString& aText,
|
||||
const nsAString& aUri,
|
||||
float aVolume,
|
||||
float aRate,
|
||||
float aPitch,
|
||||
nsISpeechTask* aTask)
|
||||
{
|
||||
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
|
||||
|
||||
MOZ_ASSERT(StringBeginsWith(aUri, NS_LITERAL_STRING("urn:moz-tts:osx:")),
|
||||
"OSXSpeechSynthesizerService doesn't allow this voice URI");
|
||||
|
||||
NSSpeechSynthesizer* synth = [[NSSpeechSynthesizer alloc] init];
|
||||
// strlen("urn:moz-tts:osx:") == 16
|
||||
NSString* identifier = nsCocoaUtils::ToNSString(Substring(aUri, 16));
|
||||
[synth setVoice:identifier];
|
||||
|
||||
// default rate is 180-220
|
||||
[synth setObject:[NSNumber numberWithInt:aRate * 200]
|
||||
forProperty:NSSpeechRateProperty error:nil];
|
||||
// volume allows 0.0-1.0
|
||||
[synth setObject:[NSNumber numberWithFloat:aVolume]
|
||||
forProperty:NSSpeechVolumeProperty error:nil];
|
||||
// Use default pitch value to calculate this
|
||||
NSNumber* defaultPitch =
|
||||
[synth objectForProperty:NSSpeechPitchBaseProperty error:nil];
|
||||
if (defaultPitch) {
|
||||
int newPitch = [defaultPitch intValue] * (aPitch / 2 + 0.5);
|
||||
[synth setObject:[NSNumber numberWithInt:newPitch]
|
||||
forProperty:NSSpeechPitchBaseProperty error:nil];
|
||||
}
|
||||
|
||||
nsAutoString escapedText;
|
||||
// We need to map the the offsets from the given text to the escaped text.
|
||||
// The index of the offsets array is the position in the escaped text,
|
||||
// the element value is the position in the user-supplied text.
|
||||
nsTArray<size_t> offsets;
|
||||
offsets.SetCapacity(aText.Length());
|
||||
|
||||
// This loop looks for occurances of "[[" or "]]", escapes them, and
|
||||
// populates the offsets array to supply a map to the original offsets.
|
||||
for (size_t i = 0; i < aText.Length(); i++) {
|
||||
if (aText.Length() > i + 1 &&
|
||||
((aText[i] == ']' && aText[i+1] == ']') ||
|
||||
(aText[i] == '[' && aText[i+1] == '['))) {
|
||||
escapedText.AppendLiteral(DLIM_ESCAPE_START);
|
||||
offsets.AppendElements(strlen(DLIM_ESCAPE_START));
|
||||
escapedText.Append(aText[i]);
|
||||
offsets.AppendElement(i);
|
||||
escapedText.Append(aText[++i]);
|
||||
offsets.AppendElement(i);
|
||||
escapedText.AppendLiteral(DLIM_ESCAPE_END);
|
||||
offsets.AppendElements(strlen(DLIM_ESCAPE_END));
|
||||
} else {
|
||||
escapedText.Append(aText[i]);
|
||||
offsets.AppendElement(i);
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<SpeechTaskCallback> callback = new SpeechTaskCallback(aTask, synth, offsets);
|
||||
nsresult rv = aTask->Setup(callback, 0, 0, 0);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
SpeechDelegate* delegate = [[SpeechDelegate alloc] initWithCallback:callback];
|
||||
[synth setDelegate:delegate];
|
||||
[delegate release ];
|
||||
|
||||
NSString* text = nsCocoaUtils::ToNSString(escapedText);
|
||||
BOOL success = [synth startSpeakingString:text];
|
||||
NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
|
||||
|
||||
aTask->DispatchStart();
|
||||
return NS_OK;
|
||||
|
||||
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
OSXSpeechSynthesizerService::GetServiceType(SpeechServiceType* aServiceType)
|
||||
{
|
||||
*aServiceType = nsISpeechService::SERVICETYPE_INDIRECT_AUDIO;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
OSXSpeechSynthesizerService::Observe(nsISupports* aSubject, const char* aTopic,
|
||||
const char16_t* aData)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
OSXSpeechSynthesizerService*
|
||||
OSXSpeechSynthesizerService::GetInstance()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (XRE_GetProcessType() != GeckoProcessType_Default) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!sSingleton) {
|
||||
RefPtr<OSXSpeechSynthesizerService> speechService =
|
||||
new OSXSpeechSynthesizerService();
|
||||
if (speechService->Init()) {
|
||||
sSingleton = speechService;
|
||||
}
|
||||
}
|
||||
return sSingleton;
|
||||
}
|
||||
|
||||
already_AddRefed<OSXSpeechSynthesizerService>
|
||||
OSXSpeechSynthesizerService::GetInstanceForService()
|
||||
{
|
||||
RefPtr<OSXSpeechSynthesizerService> speechService = GetInstance();
|
||||
return speechService.forget();
|
||||
}
|
||||
|
||||
void
|
||||
OSXSpeechSynthesizerService::Shutdown()
|
||||
{
|
||||
if (!sSingleton) {
|
||||
return;
|
||||
}
|
||||
sSingleton = nullptr;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
11
dom/media/webspeech/synth/cocoa/moz.build
Normal file
11
dom/media/webspeech/synth/cocoa/moz.build
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# 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/.
|
||||
|
||||
SOURCES += [
|
||||
'OSXSpeechSynthesizerModule.cpp',
|
||||
'OSXSpeechSynthesizerService.mm'
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
|
@ -32,6 +32,9 @@ SOURCES += [
|
|||
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
|
||||
DIRS += ['windows']
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
|
||||
DIRS += ['cocoa']
|
||||
|
||||
if CONFIG['MOZ_SYNTH_SPEECHD']:
|
||||
DIRS += ['speechd']
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue