Merge remote-tracking branch 'origin/tracking' into custom

This commit is contained in:
roytam1 2022-05-04 10:14:24 +08:00
commit 7f068ecce3
969 changed files with 96279 additions and 469 deletions

View file

@ -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

View file

@ -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();

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -14,6 +14,9 @@
#ifdef XP_WIN
#include "mozilla/WindowsVersion.h"
#endif
#ifdef XP_MACOSX
#include "nsCocoaFeatures.h"
#endif
#include "nsPrintfCString.h"
namespace mozilla {

View file

@ -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(

View file

@ -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;

View file

@ -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)
{

View 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);
}

View file

@ -5,6 +5,7 @@
UNIFIED_SOURCES += [
'TestContainerParser.cpp',
'TestExtractVPXCodecDetails.cpp',
]
LOCAL_INCLUDES += [

View file

@ -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

View file

@ -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;
};

View file

@ -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

View file

@ -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;
}

View file

@ -13,6 +13,9 @@ SOURCES += [
'../VideoSegment.cpp',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
UNIFIED_SOURCES += ['../systemservices/OSXRunLoopSingleton.cpp']
LOCAL_INCLUDES += [
'/caps',
'/dom/base',

View file

@ -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,

View 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;
}

View 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_

View file

@ -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__'

View file

@ -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(

View file

@ -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)) {

View file

@ -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.

View file

@ -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;

View file

@ -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

View 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

View 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'

View file

@ -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']