mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-15 14:25:52 +00:00
Merge m-c to inbound.
This commit is contained in:
commit
5f5bfa8616
@ -107,6 +107,10 @@
|
||||
#include "gfxWindowsPlatform.h"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
#include "mozilla/layers/ShadowLayers.h"
|
||||
#endif
|
||||
|
||||
// windows.h (included by chromium code) defines this, in its infinite wisdom
|
||||
#undef DrawText
|
||||
|
||||
@ -783,6 +787,13 @@ CanvasRenderingContext2D::EnsureTarget()
|
||||
SurfaceCaps caps = SurfaceCaps::ForRGBA();
|
||||
caps.preserve = true;
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
layers::ShadowLayerForwarder *forwarder = layerManager->AsShadowForwarder();
|
||||
if (forwarder) {
|
||||
caps.surfaceAllocator = static_cast<layers::ISurfaceAllocator*>(forwarder);
|
||||
}
|
||||
#endif
|
||||
|
||||
mGLContext = mozilla::gl::GLContextProvider::CreateOffscreen(gfxIntSize(size.width,
|
||||
size.height),
|
||||
caps,
|
||||
|
@ -55,7 +55,7 @@ InterleaveAndConvertBuffer(const int16_t** aSourceChannels,
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
void
|
||||
InterleaveAndConvertBuffer(const void** aSourceChannels,
|
||||
AudioSampleFormat aSourceFormat,
|
||||
int32_t aLength, float aVolume,
|
||||
@ -91,14 +91,54 @@ AudioSegment::ApplyVolume(float aVolume)
|
||||
static const int AUDIO_PROCESSING_FRAMES = 640; /* > 10ms of 48KHz audio */
|
||||
static const uint8_t gZeroChannel[MAX_AUDIO_SAMPLE_SIZE*AUDIO_PROCESSING_FRAMES] = {0};
|
||||
|
||||
void
|
||||
DownmixAndInterleave(const nsTArray<const void*>& aChannelData,
|
||||
AudioSampleFormat aSourceFormat, int32_t aDuration,
|
||||
float aVolume, int32_t aOutputChannels,
|
||||
AudioDataValue* aOutput)
|
||||
{
|
||||
nsAutoTArray<const void*,GUESS_AUDIO_CHANNELS> channelData;
|
||||
nsAutoTArray<float,AUDIO_PROCESSING_FRAMES*GUESS_AUDIO_CHANNELS> downmixConversionBuffer;
|
||||
nsAutoTArray<float,AUDIO_PROCESSING_FRAMES*GUESS_AUDIO_CHANNELS> downmixOutputBuffer;
|
||||
|
||||
if (aSourceFormat != AUDIO_FORMAT_FLOAT32) {
|
||||
NS_ASSERTION(aSourceFormat == AUDIO_FORMAT_S16, "unknown format");
|
||||
downmixConversionBuffer.SetLength(aDuration*aChannelData.Length());
|
||||
for (uint32_t i = 0; i < aChannelData.Length(); ++i) {
|
||||
float* conversionBuf = downmixConversionBuffer.Elements() + (i*aDuration);
|
||||
const int16_t* sourceBuf = static_cast<const int16_t*>(aChannelData[i]);
|
||||
for (uint32_t j = 0; j < (uint32_t)aDuration; ++j) {
|
||||
conversionBuf[j] = AudioSampleToFloat(sourceBuf[j]);
|
||||
}
|
||||
channelData[i] = conversionBuf;
|
||||
}
|
||||
} else {
|
||||
for (uint32_t i = 0; i < aChannelData.Length(); ++i) {
|
||||
channelData[i] = aChannelData[i];
|
||||
}
|
||||
}
|
||||
|
||||
downmixOutputBuffer.SetLength(aDuration*aOutputChannels);
|
||||
nsAutoTArray<float*,GUESS_AUDIO_CHANNELS> outputChannelBuffers;
|
||||
nsAutoTArray<const void*,GUESS_AUDIO_CHANNELS> outputChannelData;
|
||||
outputChannelBuffers.SetLength(aOutputChannels);
|
||||
outputChannelData.SetLength(aOutputChannels);
|
||||
for (uint32_t i = 0; i < (uint32_t)aOutputChannels; ++i) {
|
||||
outputChannelData[i] = outputChannelBuffers[i] =
|
||||
downmixOutputBuffer.Elements() + aDuration*i;
|
||||
}
|
||||
AudioChannelsDownMix(channelData, outputChannelBuffers.Elements(),
|
||||
aOutputChannels, aDuration);
|
||||
InterleaveAndConvertBuffer(outputChannelData.Elements(), AUDIO_FORMAT_FLOAT32,
|
||||
aDuration, aVolume, aOutputChannels, aOutput);
|
||||
}
|
||||
|
||||
void
|
||||
AudioSegment::WriteTo(AudioStream* aOutput)
|
||||
{
|
||||
uint32_t outputChannels = aOutput->GetChannels();
|
||||
nsAutoTArray<AudioDataValue,AUDIO_PROCESSING_FRAMES*GUESS_AUDIO_CHANNELS> buf;
|
||||
nsAutoTArray<const void*,GUESS_AUDIO_CHANNELS> channelData;
|
||||
nsAutoTArray<float,AUDIO_PROCESSING_FRAMES*GUESS_AUDIO_CHANNELS> downmixConversionBuffer;
|
||||
nsAutoTArray<float,AUDIO_PROCESSING_FRAMES*GUESS_AUDIO_CHANNELS> downmixOutputBuffer;
|
||||
|
||||
for (ChunkIterator ci(*this); !ci.IsEnded(); ci.Next()) {
|
||||
AudioChunk& c = *ci;
|
||||
@ -127,34 +167,8 @@ AudioSegment::WriteTo(AudioStream* aOutput)
|
||||
|
||||
if (channelData.Length() > outputChannels) {
|
||||
// Down-mix.
|
||||
if (c.mBufferFormat != AUDIO_FORMAT_FLOAT32) {
|
||||
NS_ASSERTION(c.mBufferFormat == AUDIO_FORMAT_S16, "unknown format");
|
||||
downmixConversionBuffer.SetLength(duration*channelData.Length());
|
||||
for (uint32_t i = 0; i < channelData.Length(); ++i) {
|
||||
float* conversionBuf = downmixConversionBuffer.Elements() + (i*duration);
|
||||
const int16_t* sourceBuf = static_cast<const int16_t*>(channelData[i]);
|
||||
for (uint32_t j = 0; j < duration; ++j) {
|
||||
conversionBuf[j] = AudioSampleToFloat(sourceBuf[j]);
|
||||
}
|
||||
channelData[i] = conversionBuf;
|
||||
}
|
||||
}
|
||||
|
||||
downmixOutputBuffer.SetLength(duration*outputChannels);
|
||||
nsAutoTArray<float*,GUESS_AUDIO_CHANNELS> outputChannelBuffers;
|
||||
nsAutoTArray<const void*,GUESS_AUDIO_CHANNELS> outputChannelData;
|
||||
outputChannelBuffers.SetLength(outputChannels);
|
||||
outputChannelData.SetLength(outputChannels);
|
||||
for (uint32_t i = 0; i < outputChannels; ++i) {
|
||||
outputChannelData[i] = outputChannelBuffers[i] =
|
||||
downmixOutputBuffer.Elements() + duration*i;
|
||||
}
|
||||
AudioChannelsDownMix(channelData, outputChannelBuffers.Elements(),
|
||||
outputChannels, duration);
|
||||
InterleaveAndConvertBuffer(outputChannelData.Elements(), AUDIO_FORMAT_FLOAT32,
|
||||
duration, c.mVolume,
|
||||
outputChannels,
|
||||
buf.Elements());
|
||||
DownmixAndInterleave(channelData, c.mBufferFormat, duration,
|
||||
c.mVolume, channelData.Length(), buf.Elements());
|
||||
} else {
|
||||
InterleaveAndConvertBuffer(channelData.Elements(), c.mBufferFormat,
|
||||
duration, c.mVolume,
|
||||
|
@ -25,6 +25,20 @@ const int GUESS_AUDIO_CHANNELS = 2;
|
||||
const uint32_t WEBAUDIO_BLOCK_SIZE_BITS = 7;
|
||||
const uint32_t WEBAUDIO_BLOCK_SIZE = 1 << WEBAUDIO_BLOCK_SIZE_BITS;
|
||||
|
||||
void InterleaveAndConvertBuffer(const void** aSourceChannels,
|
||||
AudioSampleFormat aSourceFormat,
|
||||
int32_t aLength, float aVolume,
|
||||
int32_t aChannels,
|
||||
AudioDataValue* aOutput);
|
||||
/**
|
||||
* Down-mix audio channels, and interleave the channel data. A total of
|
||||
* aOutputChannels*aDuration interleaved samples will be stored into aOutput.
|
||||
*/
|
||||
void DownmixAndInterleave(const nsTArray<const void*>& aChannelData,
|
||||
AudioSampleFormat aSourceFormat, int32_t aDuration,
|
||||
float aVolume, int32_t aOutputChannels,
|
||||
AudioDataValue* aOutput);
|
||||
|
||||
/**
|
||||
* An AudioChunk represents a multi-channel buffer of audio samples.
|
||||
* It references an underlying ThreadSharedObject which manages the lifetime
|
||||
|
@ -209,6 +209,10 @@ public:
|
||||
uint32_t mIndex;
|
||||
};
|
||||
|
||||
void RemoveLeading(TrackTicks aDuration)
|
||||
{
|
||||
RemoveLeading(aDuration, 0);
|
||||
}
|
||||
protected:
|
||||
MediaSegmentBase(Type aType) : MediaSegment(aType) {}
|
||||
|
||||
|
56
content/media/encoder/ContainerWriter.h
Normal file
56
content/media/encoder/ContainerWriter.h
Normal file
@ -0,0 +1,56 @@
|
||||
/* -*- 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 ContainerWriter_h_
|
||||
#define ContainerWriter_h_
|
||||
|
||||
#include "nsTArray.h"
|
||||
#include "nsAutoPtr.h"
|
||||
|
||||
namespace mozilla {
|
||||
/**
|
||||
* ContainerWriter packs encoded track data into a specific media container.
|
||||
*/
|
||||
class ContainerWriter {
|
||||
public:
|
||||
ContainerWriter()
|
||||
: mInitialized(false)
|
||||
{}
|
||||
virtual ~ContainerWriter() {}
|
||||
|
||||
enum {
|
||||
END_OF_STREAM = 1 << 0
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes encoded track data from aBuffer to a packet, and insert this packet
|
||||
* into the internal stream of container writer. aDuration is the playback
|
||||
* duration of this packet in number of samples. aFlags is true with
|
||||
* END_OF_STREAM if this is the last packet of track.
|
||||
* Currently, WriteEncodedTrack doesn't support multiple tracks.
|
||||
*/
|
||||
virtual nsresult WriteEncodedTrack(const nsTArray<uint8_t>& aBuffer,
|
||||
int aDuration, uint32_t aFlags = 0) = 0;
|
||||
|
||||
enum {
|
||||
FLUSH_NEEDED = 1 << 0
|
||||
};
|
||||
|
||||
/**
|
||||
* Copies the final container data to a buffer if it has accumulated enough
|
||||
* packets from WriteEncodedTrack. This buffer of data is appended to
|
||||
* aOutputBufs, and existing elements of aOutputBufs should not be modified.
|
||||
* aFlags is true with FLUSH_NEEDED will force OggWriter to flush an ogg page
|
||||
* even it is not full, and copy these container data to a buffer for
|
||||
* aOutputBufs to append.
|
||||
*/
|
||||
virtual nsresult GetContainerData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
|
||||
uint32_t aFlags = 0) = 0;
|
||||
|
||||
protected:
|
||||
bool mInitialized;
|
||||
};
|
||||
}
|
||||
#endif
|
19
content/media/encoder/Makefile.in
Normal file
19
content/media/encoder/Makefile.in
Normal file
@ -0,0 +1,19 @@
|
||||
# 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/.
|
||||
|
||||
DEPTH = @DEPTH@
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
LIBRARY_NAME = gkconencoder_s
|
||||
LIBXUL_LIBRARY = 1
|
||||
FAIL_ON_WARNINGS := 1
|
||||
|
||||
|
||||
FORCE_STATIC_LIB = 1
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
242
content/media/encoder/MediaEncoder.cpp
Normal file
242
content/media/encoder/MediaEncoder.cpp
Normal file
@ -0,0 +1,242 @@
|
||||
/* -*- 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 "MediaEncoder.h"
|
||||
#include "MediaDecoder.h"
|
||||
#ifdef MOZ_OGG
|
||||
#include "OggWriter.h"
|
||||
#endif
|
||||
#ifdef MOZ_OPUS
|
||||
#include "OpusTrackEncoder.h"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
#include <android/log.h>
|
||||
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "MediaEncoder", ## args);
|
||||
#else
|
||||
#define LOG(args,...)
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#define TRACK_BUFFER_LEN 8192
|
||||
|
||||
namespace {
|
||||
|
||||
template <class String>
|
||||
static bool
|
||||
TypeListContains(char const *const * aTypes, const String& aType)
|
||||
{
|
||||
for (int32_t i = 0; aTypes[i]; ++i) {
|
||||
if (aType.EqualsASCII(aTypes[i]))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef MOZ_OGG
|
||||
// The recommended mime-type for Ogg Opus files is audio/ogg.
|
||||
// See http://wiki.xiph.org/OggOpus for more details.
|
||||
static const char* const gOggTypes[2] = {
|
||||
"audio/ogg",
|
||||
nullptr
|
||||
};
|
||||
|
||||
static bool
|
||||
IsOggType(const nsAString& aType)
|
||||
{
|
||||
if (!MediaDecoder::IsOggEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return TypeListContains(gOggTypes, aType);
|
||||
}
|
||||
#endif
|
||||
} //anonymous namespace
|
||||
|
||||
void
|
||||
MediaEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
|
||||
TrackID aID,
|
||||
TrackRate aTrackRate,
|
||||
TrackTicks aTrackOffset,
|
||||
uint32_t aTrackEvents,
|
||||
const MediaSegment& aQueuedMedia)
|
||||
{
|
||||
// Process the incoming raw track data from MediaStreamGraph, called on the
|
||||
// thread of MediaStreamGraph.
|
||||
if (aQueuedMedia.GetType() == MediaSegment::AUDIO) {
|
||||
mAudioEncoder->NotifyQueuedTrackChanges(aGraph, aID, aTrackRate,
|
||||
aTrackOffset, aTrackEvents,
|
||||
aQueuedMedia);
|
||||
|
||||
} else {
|
||||
// Type video is not supported for now.
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MediaEncoder::NotifyRemoved(MediaStreamGraph* aGraph)
|
||||
{
|
||||
// In case that MediaEncoder does not receive a TRACK_EVENT_ENDED event.
|
||||
LOG("NotifyRemoved in [MediaEncoder].");
|
||||
mAudioEncoder->NotifyRemoved(aGraph);
|
||||
}
|
||||
|
||||
/* static */
|
||||
already_AddRefed<MediaEncoder>
|
||||
MediaEncoder::CreateEncoder(const nsAString& aMIMEType)
|
||||
{
|
||||
nsAutoPtr<ContainerWriter> writer;
|
||||
nsAutoPtr<AudioTrackEncoder> audioEncoder;
|
||||
nsAutoPtr<VideoTrackEncoder> videoEncoder;
|
||||
nsRefPtr<MediaEncoder> encoder;
|
||||
|
||||
if (aMIMEType.IsEmpty()) {
|
||||
// TODO: Should pick out a default container+codec base on the track
|
||||
// coming from MediaStreamGraph. For now, just default to Ogg+Opus.
|
||||
const_cast<nsAString&>(aMIMEType) = NS_LITERAL_STRING("audio/ogg");
|
||||
}
|
||||
|
||||
bool isAudioOnly = FindInReadable(NS_LITERAL_STRING("audio/"), aMIMEType);
|
||||
#ifdef MOZ_OGG
|
||||
if (IsOggType(aMIMEType)) {
|
||||
writer = new OggWriter();
|
||||
if (!isAudioOnly) {
|
||||
// Initialize the videoEncoder.
|
||||
}
|
||||
#ifdef MOZ_OPUS
|
||||
audioEncoder = new OpusTrackEncoder();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
// If the given mime-type is video but fail to create the video encoder.
|
||||
if (!isAudioOnly) {
|
||||
NS_ENSURE_TRUE(videoEncoder, nullptr);
|
||||
}
|
||||
|
||||
// Return null if we fail to create the audio encoder.
|
||||
NS_ENSURE_TRUE(audioEncoder, nullptr);
|
||||
|
||||
encoder = new MediaEncoder(writer.forget(), audioEncoder.forget(),
|
||||
videoEncoder.forget(), aMIMEType);
|
||||
|
||||
|
||||
return encoder.forget();
|
||||
}
|
||||
|
||||
/**
|
||||
* GetEncodedData() runs as a state machine, starting with mState set to
|
||||
* ENCODE_HEADER, the procedure should be as follow:
|
||||
*
|
||||
* While non-stop
|
||||
* If mState is ENCODE_HEADER
|
||||
* Create the header from audio/video encoder
|
||||
* If a header is generated
|
||||
* Insert header data into the container stream of writer
|
||||
* Force copied the final container data from writer
|
||||
* Return the copy of final container data
|
||||
* Else
|
||||
* Set mState to ENCODE_TRACK
|
||||
*
|
||||
* If mState is ENCODE_TRACK
|
||||
* Get encoded track data from audio/video encoder
|
||||
* If a packet of track data is generated
|
||||
* Insert encoded track data into the container stream of writer
|
||||
* If the final container data is copied to aOutput
|
||||
* Return the copy of final container data
|
||||
* If this is the last packet of input stream
|
||||
* Set mState to ENCODE_DONE
|
||||
*
|
||||
* If mState is ENCODE_DONE
|
||||
* Stop the loop
|
||||
*/
|
||||
void
|
||||
MediaEncoder::GetEncodedData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
|
||||
nsAString& aMIMEType)
|
||||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
aMIMEType = mMIMEType;
|
||||
|
||||
bool reloop = true;
|
||||
while (reloop) {
|
||||
switch (mState) {
|
||||
case ENCODE_HEADER: {
|
||||
nsTArray<uint8_t> buffer;
|
||||
nsresult rv = mAudioEncoder->GetHeader(&buffer);
|
||||
if (NS_FAILED(rv)) {
|
||||
// Encoding might be canceled.
|
||||
mState = ENCODE_DONE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!buffer.IsEmpty()) {
|
||||
rv = mWriter->WriteEncodedTrack(buffer, 0);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("ERROR! Fail to write header to the media container.");
|
||||
mState = ENCODE_DONE;
|
||||
break;
|
||||
}
|
||||
|
||||
rv = mWriter->GetContainerData(aOutputBufs,
|
||||
ContainerWriter::FLUSH_NEEDED);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
// Successfully get the copy of final container data from writer.
|
||||
reloop = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// No more headers, starts to encode tracks.
|
||||
mState = ENCODE_TRACK;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ENCODE_TRACK: {
|
||||
nsTArray<uint8_t> buffer;
|
||||
int encodedDuration = 0;
|
||||
nsresult rv = mAudioEncoder->GetEncodedTrack(&buffer, encodedDuration);
|
||||
if (NS_FAILED(rv)) {
|
||||
// Encoding might be canceled.
|
||||
LOG("ERROR! Fail to get encoded data from encoder.");
|
||||
mState = ENCODE_DONE;
|
||||
break;
|
||||
}
|
||||
|
||||
rv = mWriter->WriteEncodedTrack(buffer, encodedDuration,
|
||||
mAudioEncoder->IsEncodingComplete() ?
|
||||
ContainerWriter::END_OF_STREAM : 0);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("ERROR! Fail to write encoded track to the media container.");
|
||||
mState = ENCODE_DONE;
|
||||
break;
|
||||
}
|
||||
|
||||
rv = mWriter->GetContainerData(aOutputBufs,
|
||||
mAudioEncoder->IsEncodingComplete() ?
|
||||
ContainerWriter::FLUSH_NEEDED : 0);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
// Successfully get the copy of final container data from writer.
|
||||
reloop = false;
|
||||
break;
|
||||
}
|
||||
|
||||
mState = (mAudioEncoder->IsEncodingComplete()) ? ENCODE_DONE : ENCODE_TRACK;
|
||||
break;
|
||||
}
|
||||
|
||||
case ENCODE_DONE:
|
||||
LOG("MediaEncoder has been shutdown.");
|
||||
mShutdown = true;
|
||||
reloop = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_NOT_REACHED("Invalid encode state");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
134
content/media/encoder/MediaEncoder.h
Normal file
134
content/media/encoder/MediaEncoder.h
Normal file
@ -0,0 +1,134 @@
|
||||
/* -*- 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 MediaEncoder_h_
|
||||
#define MediaEncoder_h_
|
||||
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "TrackEncoder.h"
|
||||
#include "ContainerWriter.h"
|
||||
#include "MediaStreamGraph.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* MediaEncoder is the framework of encoding module, it controls and manages
|
||||
* procedures between ContainerWriter and TrackEncoder. ContainerWriter packs
|
||||
* the encoded track data with a specific container (e.g. ogg, mp4).
|
||||
* AudioTrackEncoder and VideoTrackEncoder are subclasses of TrackEncoder, and
|
||||
* are responsible for encoding raw data coming from MediaStreamGraph.
|
||||
*
|
||||
* Also, MediaEncoder is a type of MediaStreamListener, it starts to receive raw
|
||||
* segments after itself is added to the source stream. In the mean time,
|
||||
* encoded track data is pulled by its owner periodically on a worker thread. A
|
||||
* reentrant monitor is used to protect the push and pull of resource.
|
||||
*
|
||||
* MediaEncoder is designed to be a passive component, neither it owns nor in
|
||||
* charge of managing threads. However, a monitor is used in function
|
||||
* TrackEncoder::GetEncodedTrack() for the purpose of thread safety (e.g.
|
||||
* between callbacks of MediaStreamListener and others), a call to this function
|
||||
* might block. Therefore, MediaEncoder should not run on threads that forbid
|
||||
* blocking, such as main thread or I/O thread.
|
||||
*
|
||||
* For example, an usage from MediaRecorder of this component would be:
|
||||
* 1) Create an encoder with a valid MIME type.
|
||||
* => encoder = MediaEncoder::CreateEncoder(aMIMEType);
|
||||
* It then generate a ContainerWriter according to the MIME type, and an
|
||||
* AudioTrackEncoder (or a VideoTrackEncoder too) associated with the media
|
||||
* type.
|
||||
*
|
||||
* 2) Dispatch the task GetEncodedData() to a worker thread.
|
||||
*
|
||||
* 3) To start encoding, add this component to its source stream.
|
||||
* => sourceStream->AddListener(encoder);
|
||||
*
|
||||
* 4) To stop encoding, remove this component from its source stream.
|
||||
* => sourceStream->RemoveListener(encoder);
|
||||
*/
|
||||
class MediaEncoder : public MediaStreamListener
|
||||
{
|
||||
public :
|
||||
enum {
|
||||
ENCODE_HEADER,
|
||||
ENCODE_TRACK,
|
||||
ENCODE_DONE,
|
||||
};
|
||||
|
||||
MediaEncoder(ContainerWriter* aWriter,
|
||||
AudioTrackEncoder* aAudioEncoder,
|
||||
VideoTrackEncoder* aVideoEncoder,
|
||||
const nsAString& aMIMEType)
|
||||
: mWriter(aWriter)
|
||||
, mAudioEncoder(aAudioEncoder)
|
||||
, mVideoEncoder(aVideoEncoder)
|
||||
, mMIMEType(aMIMEType)
|
||||
, mState(MediaEncoder::ENCODE_HEADER)
|
||||
, mShutdown(false)
|
||||
{}
|
||||
|
||||
~MediaEncoder() {};
|
||||
|
||||
/**
|
||||
* Notified by the control loop of MediaStreamGraph; aQueueMedia is the raw
|
||||
* track data in form of MediaSegment.
|
||||
*/
|
||||
virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
|
||||
TrackRate aTrackRate,
|
||||
TrackTicks aTrackOffset,
|
||||
uint32_t aTrackEvents,
|
||||
const MediaSegment& aQueuedMedia);
|
||||
|
||||
/**
|
||||
* Notified the stream is being removed.
|
||||
*/
|
||||
virtual void NotifyRemoved(MediaStreamGraph* aGraph);
|
||||
|
||||
/**
|
||||
* Creates an encoder with a given MIME type. Returns null if we are unable
|
||||
* to create the encoder. For now, default aMIMEType to "audio/ogg" and use
|
||||
* Ogg+Opus if it is empty.
|
||||
*/
|
||||
static already_AddRefed<MediaEncoder> CreateEncoder(const nsAString& aMIMEType);
|
||||
|
||||
/**
|
||||
* Encodes the raw track data and returns the final container data. Assuming
|
||||
* it is called on a single worker thread. The buffer of container data is
|
||||
* allocated in ContainerWriter::GetContainerData(), and is appended to
|
||||
* aOutputBufs. aMIMEType is the valid mime-type of this returned container
|
||||
* data.
|
||||
*/
|
||||
void GetEncodedData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
|
||||
nsAString& aMIMEType);
|
||||
|
||||
/**
|
||||
* Return true if MediaEncoder has been shutdown. Reasons are encoding
|
||||
* complete, encounter an error, or being canceled by its caller.
|
||||
*/
|
||||
bool IsShutdown()
|
||||
{
|
||||
return mShutdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the encoding, and wakes up the lock of reentrant monitor in encoder.
|
||||
*/
|
||||
void Cancel()
|
||||
{
|
||||
if (mAudioEncoder) {
|
||||
mAudioEncoder->NotifyCancel();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
nsAutoPtr<ContainerWriter> mWriter;
|
||||
nsAutoPtr<AudioTrackEncoder> mAudioEncoder;
|
||||
nsAutoPtr<VideoTrackEncoder> mVideoEncoder;
|
||||
nsString mMIMEType;
|
||||
int mState;
|
||||
bool mShutdown;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
332
content/media/encoder/OpusTrackEncoder.cpp
Normal file
332
content/media/encoder/OpusTrackEncoder.cpp
Normal file
@ -0,0 +1,332 @@
|
||||
/* -*- 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 "OpusTrackEncoder.h"
|
||||
#include "nsString.h"
|
||||
|
||||
#include <opus/opus.h>
|
||||
|
||||
#undef LOG
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
#include <android/log.h>
|
||||
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "MediaEncoder", ## args);
|
||||
#else
|
||||
#define LOG(args, ...)
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// http://www.opus-codec.org/docs/html_api-1.0.2/group__opus__encoder.html
|
||||
// In section "opus_encoder_init", channels must be 1 or 2 of input signal.
|
||||
static const int MAX_CHANNELS = 2;
|
||||
|
||||
// A maximum data bytes for Opus to encode.
|
||||
static const int MAX_DATA_BYTES = 4096;
|
||||
|
||||
// http://tools.ietf.org/html/draft-ietf-codec-oggopus-00#section-4
|
||||
// Second paragraph, " The granule position of an audio data page is in units
|
||||
// of PCM audio samples at a fixed rate of 48 kHz."
|
||||
static const int kOpusSamplingRate = 48000;
|
||||
|
||||
// The duration of an Opus frame, and it must be 2.5, 5, 10, 20, 40 or 60 ms.
|
||||
static const int kFrameDurationMs = 20;
|
||||
|
||||
namespace {
|
||||
|
||||
// An endian-neutral serialization of integers. Serializing T in little endian
|
||||
// format to aOutput, where T is a 16 bits or 32 bits integer.
|
||||
template<typename T>
|
||||
static void
|
||||
SerializeToBuffer(T aValue, nsTArray<uint8_t>* aOutput)
|
||||
{
|
||||
for (uint32_t i = 0; i < sizeof(T); i++) {
|
||||
aOutput->AppendElement((uint8_t)(0x000000ff & (aValue >> (i * 8))));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
SerializeToBuffer(const nsCString& aComment, nsTArray<uint8_t>* aOutput)
|
||||
{
|
||||
// Format of serializing a string to buffer is, the length of string (32 bits,
|
||||
// little endian), and the string.
|
||||
SerializeToBuffer((uint32_t)(aComment.Length()), aOutput);
|
||||
aOutput->AppendElements(aComment.get(), aComment.Length());
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
SerializeOpusIdHeader(uint8_t aChannelCount, uint16_t aPreskip,
|
||||
uint32_t aInputSampleRate, nsTArray<uint8_t>* aOutput)
|
||||
{
|
||||
// The magic signature, null terminator has to be stripped off from strings.
|
||||
static const uint8_t magic[9] = "OpusHead";
|
||||
memcpy(aOutput->AppendElements(sizeof(magic) - 1), magic, sizeof(magic) - 1);
|
||||
|
||||
// The version, must always be 1 (8 bits, unsigned).
|
||||
aOutput->AppendElement(1);
|
||||
|
||||
// Number of output channels (8 bits, unsigned).
|
||||
aOutput->AppendElement(aChannelCount);
|
||||
|
||||
// Number of samples (at 48 kHz) to discard from the decoder output when
|
||||
// starting playback (16 bits, unsigned, little endian).
|
||||
SerializeToBuffer(aPreskip, aOutput);
|
||||
|
||||
// The sampling rate of input source (32 bits, unsigned, little endian).
|
||||
SerializeToBuffer(aInputSampleRate, aOutput);
|
||||
|
||||
// Output gain, an encoder should set this field to zero (16 bits, signed,
|
||||
// little endian).
|
||||
SerializeToBuffer((int16_t)0, aOutput);
|
||||
|
||||
// Channel mapping family. Family 0 allows only 1 or 2 channels (8 bits,
|
||||
// unsigned).
|
||||
aOutput->AppendElement(0);
|
||||
}
|
||||
|
||||
static void
|
||||
SerializeOpusCommentHeader(const nsCString& aVendor,
|
||||
const nsTArray<nsCString>& aComments,
|
||||
nsTArray<uint8_t>* aOutput)
|
||||
{
|
||||
// The magic signature, null terminator has to be stripped off.
|
||||
static const uint8_t magic[9] = "OpusTags";
|
||||
memcpy(aOutput->AppendElements(sizeof(magic) - 1), magic, sizeof(magic) - 1);
|
||||
|
||||
// The vendor; Should append in the following order:
|
||||
// vendor string length (32 bits, unsigned, little endian)
|
||||
// vendor string.
|
||||
SerializeToBuffer(aVendor, aOutput);
|
||||
|
||||
// Add comments; Should append in the following order:
|
||||
// comment list length (32 bits, unsigned, little endian)
|
||||
// comment #0 string length (32 bits, unsigned, little endian)
|
||||
// comment #0 string
|
||||
// comment #1 string length (32 bits, unsigned, little endian)
|
||||
// comment #1 string ...
|
||||
SerializeToBuffer((uint32_t)aComments.Length(), aOutput);
|
||||
for (uint32_t i = 0; i < aComments.Length(); ++i) {
|
||||
SerializeToBuffer(aComments[i], aOutput);
|
||||
}
|
||||
}
|
||||
|
||||
} // Anonymous namespace.
|
||||
|
||||
OpusTrackEncoder::OpusTrackEncoder()
|
||||
: AudioTrackEncoder()
|
||||
, mEncoderState(ID_HEADER)
|
||||
, mEncoder(nullptr)
|
||||
, mSourceSegment(new AudioSegment())
|
||||
, mLookahead(0)
|
||||
{
|
||||
}
|
||||
|
||||
OpusTrackEncoder::~OpusTrackEncoder()
|
||||
{
|
||||
if (mEncoder) {
|
||||
opus_encoder_destroy(mEncoder);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
OpusTrackEncoder::Init(int aChannels, int aSamplingRate)
|
||||
{
|
||||
// The track must have 1 or 2 channels.
|
||||
if (aChannels <= 0 || aChannels > MAX_CHANNELS) {
|
||||
LOG("[Opus] Fail to create the AudioTrackEncoder! The input has"
|
||||
" %d channel(s), but expects no more than %d.", aChannels, MAX_CHANNELS);
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// This monitor is used to wake up other methods that are waiting for encoder
|
||||
// to be completely initialized.
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
mChannels = aChannels;
|
||||
|
||||
// The granule position is required to be incremented at a rate of 48KHz, and
|
||||
// it is simply calculated as |granulepos = samples * (48000/source_rate)|,
|
||||
// that is, the source sampling rate must divide 48000 evenly.
|
||||
if (!((aSamplingRate >= 8000) && (kOpusSamplingRate / aSamplingRate) *
|
||||
aSamplingRate == kOpusSamplingRate)) {
|
||||
LOG("[Opus] Error! The source sample rate should be greater than 8000 and"
|
||||
" divides 48000 evenly.");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mSamplingRate = aSamplingRate;
|
||||
|
||||
int error = 0;
|
||||
mEncoder = opus_encoder_create(mSamplingRate, mChannels,
|
||||
OPUS_APPLICATION_AUDIO, &error);
|
||||
|
||||
mInitialized = (error == OPUS_OK);
|
||||
|
||||
mReentrantMonitor.NotifyAll();
|
||||
|
||||
return error == OPUS_OK ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
int
|
||||
OpusTrackEncoder::GetPacketDuration()
|
||||
{
|
||||
return mSamplingRate * kFrameDurationMs / 1000;
|
||||
}
|
||||
|
||||
nsresult
|
||||
OpusTrackEncoder::GetHeader(nsTArray<uint8_t>* aOutput)
|
||||
{
|
||||
{
|
||||
// Wait if mEncoder is not initialized.
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
while (!mCanceled && !mEncoder) {
|
||||
mReentrantMonitor.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
if (mCanceled) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
switch (mEncoderState) {
|
||||
case ID_HEADER:
|
||||
{
|
||||
mLookahead = 0;
|
||||
int error = opus_encoder_ctl(mEncoder, OPUS_GET_LOOKAHEAD(&mLookahead));
|
||||
if (error != OPUS_OK) {
|
||||
mLookahead = 0;
|
||||
}
|
||||
|
||||
// The ogg time stamping and pre-skip is always timed at 48000.
|
||||
SerializeOpusIdHeader(mChannels, mLookahead*(kOpusSamplingRate/mSamplingRate),
|
||||
mSamplingRate, aOutput);
|
||||
|
||||
mEncoderState = COMMENT_HEADER;
|
||||
break;
|
||||
}
|
||||
case COMMENT_HEADER:
|
||||
{
|
||||
nsCString vendor;
|
||||
vendor.AppendASCII(opus_get_version_string());
|
||||
|
||||
nsTArray<nsCString> comments;
|
||||
comments.AppendElement(NS_LITERAL_CSTRING("ENCODER=Mozilla" MOZ_APP_UA_VERSION));
|
||||
|
||||
SerializeOpusCommentHeader(vendor, comments, aOutput);
|
||||
|
||||
mEncoderState = DATA;
|
||||
break;
|
||||
}
|
||||
case DATA:
|
||||
// No more headers.
|
||||
break;
|
||||
default:
|
||||
MOZ_NOT_REACHED("Invalid state");
|
||||
break;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
OpusTrackEncoder::GetEncodedTrack(nsTArray<uint8_t>* aOutput,
|
||||
int &aOutputDuration)
|
||||
{
|
||||
{
|
||||
// Move all the samples from mRawSegment to mSourceSegment. We only hold
|
||||
// the monitor in this block.
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
|
||||
// Wait if mEncoder is not initialized, or when not enough raw data, but is
|
||||
// not the end of stream nor is being canceled.
|
||||
while (!mCanceled && (!mEncoder || (mRawSegment->GetDuration() +
|
||||
mSourceSegment->GetDuration() < GetPacketDuration() &&
|
||||
!mEndOfStream))) {
|
||||
mReentrantMonitor.Wait();
|
||||
}
|
||||
|
||||
if (mCanceled) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mSourceSegment->AppendFrom(mRawSegment);
|
||||
|
||||
// Pad |mLookahead| samples to the end of source stream to prevent lost of
|
||||
// original data, the pcm duration will be calculated at rate 48K later.
|
||||
if (mEndOfStream) {
|
||||
mSourceSegment->AppendNullData(mLookahead);
|
||||
}
|
||||
}
|
||||
|
||||
// Start encoding data.
|
||||
nsAutoTArray<AudioDataValue, 9600> pcm;
|
||||
pcm.SetLength(GetPacketDuration() * mChannels);
|
||||
AudioSegment::ChunkIterator iter(*mSourceSegment);
|
||||
int frameCopied = 0;
|
||||
while (!iter.IsEnded() && frameCopied < GetPacketDuration()) {
|
||||
AudioChunk chunk = *iter;
|
||||
|
||||
// Chunk to the required frame size.
|
||||
int frameToCopy = chunk.GetDuration();
|
||||
if (frameCopied + frameToCopy > GetPacketDuration()) {
|
||||
frameToCopy = GetPacketDuration() - frameCopied;
|
||||
}
|
||||
|
||||
if (!chunk.IsNull()) {
|
||||
// Append the interleaved data to the end of pcm buffer.
|
||||
InterleaveTrackData(chunk, frameToCopy, mChannels,
|
||||
pcm.Elements() + frameCopied);
|
||||
} else {
|
||||
for (int i = 0; i < frameToCopy * mChannels; i++) {
|
||||
pcm.AppendElement(0);
|
||||
}
|
||||
}
|
||||
|
||||
frameCopied += frameToCopy;
|
||||
iter.Next();
|
||||
}
|
||||
|
||||
// The ogg time stamping and pre-skip is always timed at 48000.
|
||||
aOutputDuration = frameCopied * (kOpusSamplingRate / mSamplingRate);
|
||||
|
||||
// Remove the raw data which has been pulled to pcm buffer.
|
||||
// The value of frameCopied should equal to (or smaller than, if eos)
|
||||
// GetPacketDuration().
|
||||
mSourceSegment->RemoveLeading(frameCopied);
|
||||
|
||||
// Has reached the end of input stream and all queued data has pulled for
|
||||
// encoding.
|
||||
if (mSourceSegment->GetDuration() == 0 && mEndOfStream) {
|
||||
mDoneEncoding = true;
|
||||
LOG("[Opus] Done encoding.");
|
||||
}
|
||||
|
||||
// Append null data to pcm buffer if the leftover data is not enough for
|
||||
// opus encoder.
|
||||
if (frameCopied < GetPacketDuration() && mEndOfStream) {
|
||||
for (int i = frameCopied * mChannels; i < GetPacketDuration() * mChannels; i++) {
|
||||
pcm.AppendElement(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Encode the data with Opus Encoder.
|
||||
aOutput->SetLength(MAX_DATA_BYTES);
|
||||
// result is returned as opus error code if it is negative.
|
||||
int result = 0;
|
||||
#ifdef MOZ_SAMPLE_TYPE_S16
|
||||
const opus_int16* pcmBuf = static_cast<opus_int16*>(pcm.Elements());
|
||||
result = opus_encode(mEncoder, pcmBuf, GetPacketDuration(),
|
||||
aOutput->Elements(), MAX_DATA_BYTES);
|
||||
#else
|
||||
const float* pcmBuf = static_cast<float*>(pcm.Elements());
|
||||
result = opus_encode_float(mEncoder, pcmBuf, GetPacketDuration(),
|
||||
aOutput->Elements(), MAX_DATA_BYTES);
|
||||
#endif
|
||||
aOutput->SetLength(result >= 0 ? result : 0);
|
||||
|
||||
if (result < 0) {
|
||||
LOG("[Opus] Fail to encode data! Result: %s.", opus_strerror(result));
|
||||
}
|
||||
|
||||
return result >= 0 ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
}
|
60
content/media/encoder/OpusTrackEncoder.h
Normal file
60
content/media/encoder/OpusTrackEncoder.h
Normal file
@ -0,0 +1,60 @@
|
||||
/* -*- 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 OpusTrackEncoder_h_
|
||||
#define OpusTrackEncoder_h_
|
||||
|
||||
#include "TrackEncoder.h"
|
||||
#include "nsCOMPtr.h"
|
||||
|
||||
struct OpusEncoder;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class OpusTrackEncoder : public AudioTrackEncoder
|
||||
{
|
||||
public:
|
||||
OpusTrackEncoder();
|
||||
virtual ~OpusTrackEncoder();
|
||||
|
||||
nsresult GetHeader(nsTArray<uint8_t>* aOutput) MOZ_OVERRIDE;
|
||||
|
||||
nsresult GetEncodedTrack(nsTArray<uint8_t>* aOutput, int &aOutputDuration) MOZ_OVERRIDE;
|
||||
|
||||
protected:
|
||||
int GetPacketDuration() MOZ_OVERRIDE;
|
||||
|
||||
nsresult Init(int aChannels, int aSamplingRate) MOZ_OVERRIDE;
|
||||
|
||||
private:
|
||||
enum {
|
||||
ID_HEADER,
|
||||
COMMENT_HEADER,
|
||||
DATA
|
||||
} mEncoderState;
|
||||
|
||||
/**
|
||||
* The Opus encoder from libopus.
|
||||
*/
|
||||
OpusEncoder* mEncoder;
|
||||
|
||||
/**
|
||||
* A local segment queue which stores the raw segments. Opus encoder only
|
||||
* takes GetPacketDuration() samples from mSourceSegment in every encoding
|
||||
* cycle, thus it needs to store the raw track data.
|
||||
*/
|
||||
nsAutoPtr<AudioSegment> mSourceSegment;
|
||||
|
||||
/**
|
||||
* Total samples of delay added by codec, can be queried by the encoder. From
|
||||
* the perspective of decoding, real data begins this many samples late, so
|
||||
* the encoder needs to append this many null samples to the end of stream,
|
||||
* in order to align the time of input and output.
|
||||
*/
|
||||
int mLookahead;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
117
content/media/encoder/TrackEncoder.cpp
Normal file
117
content/media/encoder/TrackEncoder.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
/* -*- 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 "TrackEncoder.h"
|
||||
#include "MediaStreamGraph.h"
|
||||
#include "AudioChannelFormat.h"
|
||||
|
||||
#undef LOG
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
#include <android/log.h>
|
||||
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "MediakEncoder", ## args);
|
||||
#else
|
||||
#define LOG(args, ...)
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#define MAX_FRAMES_TO_DROP 48000
|
||||
|
||||
void
|
||||
AudioTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
|
||||
TrackID aID,
|
||||
TrackRate aTrackRate,
|
||||
TrackTicks aTrackOffset,
|
||||
uint32_t aTrackEvents,
|
||||
const MediaSegment& aQueuedMedia)
|
||||
{
|
||||
AudioSegment* audio = const_cast<AudioSegment*>
|
||||
(static_cast<const AudioSegment*>(&aQueuedMedia));
|
||||
|
||||
// Check and initialize parameters for codec encoder.
|
||||
if (!mInitialized) {
|
||||
AudioSegment::ChunkIterator iter(*audio);
|
||||
while (!iter.IsEnded()) {
|
||||
AudioChunk chunk = *iter;
|
||||
if (chunk.mBuffer) {
|
||||
Init(chunk.mChannelData.Length(), aTrackRate);
|
||||
break;
|
||||
}
|
||||
iter.Next();
|
||||
}
|
||||
}
|
||||
|
||||
// Append and consume this raw segment.
|
||||
AppendAudioSegment(audio);
|
||||
|
||||
// The stream has stopped and reached the end of track.
|
||||
if (aTrackEvents == MediaStreamListener::TRACK_EVENT_ENDED) {
|
||||
LOG("[AudioTrackEncoder]: Receive TRACK_EVENT_ENDED .");
|
||||
NotifyEndOfStream();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioTrackEncoder::NotifyRemoved(MediaStreamGraph* aGraph)
|
||||
{
|
||||
// In case that MediaEncoder does not receive a TRACK_EVENT_ENDED event.
|
||||
LOG("[AudioTrackEncoder]: NotifyRemoved.");
|
||||
NotifyEndOfStream();
|
||||
}
|
||||
|
||||
nsresult
|
||||
AudioTrackEncoder::AppendAudioSegment(MediaSegment* aSegment)
|
||||
{
|
||||
// Drop the in-coming segment if buffer(mRawSegment) is overflow.
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
|
||||
AudioSegment* audio = static_cast<AudioSegment*>(aSegment);
|
||||
AudioSegment::ChunkIterator iter(*audio);
|
||||
|
||||
if (mRawSegment->GetDuration() < MAX_FRAMES_TO_DROP) {
|
||||
while(!iter.IsEnded()) {
|
||||
AudioChunk chunk = *iter;
|
||||
if (chunk.mBuffer) {
|
||||
mRawSegment->AppendAndConsumeChunk(&chunk);
|
||||
}
|
||||
iter.Next();
|
||||
}
|
||||
if (mRawSegment->GetDuration() >= GetPacketDuration()) {
|
||||
mReentrantMonitor.NotifyAll();
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG
|
||||
else {
|
||||
LOG("[AudioTrackEncoder]: A segment has dropped!");
|
||||
}
|
||||
#endif
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
static const int AUDIO_PROCESSING_FRAMES = 640; /* > 10ms of 48KHz audio */
|
||||
static const uint8_t gZeroChannel[MAX_AUDIO_SAMPLE_SIZE*AUDIO_PROCESSING_FRAMES] = {0};
|
||||
|
||||
void
|
||||
AudioTrackEncoder::InterleaveTrackData(AudioChunk& aChunk,
|
||||
int32_t aDuration,
|
||||
uint32_t aOutputChannels,
|
||||
AudioDataValue* aOutput)
|
||||
{
|
||||
if (aChunk.mChannelData.Length() < aOutputChannels) {
|
||||
// Up-mix. This might make the mChannelData have more than aChannels.
|
||||
AudioChannelsUpMix(&aChunk.mChannelData, aOutputChannels, gZeroChannel);
|
||||
}
|
||||
|
||||
if (aChunk.mChannelData.Length() > aOutputChannels) {
|
||||
DownmixAndInterleave(aChunk.mChannelData, aChunk.mBufferFormat, aDuration,
|
||||
aChunk.mVolume, mChannels, aOutput);
|
||||
} else {
|
||||
InterleaveAndConvertBuffer(aChunk.mChannelData.Elements(),
|
||||
aChunk.mBufferFormat, aDuration, aChunk.mVolume,
|
||||
mChannels, aOutput);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
187
content/media/encoder/TrackEncoder.h
Normal file
187
content/media/encoder/TrackEncoder.h
Normal file
@ -0,0 +1,187 @@
|
||||
/* -*- 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 TrackEncoder_h_
|
||||
#define TrackEncoder_h_
|
||||
|
||||
#include "mozilla/ReentrantMonitor.h"
|
||||
|
||||
#include "AudioSegment.h"
|
||||
#include "StreamBuffer.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class MediaStreamGraph;
|
||||
|
||||
/**
|
||||
* Base class of AudioTrackEncoder and VideoTrackEncoder. Lifetimes managed by
|
||||
* MediaEncoder. Most methods can only be called on the MediaEncoder's thread,
|
||||
* but some subclass methods can be called on other threads when noted.
|
||||
*
|
||||
* NotifyQueuedTrackChanges is called on subclasses of this class from the
|
||||
* MediaStreamGraph thread, and AppendAudioSegment/AppendVideoSegment is then
|
||||
* called to store media data in the TrackEncoder. Later on, GetEncodedTrack is
|
||||
* called on MediaEncoder's thread to encode and retrieve the encoded data.
|
||||
*/
|
||||
class TrackEncoder
|
||||
{
|
||||
public:
|
||||
TrackEncoder() {}
|
||||
virtual ~TrackEncoder() {}
|
||||
|
||||
/**
|
||||
* Notified by the same callbcak of MediaEncoder when it has received a track
|
||||
* change from MediaStreamGraph. Called on the MediaStreamGraph thread.
|
||||
*/
|
||||
virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
|
||||
TrackRate aTrackRate,
|
||||
TrackTicks aTrackOffset,
|
||||
uint32_t aTrackEvents,
|
||||
const MediaSegment& aQueuedMedia) = 0;
|
||||
|
||||
/**
|
||||
* Notified by the same callback of MediaEncoder when it has been removed from
|
||||
* MediaStreamGraph. Called on the MediaStreamGraph thread.
|
||||
*/
|
||||
virtual void NotifyRemoved(MediaStreamGraph* aGraph) = 0;
|
||||
|
||||
/**
|
||||
* Creates and sets up header for a specific codec. Result data is returned
|
||||
* in aOutput.
|
||||
*/
|
||||
virtual nsresult GetHeader(nsTArray<uint8_t>* aOutput) = 0;
|
||||
|
||||
/**
|
||||
* Encodes raw segments. Result data is returned in aOutput. aOutputDuration
|
||||
* is the playback duration of this packet in number of samples.
|
||||
*/
|
||||
virtual nsresult GetEncodedTrack(nsTArray<uint8_t>* aOutput,
|
||||
int &aOutputDuration) = 0;
|
||||
};
|
||||
|
||||
class AudioTrackEncoder : public TrackEncoder
|
||||
{
|
||||
public:
|
||||
AudioTrackEncoder()
|
||||
: TrackEncoder()
|
||||
, mChannels(0)
|
||||
, mSamplingRate(0)
|
||||
, mInitialized(false)
|
||||
, mDoneEncoding(false)
|
||||
, mReentrantMonitor("media.AudioEncoder")
|
||||
, mRawSegment(new AudioSegment())
|
||||
, mEndOfStream(false)
|
||||
, mCanceled(false)
|
||||
{}
|
||||
|
||||
void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
|
||||
TrackRate aTrackRate,
|
||||
TrackTicks aTrackOffset,
|
||||
uint32_t aTrackEvents,
|
||||
const MediaSegment& aQueuedMedia) MOZ_OVERRIDE;
|
||||
|
||||
void NotifyRemoved(MediaStreamGraph* aGraph) MOZ_OVERRIDE;
|
||||
|
||||
bool IsEncodingComplete()
|
||||
{
|
||||
return mDoneEncoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies from MediaEncoder to cancel the encoding, and wakes up
|
||||
* mReentrantMonitor if encoder is waiting on it.
|
||||
*/
|
||||
void NotifyCancel()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
mCanceled = true;
|
||||
mReentrantMonitor.NotifyAll();
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Number of samples per channel in a pcm buffer. This is also the value of
|
||||
* frame size required by audio encoder, and mReentrantMonitor will be
|
||||
* notified when at least this much data has been added to mRawSegment.
|
||||
*/
|
||||
virtual int GetPacketDuration() = 0;
|
||||
|
||||
/**
|
||||
* Initializes the audio encoder. The call of this method is delayed until we
|
||||
* have received the first valid track from MediaStreamGraph, and the
|
||||
* mReentrantMonitor will be notified if other methods is waiting for encoder
|
||||
* to be completely initialized. This method is called on the MediaStreamGraph
|
||||
* thread.
|
||||
*/
|
||||
virtual nsresult Init(int aChannels, int aSamplingRate) = 0;
|
||||
|
||||
/**
|
||||
* Appends and consumes track data from aSegment, this method is called on
|
||||
* the MediaStreamGraph thread. mReentrantMonitor will be notified when at
|
||||
* least GetPacketDuration() data has been added to mRawSegment, wake up other
|
||||
* method which is waiting for more data from mRawSegment.
|
||||
*/
|
||||
nsresult AppendAudioSegment(MediaSegment* aSegment);
|
||||
|
||||
/**
|
||||
* Notifies the audio encoder that we have reached the end of source stream,
|
||||
* and wakes up mReentrantMonitor if encoder is waiting for more track data.
|
||||
*/
|
||||
void NotifyEndOfStream()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
|
||||
mEndOfStream = true;
|
||||
mReentrantMonitor.NotifyAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interleaves the track data and stores the result into aOutput. Might need
|
||||
* to up-mix or down-mix the channel data if the channels number of this chunk
|
||||
* is different from mChannels. The channel data from aChunk might be modified
|
||||
* by up-mixing.
|
||||
*/
|
||||
void InterleaveTrackData(AudioChunk& aChunk, int32_t aDuration,
|
||||
uint32_t aOutputChannels, AudioDataValue* aOutput);
|
||||
|
||||
/**
|
||||
* The number of channels in the first valid audio chunk, and is being used
|
||||
* to initialize the audio encoder.
|
||||
*/
|
||||
int mChannels;
|
||||
int mSamplingRate;
|
||||
bool mInitialized;
|
||||
bool mDoneEncoding;
|
||||
|
||||
/**
|
||||
* A ReentrantMonitor to protect the pushing and pulling of mRawSegment.
|
||||
*/
|
||||
ReentrantMonitor mReentrantMonitor;
|
||||
|
||||
/**
|
||||
* A segment queue of audio track data, protected by mReentrantMonitor.
|
||||
*/
|
||||
nsAutoPtr<AudioSegment> mRawSegment;
|
||||
|
||||
/**
|
||||
* True if we have received an event of TRACK_EVENT_ENDED from MediaStreamGraph,
|
||||
* or the MediaEncoder is removed from its source stream, protected by
|
||||
* mReentrantMonitor.
|
||||
*/
|
||||
bool mEndOfStream;
|
||||
|
||||
/**
|
||||
* True if a cancellation of encoding is sent from MediaEncoder, protected by
|
||||
* mReentrantMonitor.
|
||||
*/
|
||||
bool mCanceled;
|
||||
};
|
||||
|
||||
class VideoTrackEncoder : public TrackEncoder
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
22
content/media/encoder/moz.build
Normal file
22
content/media/encoder/moz.build
Normal file
@ -0,0 +1,22 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
MODULE = 'content'
|
||||
|
||||
EXPORTS += [
|
||||
'ContainerWriter.h',
|
||||
'MediaEncoder.h',
|
||||
'TrackEncoder.h',
|
||||
]
|
||||
|
||||
CPP_SOURCES += [
|
||||
'MediaEncoder.cpp',
|
||||
'TrackEncoder.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_OPUS']:
|
||||
EXPORTS += ['OpusTrackEncoder.h']
|
||||
CPP_SOURCES += ['OpusTrackEncoder.cpp']
|
@ -4,6 +4,8 @@
|
||||
# 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/.
|
||||
|
||||
PARALLEL_DIRS += ['encoder']
|
||||
|
||||
PARALLEL_DIRS += ['webaudio']
|
||||
|
||||
if CONFIG['MOZ_RAW']:
|
||||
|
113
content/media/ogg/OggWriter.cpp
Normal file
113
content/media/ogg/OggWriter.cpp
Normal file
@ -0,0 +1,113 @@
|
||||
/* -*- 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 "OggWriter.h"
|
||||
|
||||
#undef LOG
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
#include <android/log.h>
|
||||
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "MediaEncoder", ## args);
|
||||
#else
|
||||
#define LOG(args, ...)
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
OggWriter::OggWriter() : ContainerWriter()
|
||||
{
|
||||
if (NS_FAILED(Init())) {
|
||||
LOG("ERROR! Fail to initialize the OggWriter.");
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
OggWriter::Init()
|
||||
{
|
||||
MOZ_ASSERT(!mInitialized);
|
||||
|
||||
// The serial number (serialno) should be a random number, for the current
|
||||
// implementation where the output file contains only a single stream, this
|
||||
// serialno is used to differentiate between files.
|
||||
srand(static_cast<unsigned>(PR_Now()));
|
||||
int rc = ogg_stream_init(&mOggStreamState, rand());
|
||||
|
||||
mPacket.b_o_s = 1;
|
||||
mPacket.e_o_s = 0;
|
||||
mPacket.granulepos = 0;
|
||||
mPacket.packet = nullptr;
|
||||
mPacket.packetno = 0;
|
||||
mPacket.bytes = 0;
|
||||
|
||||
mInitialized = (rc == 0);
|
||||
|
||||
return (rc == 0) ? NS_OK : NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
nsresult
|
||||
OggWriter::WriteEncodedTrack(const nsTArray<uint8_t>& aBuffer, int aDuration,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
MOZ_ASSERT(!ogg_stream_eos(&mOggStreamState),
|
||||
"No data can be written after eos has marked.");
|
||||
|
||||
// Set eos flag to true, and once the eos is written to a packet, there must
|
||||
// not be anymore pages after a page has marked as eos.
|
||||
if (aFlags & ContainerWriter::END_OF_STREAM) {
|
||||
LOG("[OggWriter] Set e_o_s flag to true.");
|
||||
mPacket.e_o_s = 1;
|
||||
}
|
||||
|
||||
mPacket.packet = const_cast<uint8_t*>(aBuffer.Elements());
|
||||
mPacket.bytes = aBuffer.Length();
|
||||
mPacket.granulepos += aDuration;
|
||||
|
||||
// 0 returned on success. -1 returned in the event of internal error.
|
||||
// The data in the packet is copied into the internal storage managed by the
|
||||
// mOggStreamState, so we are free to alter the contents of mPacket after
|
||||
// this call has returned.
|
||||
int rc = ogg_stream_packetin(&mOggStreamState, &mPacket);
|
||||
if (rc < 0) {
|
||||
LOG("[OggWriter] Failed in ogg_stream_packetin! (%d).", rc);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (mPacket.b_o_s) {
|
||||
mPacket.b_o_s = 0;
|
||||
}
|
||||
mPacket.packetno++;
|
||||
mPacket.packet = nullptr;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
OggWriter::GetContainerData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
int rc = -1;
|
||||
// Force generate a page even if the amount of packet data is not enough.
|
||||
// Usually do so after a header packet.
|
||||
if (aFlags & ContainerWriter::FLUSH_NEEDED) {
|
||||
// rc = 0 means no packet to put into a page, or an internal error.
|
||||
rc = ogg_stream_flush(&mOggStreamState, &mOggPage);
|
||||
} else {
|
||||
// rc = 0 means insufficient data has accumulated to fill a page, or an
|
||||
// internal error has occurred.
|
||||
rc = ogg_stream_pageout(&mOggStreamState, &mOggPage);
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
aOutputBufs->AppendElement();
|
||||
aOutputBufs->LastElement().SetLength(mOggPage.header_len +
|
||||
mOggPage.body_len);
|
||||
memcpy(aOutputBufs->LastElement().Elements(), mOggPage.header,
|
||||
mOggPage.header_len);
|
||||
memcpy(aOutputBufs->LastElement().Elements() + mOggPage.header_len,
|
||||
mOggPage.body, mOggPage.body_len);
|
||||
}
|
||||
|
||||
return (rc > 0) ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
}
|
39
content/media/ogg/OggWriter.h
Normal file
39
content/media/ogg/OggWriter.h
Normal file
@ -0,0 +1,39 @@
|
||||
/* -*- 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 OggWriter_h_
|
||||
#define OggWriter_h_
|
||||
|
||||
#include "ContainerWriter.h"
|
||||
#include <ogg/ogg.h>
|
||||
|
||||
namespace mozilla {
|
||||
/**
|
||||
* WriteEncodedTrack inserts raw packets into Ogg stream (ogg_stream_state), and
|
||||
* GetContainerData outputs an ogg_page when enough packets have been written
|
||||
* to the Ogg stream.
|
||||
* For more details, please reference:
|
||||
* http://www.xiph.org/ogg/doc/libogg/encoding.html
|
||||
*/
|
||||
class OggWriter : public ContainerWriter
|
||||
{
|
||||
public:
|
||||
OggWriter();
|
||||
|
||||
nsresult WriteEncodedTrack(const nsTArray<uint8_t>& aBuffer, int aDuration,
|
||||
uint32_t aFlags = 0) MOZ_OVERRIDE;
|
||||
|
||||
nsresult GetContainerData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
|
||||
uint32_t aFlags = 0) MOZ_OVERRIDE;
|
||||
|
||||
private:
|
||||
nsresult Init();
|
||||
|
||||
ogg_stream_state mOggStreamState;
|
||||
ogg_page mOggPage;
|
||||
ogg_packet mPacket;
|
||||
};
|
||||
}
|
||||
#endif
|
@ -10,11 +10,13 @@ EXPORTS += [
|
||||
'OggCodecState.h',
|
||||
'OggDecoder.h',
|
||||
'OggReader.h',
|
||||
'OggWriter.h',
|
||||
]
|
||||
|
||||
CPP_SOURCES += [
|
||||
'OggCodecState.cpp',
|
||||
'OggDecoder.cpp',
|
||||
'OggReader.cpp',
|
||||
'OggWriter.cpp',
|
||||
]
|
||||
|
||||
|
@ -1026,15 +1026,22 @@ BluetoothHfpManager::Connect(const nsAString& aDeviceAddress,
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
NS_ENSURE_FALSE_VOID(gInShutdown);
|
||||
NS_ENSURE_FALSE_VOID(mSocket);
|
||||
BluetoothService* bs = BluetoothService::Get();
|
||||
if (!bs || gInShutdown) {
|
||||
DispatchBluetoothReply(aRunnable, BluetoothValue(),
|
||||
NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSocket) {
|
||||
DispatchBluetoothReply(aRunnable, BluetoothValue(),
|
||||
NS_LITERAL_STRING(ERR_REACHED_CONNECTION_LIMIT));
|
||||
return;
|
||||
}
|
||||
|
||||
mNeedsUpdatingSdpRecords = true;
|
||||
mIsHandsfree = aIsHandsfree;
|
||||
|
||||
BluetoothService* bs = BluetoothService::Get();
|
||||
NS_ENSURE_TRUE_VOID(bs);
|
||||
|
||||
nsString uuid;
|
||||
if (aIsHandsfree) {
|
||||
BluetoothUuidHelper::GetString(BluetoothServiceClass::HANDSFREE, uuid);
|
||||
|
@ -257,10 +257,18 @@ BluetoothOppManager::Connect(const nsAString& aDeviceAddress,
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
NS_ENSURE_FALSE_VOID(mSocket);
|
||||
|
||||
BluetoothService* bs = BluetoothService::Get();
|
||||
NS_ENSURE_TRUE_VOID(bs);
|
||||
if (!bs || sInShutdown) {
|
||||
DispatchBluetoothReply(aRunnable, BluetoothValue(),
|
||||
NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSocket) {
|
||||
DispatchBluetoothReply(aRunnable, BluetoothValue(),
|
||||
NS_LITERAL_STRING(ERR_REACHED_CONNECTION_LIMIT));
|
||||
return;
|
||||
}
|
||||
|
||||
mNeedsUpdatingSdpRecords = true;
|
||||
|
||||
|
@ -8,6 +8,8 @@
|
||||
#define mozilla_dom_bluetooth_bluetoothprofilemanagerbase_h__
|
||||
|
||||
#define ERR_SERVICE_CHANNEL_NOT_FOUND "DeviceChannelRetrievalError"
|
||||
#define ERR_REACHED_CONNECTION_LIMIT "ReachedConnectionLimitError"
|
||||
#define ERR_NO_AVAILABLE_RESOURCE "NoAvailableResourceError"
|
||||
|
||||
#include "BluetoothCommon.h"
|
||||
|
||||
|
@ -1566,8 +1566,13 @@ EventFilter(DBusConnection* aConn, DBusMessage* aMsg, void* aData)
|
||||
}
|
||||
|
||||
BluetoothSignal signal(signalName, signalPath, v);
|
||||
nsRefPtr<DistributeBluetoothSignalTask> task
|
||||
= new DistributeBluetoothSignalTask(signal);
|
||||
nsRefPtr<nsRunnable> task;
|
||||
if (signalInterface.EqualsLiteral(DBUS_SINK_IFACE)) {
|
||||
task = new SinkPropertyChangedHandler(signal);
|
||||
} else {
|
||||
task = new DistributeBluetoothSignalTask(signal);
|
||||
}
|
||||
|
||||
NS_DispatchToMainThread(task);
|
||||
|
||||
return DBUS_HANDLER_RESULT_HANDLED;
|
||||
|
@ -73,17 +73,8 @@ const WIFI_CTRL_INTERFACE = "wl0.1";
|
||||
const NETWORK_INTERFACE_UP = "up";
|
||||
const NETWORK_INTERFACE_DOWN = "down";
|
||||
|
||||
// Settings DB path for Wifi tethering.
|
||||
const SETTINGS_WIFI_ENABLED = "tethering.wifi.enabled";
|
||||
const SETTINGS_WIFI_SSID = "tethering.wifi.ssid";
|
||||
const SETTINGS_WIFI_SECURITY_TYPE = "tethering.wifi.security.type";
|
||||
const SETTINGS_WIFI_SECURITY_PASSWORD = "tethering.wifi.security.password";
|
||||
const SETTINGS_WIFI_IP = "tethering.wifi.ip";
|
||||
const SETTINGS_WIFI_PREFIX = "tethering.wifi.prefix";
|
||||
const SETTINGS_WIFI_DHCPSERVER_STARTIP = "tethering.wifi.dhcpserver.startip";
|
||||
const SETTINGS_WIFI_DHCPSERVER_ENDIP = "tethering.wifi.dhcpserver.endip";
|
||||
const SETTINGS_WIFI_DNS1 = "tethering.wifi.dns1";
|
||||
const SETTINGS_WIFI_DNS2 = "tethering.wifi.dns2";
|
||||
const TETHERING_STATE_ONGOING = "ongoing";
|
||||
const TETHERING_STATE_IDLE = "idle";
|
||||
|
||||
// Settings DB path for USB tethering.
|
||||
const SETTINGS_USB_ENABLED = "tethering.usb.enabled";
|
||||
@ -94,15 +85,6 @@ const SETTINGS_USB_DHCPSERVER_ENDIP = "tethering.usb.dhcpserver.endip";
|
||||
const SETTINGS_USB_DNS1 = "tethering.usb.dns1";
|
||||
const SETTINGS_USB_DNS2 = "tethering.usb.dns2";
|
||||
|
||||
// Default value for WIFI tethering.
|
||||
const DEFAULT_WIFI_IP = "192.168.1.1";
|
||||
const DEFAULT_WIFI_PREFIX = "24";
|
||||
const DEFAULT_WIFI_DHCPSERVER_STARTIP = "192.168.1.10";
|
||||
const DEFAULT_WIFI_DHCPSERVER_ENDIP = "192.168.1.30";
|
||||
const DEFAULT_WIFI_SSID = "FirefoxHotspot";
|
||||
const DEFAULT_WIFI_SECURITY_TYPE = "open";
|
||||
const DEFAULT_WIFI_SECURITY_PASSWORD = "1234567890";
|
||||
|
||||
// Default value for USB tethering.
|
||||
const DEFAULT_USB_IP = "192.168.0.1";
|
||||
const DEFAULT_USB_PREFIX = "24";
|
||||
@ -183,16 +165,6 @@ function NetworkManager() {
|
||||
this.initTetheringSettings();
|
||||
|
||||
let settingsLock = gSettingsService.createLock();
|
||||
// Read wifi tethering data from settings DB.
|
||||
settingsLock.get(SETTINGS_WIFI_SSID, this);
|
||||
settingsLock.get(SETTINGS_WIFI_SECURITY_TYPE, this);
|
||||
settingsLock.get(SETTINGS_WIFI_SECURITY_PASSWORD, this);
|
||||
settingsLock.get(SETTINGS_WIFI_IP, this);
|
||||
settingsLock.get(SETTINGS_WIFI_PREFIX, this);
|
||||
settingsLock.get(SETTINGS_WIFI_DHCPSERVER_STARTIP, this);
|
||||
settingsLock.get(SETTINGS_WIFI_DHCPSERVER_ENDIP, this);
|
||||
settingsLock.get(SETTINGS_WIFI_DNS1, this);
|
||||
settingsLock.get(SETTINGS_WIFI_DNS2, this);
|
||||
// Read usb tethering data from settings DB.
|
||||
settingsLock.get(SETTINGS_USB_IP, this);
|
||||
settingsLock.get(SETTINGS_USB_PREFIX, this);
|
||||
@ -200,28 +172,19 @@ function NetworkManager() {
|
||||
settingsLock.get(SETTINGS_USB_DHCPSERVER_ENDIP, this);
|
||||
settingsLock.get(SETTINGS_USB_DNS1, this);
|
||||
settingsLock.get(SETTINGS_USB_DNS2, this);
|
||||
settingsLock.get(SETTINGS_USB_ENABLED, this);
|
||||
|
||||
this._usbTetheringSettingsToRead = [SETTINGS_USB_IP,
|
||||
SETTINGS_USB_PREFIX,
|
||||
SETTINGS_USB_DHCPSERVER_STARTIP,
|
||||
SETTINGS_USB_DHCPSERVER_ENDIP,
|
||||
SETTINGS_USB_DNS1,
|
||||
SETTINGS_USB_DNS2,
|
||||
SETTINGS_USB_ENABLED];
|
||||
|
||||
this.wantConnectionEvent = null;
|
||||
this.setAndConfigureActive();
|
||||
|
||||
let self = this;
|
||||
this.waitForConnectionReadyCallback = null;
|
||||
settingsLock.get(SETTINGS_WIFI_ENABLED, {
|
||||
handle: function (aName, aResult) {
|
||||
if (!aResult) {
|
||||
return;
|
||||
}
|
||||
// Turn on wifi tethering when the mobile data connection is established.
|
||||
self.waitForConnectionReadyCallback = (function callback() {
|
||||
let settingsLock = gSettingsService.createLock();
|
||||
settingsLock.set(SETTINGS_WIFI_ENABLED, aResult, null);
|
||||
});
|
||||
},
|
||||
|
||||
handleError: function (aErrorMessage) {
|
||||
debug("Error reading the 'tethering.wifi.enabled' setting: " + aErrorMessage);
|
||||
}
|
||||
});
|
||||
|
||||
ppmm.addMessageListener('NetworkInterfaceList:ListInterface', this);
|
||||
|
||||
// Used in resolveHostname().
|
||||
@ -267,11 +230,9 @@ NetworkManager.prototype = {
|
||||
if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
|
||||
this.mRIL.updateRILNetworkInterface();
|
||||
}
|
||||
// Turn on wifi tethering when the callback is set.
|
||||
if (this.waitForConnectionReadyCallback) {
|
||||
this.waitForConnectionReadyCallback.call(this);
|
||||
this.waitForConnectionReadyCallback = null;
|
||||
}
|
||||
|
||||
this.onConnectionChanged(network);
|
||||
|
||||
// Probing the public network accessibility after routing table is ready
|
||||
CaptivePortalDetectionHelper.notify(CaptivePortalDetectionHelper.EVENT_CONNECT, this.active);
|
||||
break;
|
||||
@ -704,17 +665,7 @@ NetworkManager.prototype = {
|
||||
tetheringSettings: {},
|
||||
|
||||
initTetheringSettings: function initTetheringSettings() {
|
||||
this.tetheringSettings[SETTINGS_WIFI_ENABLED] = false;
|
||||
this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
|
||||
this.tetheringSettings[SETTINGS_WIFI_SSID] = DEFAULT_WIFI_SSID;
|
||||
this.tetheringSettings[SETTINGS_WIFI_SECURITY_TYPE] = DEFAULT_WIFI_SECURITY_TYPE;
|
||||
this.tetheringSettings[SETTINGS_WIFI_SECURITY_PASSWORD] = DEFAULT_WIFI_SECURITY_PASSWORD;
|
||||
this.tetheringSettings[SETTINGS_WIFI_IP] = DEFAULT_WIFI_IP;
|
||||
this.tetheringSettings[SETTINGS_WIFI_PREFIX] = DEFAULT_WIFI_PREFIX;
|
||||
this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP] = DEFAULT_WIFI_DHCPSERVER_STARTIP;
|
||||
this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP] = DEFAULT_WIFI_DHCPSERVER_ENDIP;
|
||||
this.tetheringSettings[SETTINGS_WIFI_DNS1] = DEFAULT_DNS1;
|
||||
this.tetheringSettings[SETTINGS_WIFI_DNS2] = DEFAULT_DNS2;
|
||||
this.tetheringSettings[SETTINGS_USB_IP] = DEFAULT_USB_IP;
|
||||
this.tetheringSettings[SETTINGS_USB_PREFIX] = DEFAULT_USB_PREFIX;
|
||||
this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP] = DEFAULT_USB_DHCPSERVER_STARTIP;
|
||||
@ -723,34 +674,42 @@ NetworkManager.prototype = {
|
||||
this.tetheringSettings[SETTINGS_USB_DNS2] = DEFAULT_DNS2;
|
||||
},
|
||||
|
||||
_requestCount: 0,
|
||||
|
||||
handle: function handle(aName, aResult) {
|
||||
switch(aName) {
|
||||
case SETTINGS_USB_ENABLED:
|
||||
this.handleUSBTetheringToggle(aResult);
|
||||
break;
|
||||
// SETTINGS_WIFI_ENABLED is handled in WifiManager.js to deal with
|
||||
// the interaction between wifi and wifi tethering settings. Also, we
|
||||
// update tetheringSettings[SETTINGS_WIFI_ENABLED] in setWifiTethering
|
||||
// function.
|
||||
case SETTINGS_WIFI_SSID:
|
||||
case SETTINGS_WIFI_SECURITY_TYPE:
|
||||
case SETTINGS_WIFI_SECURITY_PASSWORD:
|
||||
case SETTINGS_WIFI_IP:
|
||||
case SETTINGS_WIFI_PREFIX:
|
||||
case SETTINGS_WIFI_DHCPSERVER_STARTIP:
|
||||
case SETTINGS_WIFI_DHCPSERVER_ENDIP:
|
||||
case SETTINGS_WIFI_DNS1:
|
||||
case SETTINGS_WIFI_DNS2:
|
||||
this._oldUsbTetheringEnabledState = this.tetheringSettings[SETTINGS_USB_ENABLED];
|
||||
case SETTINGS_USB_IP:
|
||||
case SETTINGS_USB_PREFIX:
|
||||
case SETTINGS_USB_DHCPSERVER_STARTIP:
|
||||
case SETTINGS_USB_DHCPSERVER_ENDIP:
|
||||
case SETTINGS_USB_DNS1:
|
||||
case SETTINGS_USB_DNS2:
|
||||
if (aResult) {
|
||||
if (aResult !== null) {
|
||||
this.tetheringSettings[aName] = aResult;
|
||||
}
|
||||
debug("'" + aName + "'" + " is now " + this.tetheringSettings[aName]);
|
||||
let index = this._usbTetheringSettingsToRead.indexOf(aName);
|
||||
|
||||
if (index != -1) {
|
||||
this._usbTetheringSettingsToRead.splice(index, 1);
|
||||
}
|
||||
|
||||
if (this._usbTetheringSettingsToRead.length) {
|
||||
debug("We haven't read completely the usb Tethering data from settings db.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (this._oldUsbTetheringEnabledState === this.tetheringSettings[SETTINGS_USB_ENABLED]) {
|
||||
debug("No changes for SETTINGS_USB_ENABLED flag. Nothing to do.");
|
||||
break;
|
||||
}
|
||||
|
||||
this._requestCount++;
|
||||
if (this._requestCount === 1) {
|
||||
this.handleUSBTetheringToggle(aResult);
|
||||
}
|
||||
break;
|
||||
};
|
||||
},
|
||||
@ -758,7 +717,6 @@ NetworkManager.prototype = {
|
||||
handleError: function handleError(aErrorMessage) {
|
||||
debug("There was an error while reading Tethering settings.");
|
||||
this.tetheringSettings = {};
|
||||
this.tetheringSettings[SETTINGS_WIFI_ENABLED] = false;
|
||||
this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
|
||||
},
|
||||
|
||||
@ -771,14 +729,36 @@ NetworkManager.prototype = {
|
||||
return null;
|
||||
},
|
||||
|
||||
_usbTetheringAction: TETHERING_STATE_IDLE,
|
||||
|
||||
_usbTetheringSettingsToRead: [],
|
||||
|
||||
_oldUsbTetheringEnabledState: null,
|
||||
|
||||
// External and internal interface name.
|
||||
_tetheringInterface: null,
|
||||
|
||||
handleUSBTetheringToggle: function handleUSBTetheringToggle(enable) {
|
||||
if (this.tetheringSettings[SETTINGS_USB_ENABLED] == enable) {
|
||||
handleLastRequest: function handleLastRequest() {
|
||||
let count = this._requestCount;
|
||||
this._requestCount = 0;
|
||||
|
||||
if (count === 1) {
|
||||
if (this.wantConnectionEvent) {
|
||||
if (this.tetheringSettings[SETTINGS_USB_ENABLED]) {
|
||||
this.wantConnectionEvent.call(this);
|
||||
}
|
||||
this.wantConnectionEvent = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (count > 1) {
|
||||
this.handleUSBTetheringToggle(this.tetheringSettings[SETTINGS_USB_ENABLED]);
|
||||
this.wantConnectionEvent = null;
|
||||
}
|
||||
},
|
||||
|
||||
handleUSBTetheringToggle: function handleUSBTetheringToggle(enable) {
|
||||
if (!enable) {
|
||||
this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
|
||||
this.enableUsbRndis(false, this.enableUsbRndisResult);
|
||||
@ -797,72 +777,6 @@ NetworkManager.prototype = {
|
||||
this.enableUsbRndis(true, this.enableUsbRndisResult);
|
||||
},
|
||||
|
||||
getWifiTetheringParameters: function getWifiTetheringParameters(enable, tetheringinterface) {
|
||||
let ssid;
|
||||
let securityType;
|
||||
let securityId;
|
||||
let interfaceIp;
|
||||
let prefix;
|
||||
let dhcpStartIp;
|
||||
let dhcpEndIp;
|
||||
let dns1;
|
||||
let dns2;
|
||||
let internalInterface = tetheringinterface.internalInterface;
|
||||
let externalInterface = tetheringinterface.externalInterface;
|
||||
|
||||
ssid = this.tetheringSettings[SETTINGS_WIFI_SSID];
|
||||
securityType = this.tetheringSettings[SETTINGS_WIFI_SECURITY_TYPE];
|
||||
securityId = this.tetheringSettings[SETTINGS_WIFI_SECURITY_PASSWORD];
|
||||
interfaceIp = this.tetheringSettings[SETTINGS_WIFI_IP];
|
||||
prefix = this.tetheringSettings[SETTINGS_WIFI_PREFIX];
|
||||
dhcpStartIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP];
|
||||
dhcpEndIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP];
|
||||
dns1 = this.tetheringSettings[SETTINGS_WIFI_DNS1];
|
||||
dns2 = this.tetheringSettings[SETTINGS_WIFI_DNS2];
|
||||
|
||||
// Check the format to prevent netd from crash.
|
||||
if (!ssid || ssid == "") {
|
||||
debug("Invalid SSID value.");
|
||||
return null;
|
||||
}
|
||||
if (securityType != WIFI_SECURITY_TYPE_NONE &&
|
||||
securityType != WIFI_SECURITY_TYPE_WPA_PSK &&
|
||||
securityType != WIFI_SECURITY_TYPE_WPA2_PSK) {
|
||||
|
||||
debug("Invalid security type.");
|
||||
return null;
|
||||
}
|
||||
if (securityType != WIFI_SECURITY_TYPE_NONE && !securityId) {
|
||||
debug("Invalid security password.");
|
||||
return null;
|
||||
}
|
||||
// Using the default values here until application supports these settings.
|
||||
if (interfaceIp == "" || prefix == "" ||
|
||||
dhcpStartIp == "" || dhcpEndIp == "") {
|
||||
debug("Invalid subnet information.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
ifname: internalInterface,
|
||||
wifictrlinterfacename: WIFI_CTRL_INTERFACE,
|
||||
ssid: ssid,
|
||||
security: securityType,
|
||||
key: securityId,
|
||||
ip: interfaceIp,
|
||||
prefix: prefix,
|
||||
startIp: dhcpStartIp,
|
||||
endIp: dhcpEndIp,
|
||||
dns1: dns1,
|
||||
dns2: dns2,
|
||||
internalIfname: internalInterface,
|
||||
externalIfname: externalInterface,
|
||||
enable: enable,
|
||||
mode: enable ? WIFI_FIRMWARE_AP : WIFI_FIRMWARE_STATION,
|
||||
link: enable ? NETWORK_INTERFACE_UP : NETWORK_INTERFACE_DOWN
|
||||
};
|
||||
},
|
||||
|
||||
getUSBTetheringParameters: function getUSBTetheringParameters(enable, tetheringinterface) {
|
||||
let interfaceIp;
|
||||
let prefix;
|
||||
@ -902,14 +816,9 @@ NetworkManager.prototype = {
|
||||
};
|
||||
},
|
||||
|
||||
get wifiTetheringEnabled() {
|
||||
return this.tetheringSettings[SETTINGS_WIFI_ENABLED];
|
||||
},
|
||||
|
||||
notifyError: function notifyError(resetSettings, callback, msg) {
|
||||
if (resetSettings) {
|
||||
let settingsLock = gSettingsService.createLock();
|
||||
this.tetheringSettings[SETTINGS_WIFI_ENABLED] = false;
|
||||
// Disable wifi tethering with a useful error message for the user.
|
||||
settingsLock.set("tethering.wifi.enabled", false, null, msg);
|
||||
}
|
||||
@ -922,16 +831,17 @@ NetworkManager.prototype = {
|
||||
},
|
||||
|
||||
// Enable/disable WiFi tethering by sending commands to netd.
|
||||
setWifiTethering: function setWifiTethering(enable, network, callback) {
|
||||
if (this.tetheringSettings[SETTINGS_WIFI_ENABLED] == enable) {
|
||||
this.notifyError(false, callback, "no change");
|
||||
return;
|
||||
}
|
||||
|
||||
setWifiTethering: function setWifiTethering(enable, network, config, callback) {
|
||||
if (!network) {
|
||||
this.notifyError(true, callback, "invalid network information");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
this.notifyError(true, callback, "invalid configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
this._tetheringInterface[TETHERING_TYPE_WIFI].internalInterface = network.name;
|
||||
|
||||
let mobile = this.getNetworkInterface(Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE);
|
||||
@ -939,27 +849,22 @@ NetworkManager.prototype = {
|
||||
if (mobile) {
|
||||
this._tetheringInterface[TETHERING_TYPE_WIFI].externalInterface = mobile.name;
|
||||
}
|
||||
// Clear this flag to prevent unexpected action.
|
||||
this.waitForConnectionReadyCallback = null;
|
||||
|
||||
let params = this.getWifiTetheringParameters(enable, this._tetheringInterface[TETHERING_TYPE_WIFI]);
|
||||
if (!params) {
|
||||
this.notifyError(true, callback, "invalid parameters");
|
||||
return;
|
||||
}
|
||||
config.ifname = this._tetheringInterface[TETHERING_TYPE_WIFI].internalInterface;
|
||||
config.internalIfname = this._tetheringInterface[TETHERING_TYPE_WIFI].internalInterface;
|
||||
config.externalIfname = this._tetheringInterface[TETHERING_TYPE_WIFI].externalInterface;
|
||||
config.wifictrlinterfacename = WIFI_CTRL_INTERFACE;
|
||||
|
||||
params.cmd = "setWifiTethering";
|
||||
config.cmd = "setWifiTethering";
|
||||
// The callback function in controlMessage may not be fired immediately.
|
||||
params.isAsync = true;
|
||||
this.controlMessage(params, function setWifiTetheringResult(data) {
|
||||
config.isAsync = true;
|
||||
this.controlMessage(config, function setWifiTetheringResult(data) {
|
||||
let code = data.resultCode;
|
||||
let reason = data.resultReason;
|
||||
let enable = data.enable;
|
||||
let enableString = enable ? "Enable" : "Disable";
|
||||
|
||||
debug(enableString + " Wifi tethering result: Code " + code + " reason " + reason);
|
||||
// Update settings status.
|
||||
this.tetheringSettings[SETTINGS_WIFI_ENABLED] = enable;
|
||||
|
||||
if (isError(code)) {
|
||||
this.notifyError(true, callback, "netd command error");
|
||||
@ -970,7 +875,9 @@ NetworkManager.prototype = {
|
||||
},
|
||||
|
||||
// Enable/disable USB tethering by sending commands to netd.
|
||||
setUSBTethering: function setUSBTethering(enable, tetheringInterface) {
|
||||
setUSBTethering: function setUSBTethering(enable,
|
||||
tetheringInterface,
|
||||
callback) {
|
||||
let params = this.getUSBTetheringParameters(enable, tetheringInterface);
|
||||
|
||||
if (params === null) {
|
||||
@ -979,15 +886,15 @@ NetworkManager.prototype = {
|
||||
resultCode: NETD_COMMAND_ERROR,
|
||||
resultReason: "Invalid parameters"
|
||||
};
|
||||
this.usbTetheringResultReport(params);
|
||||
this.enableUsbRndis(false, null);
|
||||
this.usbTetheringResultReport(params);
|
||||
return;
|
||||
}
|
||||
|
||||
params.cmd = "setUSBTethering";
|
||||
// The callback function in controlMessage may not be fired immediately.
|
||||
params.isAsync = true;
|
||||
this.controlMessage(params, this.usbTetheringResultReport);
|
||||
this.controlMessage(params, callback);
|
||||
},
|
||||
|
||||
getUsbInterface: function getUsbInterface() {
|
||||
@ -1012,7 +919,9 @@ NetworkManager.prototype = {
|
||||
let enable = data.enable;
|
||||
if (result) {
|
||||
this._tetheringInterface[TETHERING_TYPE_USB].internalInterface = this.getUsbInterface();
|
||||
this.setUSBTethering(enable, this._tetheringInterface[TETHERING_TYPE_USB]);
|
||||
this.setUSBTethering(enable,
|
||||
this._tetheringInterface[TETHERING_TYPE_USB],
|
||||
this.usbTetheringResultReport);
|
||||
} else {
|
||||
let params = {
|
||||
enable: false,
|
||||
@ -1040,6 +949,7 @@ NetworkManager.prototype = {
|
||||
|
||||
// The callback function in controlMessage may not be fired immediately.
|
||||
params.isAsync = true;
|
||||
this._usbTetheringAction = TETHERING_STATE_ONGOING;
|
||||
this.controlMessage(params, callback);
|
||||
},
|
||||
|
||||
@ -1051,11 +961,84 @@ NetworkManager.prototype = {
|
||||
let settingsLock = gSettingsService.createLock();
|
||||
|
||||
debug(enableString + " USB tethering result: Code " + code + " reason " + reason);
|
||||
this._usbTetheringAction = TETHERING_STATE_IDLE;
|
||||
// Disable tethering settings when fail to enable it.
|
||||
if (isError(code)) {
|
||||
this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
|
||||
settingsLock.set("tethering.usb.enabled", false, null);
|
||||
// Skip others request when we found an error.
|
||||
this._requestCount = 0;
|
||||
} else {
|
||||
this.handleLastRequest();
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
updateUpStream: function updateUpStream(previous, current, callback) {
|
||||
let params = {
|
||||
cmd: "updateUpStream",
|
||||
isAsync: true,
|
||||
previous: previous,
|
||||
current: current
|
||||
};
|
||||
|
||||
this.controlMessage(params, callback);
|
||||
},
|
||||
|
||||
onConnectionChangedReport: function onConnectionChangedReport(data) {
|
||||
let code = data.resultCode;
|
||||
let reason = data.resultReason;
|
||||
|
||||
debug("onConnectionChangedReport result: Code " + code + " reason " + reason);
|
||||
|
||||
if (!isError(code)) {
|
||||
// Update the external interface.
|
||||
this._tetheringInterface[TETHERING_TYPE_USB].externalInterface = data.current.externalIfname;
|
||||
debug("Change the interface name to " + data.current.externalIfname);
|
||||
}
|
||||
},
|
||||
|
||||
onConnectionChanged: function onConnectionChanged(network) {
|
||||
if (network.state != Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) {
|
||||
debug("We are only interested in CONNECTED event");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.tetheringSettings[SETTINGS_USB_ENABLED]) {
|
||||
debug("Usb tethering settings is not enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._tetheringInterface[TETHERING_TYPE_USB].externalInterface ===
|
||||
this.active.name) {
|
||||
debug("The active interface is the same");
|
||||
return;
|
||||
}
|
||||
|
||||
let previous = {
|
||||
internalIfname: this._tetheringInterface[TETHERING_TYPE_USB].internalInterface,
|
||||
externalIfname: this._tetheringInterface[TETHERING_TYPE_USB].externalInterface
|
||||
};
|
||||
|
||||
let current = {
|
||||
internalIfname: this._tetheringInterface[TETHERING_TYPE_USB].internalInterface,
|
||||
externalIfname: network.name
|
||||
};
|
||||
|
||||
let callback = (function () {
|
||||
// Update external network interface.
|
||||
debug("Update upstream interface to " + network.name);
|
||||
this.updateUpStream(previous, current, this.onConnectionChangedReport);
|
||||
}).bind(this);
|
||||
|
||||
if (this._usbTetheringAction === TETHERING_STATE_ONGOING) {
|
||||
debug("Postpone the event and handle it when state is idle.");
|
||||
this.wantConnectionEvent = callback;
|
||||
return;
|
||||
}
|
||||
this.wantConnectionEvent = null;
|
||||
|
||||
callback.call(this);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -116,9 +116,6 @@ function usbTetheringFail(params) {
|
||||
postMessage(params);
|
||||
// Try to roll back to ensure
|
||||
// we don't leave the network systems in limbo.
|
||||
let functionChain = [setIpForwardingEnabled,
|
||||
stopTethering];
|
||||
|
||||
// This parameter is used to disable ipforwarding.
|
||||
params.enable = false;
|
||||
chain(params, gUSBFailChain, null);
|
||||
@ -147,6 +144,18 @@ function networkInterfaceStatsSuccess(params) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateUpStreamSuccess(params) {
|
||||
// Notify the main thread.
|
||||
postMessage(params);
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateUpStreamFail(params) {
|
||||
// Notify the main thread.
|
||||
postMessage(params);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get network interface properties from the system property table.
|
||||
*
|
||||
@ -442,6 +451,18 @@ function setAccessPoint(params, callback) {
|
||||
return doCommand(command, callback);
|
||||
}
|
||||
|
||||
function cleanUpStream(params, callback) {
|
||||
let command = "nat disable " + params.previous.internalIfname + " " +
|
||||
params.previous.externalIfname + " " + "0";
|
||||
return doCommand(command, callback);
|
||||
}
|
||||
|
||||
function createUpStream(params, callback) {
|
||||
let command = "nat enable " + params.current.internalIfname + " " +
|
||||
params.current.externalIfname + " " + "0";
|
||||
return doCommand(command, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify usb function's property to turn on USB RNDIS function
|
||||
*/
|
||||
@ -616,6 +637,16 @@ function setWifiTethering(params) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let gUpdateUpStreamChain = [cleanUpStream,
|
||||
createUpStream,
|
||||
updateUpStreamSuccess];
|
||||
/**
|
||||
* handling upstream interface change event.
|
||||
*/
|
||||
function updateUpStream(params) {
|
||||
chain(params, gUpdateUpStreamChain, updateUpStreamFail);
|
||||
}
|
||||
|
||||
let gUSBEnableChain = [setInterfaceUp,
|
||||
enableNat,
|
||||
setIpForwardingEnabled,
|
||||
|
@ -112,7 +112,7 @@ interface nsINetworkStatsCallback : nsISupports
|
||||
/**
|
||||
* Manage network interfaces.
|
||||
*/
|
||||
[scriptable, uuid(4bee9633-47ed-47ae-b92b-3e0679087561)]
|
||||
[scriptable, uuid(24f8ede0-c862-11e2-8b8b-0800200c9a66)]
|
||||
interface nsINetworkManager : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -180,11 +180,6 @@ interface nsINetworkManager : nsISupports
|
||||
*/
|
||||
long overrideActive(in nsINetworkInterface network);
|
||||
|
||||
/**
|
||||
* Returns whether or not wifi tethering is currently enabled.
|
||||
*/
|
||||
readonly attribute boolean wifiTetheringEnabled;
|
||||
|
||||
/**
|
||||
* Enable or disable Wifi Tethering
|
||||
*
|
||||
@ -192,11 +187,14 @@ interface nsINetworkManager : nsISupports
|
||||
* Boolean that indicates whether tethering should be enabled (true) or disabled (false).
|
||||
* @param network
|
||||
* The Wifi network interface with at least name of network interface.
|
||||
* @param config
|
||||
* The Wifi Tethering configuration from settings db.
|
||||
* @param callback
|
||||
* Callback function used to report status to WifiManager.
|
||||
*/
|
||||
void setWifiTethering(in boolean enabled,
|
||||
in nsINetworkInterface networkInterface,
|
||||
in jsval config,
|
||||
in nsIWifiTetheringCallback callback);
|
||||
|
||||
/**
|
||||
|
@ -24,6 +24,41 @@ const kMozSettingsChangedObserverTopic = "mozsettings-changed";
|
||||
const MAX_RETRIES_ON_AUTHENTICATION_FAILURE = 2;
|
||||
const MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
|
||||
|
||||
// Settings DB path for wifi
|
||||
const SETTINGS_WIFI_ENABLED = "wifi.enabled";
|
||||
const SETTINGS_WIFI_DEBUG_ENABLED = "wifi.debugging.enabled";
|
||||
// Settings DB path for Wifi tethering.
|
||||
const SETTINGS_WIFI_TETHERING_ENABLED = "tethering.wifi.enabled";
|
||||
const SETTINGS_WIFI_SSID = "tethering.wifi.ssid";
|
||||
const SETTINGS_WIFI_SECURITY_TYPE = "tethering.wifi.security.type";
|
||||
const SETTINGS_WIFI_SECURITY_PASSWORD = "tethering.wifi.security.password";
|
||||
const SETTINGS_WIFI_IP = "tethering.wifi.ip";
|
||||
const SETTINGS_WIFI_PREFIX = "tethering.wifi.prefix";
|
||||
const SETTINGS_WIFI_DHCPSERVER_STARTIP = "tethering.wifi.dhcpserver.startip";
|
||||
const SETTINGS_WIFI_DHCPSERVER_ENDIP = "tethering.wifi.dhcpserver.endip";
|
||||
const SETTINGS_WIFI_DNS1 = "tethering.wifi.dns1";
|
||||
const SETTINGS_WIFI_DNS2 = "tethering.wifi.dns2";
|
||||
|
||||
// Default value for WIFI tethering.
|
||||
const DEFAULT_WIFI_IP = "192.168.1.1";
|
||||
const DEFAULT_WIFI_PREFIX = "24";
|
||||
const DEFAULT_WIFI_DHCPSERVER_STARTIP = "192.168.1.10";
|
||||
const DEFAULT_WIFI_DHCPSERVER_ENDIP = "192.168.1.30";
|
||||
const DEFAULT_WIFI_SSID = "FirefoxHotspot";
|
||||
const DEFAULT_WIFI_SECURITY_TYPE = "open";
|
||||
const DEFAULT_WIFI_SECURITY_PASSWORD = "1234567890";
|
||||
const DEFAULT_DNS1 = "8.8.8.8";
|
||||
const DEFAULT_DNS2 = "8.8.4.4";
|
||||
|
||||
const WIFI_FIRMWARE_AP = "AP";
|
||||
const WIFI_FIRMWARE_STATION = "STA";
|
||||
const WIFI_SECURITY_TYPE_NONE = "open";
|
||||
const WIFI_SECURITY_TYPE_WPA_PSK = "wpa-psk";
|
||||
const WIFI_SECURITY_TYPE_WPA2_PSK = "wpa2-psk";
|
||||
|
||||
const NETWORK_INTERFACE_UP = "up";
|
||||
const NETWORK_INTERFACE_DOWN = "down";
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
|
||||
"@mozilla.org/network/manager;1",
|
||||
"nsINetworkManager");
|
||||
@ -1142,7 +1177,7 @@ var WifiManager = (function() {
|
||||
}
|
||||
|
||||
// Get wifi interface and load wifi driver when enable Ap mode.
|
||||
manager.setWifiApEnabled = function(enabled, callback) {
|
||||
manager.setWifiApEnabled = function(enabled, configuration, callback) {
|
||||
if (enabled) {
|
||||
manager.tetheringState = "INITIALIZING";
|
||||
getProperty("wifi.interface", "tiwlan0", function (ifname) {
|
||||
@ -1162,7 +1197,8 @@ var WifiManager = (function() {
|
||||
function doStartWifiTethering() {
|
||||
cancelWaitForDriverReadyTimer();
|
||||
WifiNetworkInterface.name = manager.ifname;
|
||||
gNetworkManager.setWifiTethering(enabled, WifiNetworkInterface, function(result) {
|
||||
gNetworkManager.setWifiTethering(enabled, WifiNetworkInterface,
|
||||
configuration, function(result) {
|
||||
if (result) {
|
||||
manager.tetheringState = "UNINITIALIZED";
|
||||
} else {
|
||||
@ -1182,7 +1218,8 @@ var WifiManager = (function() {
|
||||
});
|
||||
});
|
||||
} else {
|
||||
gNetworkManager.setWifiTethering(enabled, WifiNetworkInterface, function(result) {
|
||||
gNetworkManager.setWifiTethering(enabled, WifiNetworkInterface,
|
||||
configuration, function(result) {
|
||||
// Should we fire a dom event if we fail to set wifi tethering ?
|
||||
debug("Disable Wifi tethering result: " + (result ? result : "successfully"));
|
||||
// Unload wifi driver even if we fail to control wifi tethering.
|
||||
@ -1661,6 +1698,10 @@ function WifiWorker() {
|
||||
this._connectionInfoTimer = null;
|
||||
this._reconnectOnDisconnect = false;
|
||||
|
||||
// Users of instances of nsITimer should keep a reference to the timer until
|
||||
// it is no longer needed in order to assure the timer is fired.
|
||||
this._callbackTimer = null;
|
||||
|
||||
// XXX On some phones (Otoro and Unagi) the wifi driver doesn't play nicely
|
||||
// with the automatic scans that wpa_supplicant does (it appears that the
|
||||
// driver forgets that it's returned scan results and then refuses to try to
|
||||
@ -1912,7 +1953,7 @@ function WifiWorker() {
|
||||
|
||||
// We get the ASSOCIATED event when we've associated but not connected, so
|
||||
// wait until the handshake is complete.
|
||||
if (this.fromStatus) {
|
||||
if (this.fromStatus || !self.currentNetwork) {
|
||||
// In this case, we connected to an already-connected wpa_supplicant,
|
||||
// because of that we need to gather information about the current
|
||||
// network here.
|
||||
@ -2114,21 +2155,21 @@ function WifiWorker() {
|
||||
// nsISettingsServiceCallback implementation
|
||||
var initWifiEnabledCb = {
|
||||
handle: function handle(aName, aResult) {
|
||||
if (aName !== "wifi.enabled")
|
||||
if (aName !== SETTINGS_WIFI_ENABLED)
|
||||
return;
|
||||
if (aResult === null)
|
||||
aResult = true;
|
||||
self.setWifiEnabled({enabled: aResult});
|
||||
self.handleWifiEnabled(aResult);
|
||||
},
|
||||
handleError: function handleError(aErrorMessage) {
|
||||
debug("Error reading the 'wifi.enabled' setting. Default to wifi on.");
|
||||
self.setWifiEnabled({enabled: true});
|
||||
self.handleWifiEnabled(true);
|
||||
}
|
||||
};
|
||||
|
||||
var initWifiDebuggingEnabledCb = {
|
||||
handle: function handle(aName, aResult) {
|
||||
if (aName !== "wifi.debugging.enabled")
|
||||
if (aName !== SETTINGS_WIFI_DEBUG_ENABLED)
|
||||
return;
|
||||
if (aResult === null)
|
||||
aResult = false;
|
||||
@ -2142,9 +2183,34 @@ function WifiWorker() {
|
||||
}
|
||||
};
|
||||
|
||||
this.initTetheringSettings();
|
||||
|
||||
let lock = gSettingsService.createLock();
|
||||
lock.get("wifi.enabled", initWifiEnabledCb);
|
||||
lock.get("wifi.debugging.enabled", initWifiDebuggingEnabledCb);
|
||||
lock.get(SETTINGS_WIFI_ENABLED, initWifiEnabledCb);
|
||||
lock.get(SETTINGS_WIFI_DEBUG_ENABLED, initWifiDebuggingEnabledCb);
|
||||
|
||||
lock.get(SETTINGS_WIFI_SSID, this);
|
||||
lock.get(SETTINGS_WIFI_SECURITY_TYPE, this);
|
||||
lock.get(SETTINGS_WIFI_SECURITY_PASSWORD, this);
|
||||
lock.get(SETTINGS_WIFI_IP, this);
|
||||
lock.get(SETTINGS_WIFI_PREFIX, this);
|
||||
lock.get(SETTINGS_WIFI_DHCPSERVER_STARTIP, this);
|
||||
lock.get(SETTINGS_WIFI_DHCPSERVER_ENDIP, this);
|
||||
lock.get(SETTINGS_WIFI_DNS1, this);
|
||||
lock.get(SETTINGS_WIFI_DNS2, this);
|
||||
lock.get(SETTINGS_WIFI_TETHERING_ENABLED, this);
|
||||
|
||||
this._wifiTetheringSettingsToRead = [SETTINGS_WIFI_SSID,
|
||||
SETTINGS_WIFI_SECURITY_TYPE,
|
||||
SETTINGS_WIFI_SECURITY_PASSWORD,
|
||||
SETTINGS_WIFI_IP,
|
||||
SETTINGS_WIFI_PREFIX,
|
||||
SETTINGS_WIFI_DHCPSERVER_STARTIP,
|
||||
SETTINGS_WIFI_DHCPSERVER_ENDIP,
|
||||
SETTINGS_WIFI_DNS1,
|
||||
SETTINGS_WIFI_DNS2,
|
||||
SETTINGS_WIFI_TETHERING_ENABLED];
|
||||
|
||||
}
|
||||
|
||||
function translateState(state) {
|
||||
@ -2178,12 +2244,32 @@ WifiWorker.prototype = {
|
||||
Ci.nsIObserver]}),
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWorkerHolder,
|
||||
Ci.nsIWifi]),
|
||||
Ci.nsIWifi,
|
||||
Ci.nsISettingsServiceCallback]),
|
||||
|
||||
disconnectedByWifi: false,
|
||||
|
||||
disconnectedByWifiTethering: false,
|
||||
|
||||
_wifiTetheringSettingsToRead: [],
|
||||
|
||||
_oldWifiTetheringEnabledState: null,
|
||||
|
||||
tetheringSettings: {},
|
||||
|
||||
initTetheringSettings: function initTetheringSettings() {
|
||||
this.tetheringSettings[SETTINGS_WIFI_ENABLED] = false;
|
||||
this.tetheringSettings[SETTINGS_WIFI_SSID] = DEFAULT_WIFI_SSID;
|
||||
this.tetheringSettings[SETTINGS_WIFI_SECURITY_TYPE] = DEFAULT_WIFI_SECURITY_TYPE;
|
||||
this.tetheringSettings[SETTINGS_WIFI_SECURITY_PASSWORD] = DEFAULT_WIFI_SECURITY_PASSWORD;
|
||||
this.tetheringSettings[SETTINGS_WIFI_IP] = DEFAULT_WIFI_IP;
|
||||
this.tetheringSettings[SETTINGS_WIFI_PREFIX] = DEFAULT_WIFI_PREFIX;
|
||||
this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP] = DEFAULT_WIFI_DHCPSERVER_STARTIP;
|
||||
this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP] = DEFAULT_WIFI_DHCPSERVER_ENDIP;
|
||||
this.tetheringSettings[SETTINGS_WIFI_DNS1] = DEFAULT_DNS1;
|
||||
this.tetheringSettings[SETTINGS_WIFI_DNS2] = DEFAULT_DNS2;
|
||||
},
|
||||
|
||||
// Internal methods.
|
||||
waitForScan: function(callback) {
|
||||
this.wantScanResults.push(callback);
|
||||
@ -2585,12 +2671,11 @@ WifiWorker.prototype = {
|
||||
!("callback" in this._stateRequests[0]) &&
|
||||
this._stateRequests[0].enabled === state);
|
||||
}
|
||||
|
||||
// If there were requests queued after this one, run them.
|
||||
if (this._stateRequests.length > 0) {
|
||||
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
let self = this;
|
||||
timer.initWithCallback(function(timer) {
|
||||
this._callbackTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this._callbackTimer.initWithCallback(function(timer) {
|
||||
if ("callback" in self._stateRequests[0]) {
|
||||
self._stateRequests[0].callback.call(self, self._stateRequests[0].enabled);
|
||||
} else {
|
||||
@ -2643,8 +2728,75 @@ WifiWorker.prototype = {
|
||||
this.setWifiEnabled({enabled: enabled, callback: callback});
|
||||
},
|
||||
|
||||
getWifiTetheringParameters: function getWifiTetheringParameters(enable) {
|
||||
let ssid;
|
||||
let securityType;
|
||||
let securityId;
|
||||
let interfaceIp;
|
||||
let prefix;
|
||||
let dhcpStartIp;
|
||||
let dhcpEndIp;
|
||||
let dns1;
|
||||
let dns2;
|
||||
|
||||
ssid = this.tetheringSettings[SETTINGS_WIFI_SSID];
|
||||
securityType = this.tetheringSettings[SETTINGS_WIFI_SECURITY_TYPE];
|
||||
securityId = this.tetheringSettings[SETTINGS_WIFI_SECURITY_PASSWORD];
|
||||
interfaceIp = this.tetheringSettings[SETTINGS_WIFI_IP];
|
||||
prefix = this.tetheringSettings[SETTINGS_WIFI_PREFIX];
|
||||
dhcpStartIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP];
|
||||
dhcpEndIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP];
|
||||
dns1 = this.tetheringSettings[SETTINGS_WIFI_DNS1];
|
||||
dns2 = this.tetheringSettings[SETTINGS_WIFI_DNS2];
|
||||
|
||||
// Check the format to prevent netd from crash.
|
||||
if (!ssid || ssid == "") {
|
||||
debug("Invalid SSID value.");
|
||||
return null;
|
||||
}
|
||||
if (securityType != WIFI_SECURITY_TYPE_NONE &&
|
||||
securityType != WIFI_SECURITY_TYPE_WPA_PSK &&
|
||||
securityType != WIFI_SECURITY_TYPE_WPA2_PSK) {
|
||||
|
||||
debug("Invalid security type.");
|
||||
return null;
|
||||
}
|
||||
if (securityType != WIFI_SECURITY_TYPE_NONE && !securityId) {
|
||||
debug("Invalid security password.");
|
||||
return null;
|
||||
}
|
||||
// Using the default values here until application supports these settings.
|
||||
if (interfaceIp == "" || prefix == "" ||
|
||||
dhcpStartIp == "" || dhcpEndIp == "") {
|
||||
debug("Invalid subnet information.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
ssid: ssid,
|
||||
security: securityType,
|
||||
key: securityId,
|
||||
ip: interfaceIp,
|
||||
prefix: prefix,
|
||||
startIp: dhcpStartIp,
|
||||
endIp: dhcpEndIp,
|
||||
dns1: dns1,
|
||||
dns2: dns2,
|
||||
enable: enable,
|
||||
mode: enable ? WIFI_FIRMWARE_AP : WIFI_FIRMWARE_STATION,
|
||||
link: enable ? NETWORK_INTERFACE_UP : NETWORK_INTERFACE_DOWN
|
||||
};
|
||||
},
|
||||
|
||||
setWifiApEnabled: function(enabled, callback) {
|
||||
WifiManager.setWifiApEnabled(enabled, callback);
|
||||
let configuration = this.getWifiTetheringParameters(enabled);
|
||||
|
||||
if (!configuration) {
|
||||
debug("Invalid Wifi Tethering configuration.");
|
||||
return;
|
||||
}
|
||||
|
||||
WifiManager.setWifiApEnabled(enabled, configuration, callback);
|
||||
},
|
||||
|
||||
associate: function(msg) {
|
||||
@ -2849,8 +3001,9 @@ WifiWorker.prototype = {
|
||||
// It's really sad that we don't have an API to notify the wifi
|
||||
// hotspot status. Toggle settings to let gaia know that wifi hotspot
|
||||
// is enabled.
|
||||
this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = true;
|
||||
gSettingsService.createLock().set(
|
||||
"tethering.wifi.enabled", true, null, "fromInternalSetting");
|
||||
SETTINGS_WIFI_TETHERING_ENABLED, true, null, "fromInternalSetting");
|
||||
// Check for the next request.
|
||||
this.nextRequest();
|
||||
},
|
||||
@ -2859,8 +3012,9 @@ WifiWorker.prototype = {
|
||||
// It's really sad that we don't have an API to notify the wifi
|
||||
// hotspot status. Toggle settings to let gaia know that wifi hotspot
|
||||
// is disabled.
|
||||
this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = false;
|
||||
gSettingsService.createLock().set(
|
||||
"tethering.wifi.enabled", false, null, "fromInternalSetting");
|
||||
SETTINGS_WIFI_TETHERING_ENABLED, false, null, "fromInternalSetting");
|
||||
// Check for the next request.
|
||||
this.nextRequest();
|
||||
},
|
||||
@ -2870,7 +3024,7 @@ WifiWorker.prototype = {
|
||||
return;
|
||||
}
|
||||
// Make sure Wifi hotspot is idle before switching to Wifi mode.
|
||||
if (enabled && (gNetworkManager.wifiTetheringEnabled ||
|
||||
if (enabled && (this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] ||
|
||||
WifiManager.tetheringState != "UNINITIALIZED")) {
|
||||
this.queueRequest(false, function(data) {
|
||||
this.disconnectedByWifi = true;
|
||||
@ -2878,7 +3032,7 @@ WifiWorker.prototype = {
|
||||
}.bind(this));
|
||||
}
|
||||
this.setWifiEnabled({enabled: enabled});
|
||||
|
||||
|
||||
if (!enabled && this.disconnectedByWifi) {
|
||||
this.queueRequest(true, function(data) {
|
||||
this.disconnectedByWifi = false;
|
||||
@ -2888,10 +3042,6 @@ WifiWorker.prototype = {
|
||||
},
|
||||
|
||||
handleWifiTetheringEnabled: function(enabled) {
|
||||
if (gNetworkManager.wifiTetheringEnabled === enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure Wifi is idle before switching to Wifi hotspot mode.
|
||||
if (enabled && (WifiManager.enabled ||
|
||||
WifiManager.state != "UNINITIALIZED")) {
|
||||
@ -2920,30 +3070,74 @@ WifiWorker.prototype = {
|
||||
}
|
||||
|
||||
let setting = JSON.parse(data);
|
||||
if (setting.key === "wifi.debugging.enabled") {
|
||||
DEBUG = setting.value;
|
||||
updateDebug();
|
||||
return;
|
||||
}
|
||||
if (setting.key !== "wifi.enabled" &&
|
||||
setting.key !== "tethering.wifi.enabled") {
|
||||
return;
|
||||
}
|
||||
// To avoid WifiWorker setting the wifi again, don't need to deal with
|
||||
// the "mozsettings-changed" event fired from internal setting.
|
||||
if (setting.message && setting.message === "fromInternalSetting") {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (setting.key) {
|
||||
case "wifi.enabled":
|
||||
this.handleWifiEnabled(setting.value)
|
||||
this.handle(setting.key, setting.value);
|
||||
},
|
||||
|
||||
handle: function handle(aName, aResult) {
|
||||
switch(aName) {
|
||||
case SETTINGS_WIFI_ENABLED:
|
||||
this.handleWifiEnabled(aResult)
|
||||
break;
|
||||
case "tethering.wifi.enabled":
|
||||
this.handleWifiTetheringEnabled(setting.value)
|
||||
case SETTINGS_WIFI_DEBUG_ENABLED:
|
||||
if (aResult === null)
|
||||
aResult = false;
|
||||
DEBUG = aResult;
|
||||
updateDebug();
|
||||
break;
|
||||
}
|
||||
}
|
||||
case SETTINGS_WIFI_TETHERING_ENABLED:
|
||||
this._oldWifiTetheringEnabledState = this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED];
|
||||
// Fall through!
|
||||
case SETTINGS_WIFI_SSID:
|
||||
case SETTINGS_WIFI_SECURITY_TYPE:
|
||||
case SETTINGS_WIFI_SECURITY_PASSWORD:
|
||||
case SETTINGS_WIFI_IP:
|
||||
case SETTINGS_WIFI_PREFIX:
|
||||
case SETTINGS_WIFI_DHCPSERVER_STARTIP:
|
||||
case SETTINGS_WIFI_DHCPSERVER_ENDIP:
|
||||
case SETTINGS_WIFI_DNS1:
|
||||
case SETTINGS_WIFI_DNS2:
|
||||
if (aResult !== null) {
|
||||
this.tetheringSettings[aName] = aResult;
|
||||
}
|
||||
debug("'" + aName + "'" + " is now " + this.tetheringSettings[aName]);
|
||||
let index = this._wifiTetheringSettingsToRead.indexOf(aName);
|
||||
|
||||
if (index != -1) {
|
||||
this._wifiTetheringSettingsToRead.splice(index, 1);
|
||||
}
|
||||
|
||||
if (this._wifiTetheringSettingsToRead.length) {
|
||||
debug("We haven't read completely the wifi Tethering data from settings db.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (this._oldWifiTetheringEnabledState === this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED]) {
|
||||
debug("No changes for SETTINGS_WIFI_TETHERING_ENABLED flag. Nothing to do.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (this._oldWifiTetheringEnabledState === null &&
|
||||
!this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED]) {
|
||||
debug("Do nothing when initial settings for SETTINGS_WIFI_TETHERING_ENABLED flag is false.");
|
||||
break;
|
||||
}
|
||||
|
||||
this.handleWifiTetheringEnabled(aResult)
|
||||
break;
|
||||
};
|
||||
},
|
||||
|
||||
handleError: function handleError(aErrorMessage) {
|
||||
debug("There was an error while reading Tethering settings.");
|
||||
this.tetheringSettings = {};
|
||||
this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = false;
|
||||
},
|
||||
};
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WifiWorker]);
|
||||
|
@ -25,6 +25,8 @@
|
||||
|
||||
#include "GeckoProfiler.h"
|
||||
|
||||
#include "cutils/properties.h"
|
||||
|
||||
using namespace android;
|
||||
using namespace base;
|
||||
using namespace mozilla::layers;
|
||||
@ -343,6 +345,18 @@ ISurfaceAllocator::PlatformAllocSurfaceDescriptor(const gfxIntSize& aSize,
|
||||
uint32_t aCaps,
|
||||
SurfaceDescriptor* aBuffer)
|
||||
{
|
||||
|
||||
// Check for Nexus S to disable gralloc. We only check for this on ICS or
|
||||
// earlier, in hopes that JB will work.
|
||||
#ifdef ANDROID_VERSION <= 15
|
||||
char propValue[PROPERTY_VALUE_MAX];
|
||||
property_get("ro.product.device", propValue, "None");
|
||||
if (strcmp("crespo",propValue) == 0) {
|
||||
NS_WARNING("Nexus S has issues with gralloc, falling back to shmem");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Some GL implementations fail to render gralloc textures with
|
||||
// width < 64. There's not much point in gralloc'ing buffers that
|
||||
// small anyway, so fall back on shared memory plus a texture
|
||||
|
@ -223,8 +223,12 @@ runTavaruaRadio(void *)
|
||||
for (unsigned int i = 0; i < buffer.bytesused; i++) {
|
||||
switch (buf[i]) {
|
||||
case TAVARUA_EVT_RADIO_READY:
|
||||
NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_ENABLE,
|
||||
hal::FM_RADIO_OPERATION_STATUS_SUCCESS));
|
||||
// The driver sends RADIO_READY both when we turn the radio on and when we turn
|
||||
// the radio off.
|
||||
if (sRadioEnabled) {
|
||||
NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_ENABLE,
|
||||
hal::FM_RADIO_OPERATION_STATUS_SUCCESS));
|
||||
}
|
||||
break;
|
||||
case TAVARUA_EVT_SEEK_COMPLETE:
|
||||
NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_SEEK,
|
||||
|
@ -39,6 +39,7 @@ SHARED_LIBRARY_LIBS = \
|
||||
$(DEPTH)/content/html/content/src/$(LIB_PREFIX)gkconhtmlcon_s.$(LIB_SUFFIX) \
|
||||
$(DEPTH)/content/html/document/src/$(LIB_PREFIX)gkconhtmldoc_s.$(LIB_SUFFIX) \
|
||||
$(DEPTH)/content/media/$(LIB_PREFIX)gkconmedia_s.$(LIB_SUFFIX) \
|
||||
$(DEPTH)/content/media/encoder/$(LIB_PREFIX)gkconencoder_s.$(LIB_SUFFIX) \
|
||||
$(DEPTH)/content/media/webaudio/$(LIB_PREFIX)gkconwebaudio_s.$(LIB_SUFFIX) \
|
||||
$(DEPTH)/content/media/webaudio/blink/$(LIB_PREFIX)gkconwebaudio_blink_s.$(LIB_SUFFIX) \
|
||||
$(DEPTH)/content/media/webrtc/$(LIB_PREFIX)gkconwebrtc_s.$(LIB_SUFFIX) \
|
||||
|
@ -57,9 +57,13 @@ ogg_page_granulepos
|
||||
ogg_page_serialno
|
||||
ogg_stream_check
|
||||
ogg_stream_clear
|
||||
ogg_stream_eos
|
||||
ogg_stream_flush
|
||||
ogg_stream_init
|
||||
ogg_stream_packetin
|
||||
ogg_stream_packetout
|
||||
ogg_stream_pagein
|
||||
ogg_stream_pageout
|
||||
ogg_stream_reset
|
||||
ogg_sync_buffer
|
||||
ogg_sync_clear
|
||||
|
@ -79,7 +79,7 @@ MTRANSPORT_LCPPSRCS = \
|
||||
transportlayerprsock.cpp \
|
||||
$(NULL)
|
||||
|
||||
ifdef MOZ_B2G_RIL
|
||||
ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
|
||||
MTRANSPORT_LCPPSRCS += \
|
||||
gonk_addrs.cpp \
|
||||
$(NULL)
|
||||
|
Loading…
Reference in New Issue
Block a user