Merge m-c to inbound.

This commit is contained in:
Ryan VanderMeulen 2013-06-11 10:41:06 -04:00
commit 5f5bfa8616
30 changed files with 1887 additions and 268 deletions

View File

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

View File

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

View File

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

View File

@ -209,6 +209,10 @@ public:
uint32_t mIndex;
};
void RemoveLeading(TrackTicks aDuration)
{
RemoveLeading(aDuration, 0);
}
protected:
MediaSegmentBase(Type aType) : MediaSegment(aType) {}

View 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

View 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

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

View 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

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

View 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

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

View 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

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

View File

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

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

View 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

View File

@ -10,11 +10,13 @@ EXPORTS += [
'OggCodecState.h',
'OggDecoder.h',
'OggReader.h',
'OggWriter.h',
]
CPP_SOURCES += [
'OggCodecState.cpp',
'OggDecoder.cpp',
'OggReader.cpp',
'OggWriter.cpp',
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -79,7 +79,7 @@ MTRANSPORT_LCPPSRCS = \
transportlayerprsock.cpp \
$(NULL)
ifdef MOZ_B2G_RIL
ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
MTRANSPORT_LCPPSRCS += \
gonk_addrs.cpp \
$(NULL)