Merge b2g-inbound to m-c.

This commit is contained in:
Ryan VanderMeulen 2013-12-18 21:27:05 -05:00
commit 9c7c648b58
18 changed files with 457 additions and 157 deletions

View File

@ -1,4 +1,4 @@
{
"revision": "7412c36923b59f6e4d7076de5be7e6ded6ddc586",
"revision": "c6aeaa41977d9410b9baac23ccea6909f796fd1f",
"repo_path": "/integration/gaia-central"
}

View File

@ -324,6 +324,9 @@ DOMMediaStream::OnTracksAvailable(OnTracksAvailableCallback* aRunnable)
void
DOMMediaStream::CheckTracksAvailable()
{
if (mTrackTypesAvailable == 0) {
return;
}
nsTArray<nsAutoPtr<OnTracksAvailableCallback> > callbacks;
callbacks.SwapElements(mRunOnTracksAvailable);

View File

@ -164,6 +164,7 @@ public:
// We only care about track additions, we'll fire the notification even if
// some of the tracks have been removed.
// Takes ownership of aCallback.
// If GetExpectedTracks() returns 0, the callback will be fired as soon as there are any tracks.
void OnTracksAvailable(OnTracksAvailableCallback* aCallback);
/**
@ -182,6 +183,7 @@ protected:
void InitSourceStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents);
void InitTrackUnionStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents);
void InitStreamCommon(MediaStream* aStream);
void CheckTracksAvailable();
class StreamListener;

View File

@ -17,12 +17,16 @@
#include "nsIDOMFile.h"
#include "mozilla/dom/BlobEvent.h"
#include "mozilla/dom/AudioStreamTrack.h"
#include "mozilla/dom/VideoStreamTrack.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_INHERITED_1(MediaRecorder, nsDOMEventTargetHelper,
mStream)
NS_IMPL_CYCLE_COLLECTION_INHERITED_2(MediaRecorder, nsDOMEventTargetHelper,
mStream, mSession)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaRecorder)
NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
@ -45,7 +49,7 @@ NS_IMPL_RELEASE_INHERITED(MediaRecorder, nsDOMEventTargetHelper)
*
* Life cycle of a Session object.
* 1) Initialization Stage (in main thread)
* Setup media streams in MSG, and bind MediaEncoder with Source Stream.
* Setup media streams in MSG, and bind MediaEncoder with Source Stream when mStream is available.
* Resource allocation, such as encoded data cache buffer and MediaEncoder.
* Create read thread.
* Automatically switch to Extract stage in the end of this stage.
@ -90,7 +94,7 @@ class MediaRecorder::Session: public nsIObserver
}
private:
Session *mSession;
nsRefPtr<Session> mSession;
};
// Record thread task.
@ -110,7 +114,36 @@ class MediaRecorder::Session: public nsIObserver
}
private:
Session *mSession;
nsRefPtr<Session> mSession;
};
// For Ensure recorder has tracks to record.
class TracksAvailableCallback : public DOMMediaStream::OnTracksAvailableCallback
{
public:
TracksAvailableCallback(Session *aSession)
: mSession(aSession) {}
virtual void NotifyTracksAvailable(DOMMediaStream* aStream)
{
uint8_t trackType = aStream->GetHintContents();
// ToDo: GetHintContents return 0 when recording media tags.
if (trackType == 0) {
nsTArray<nsRefPtr<mozilla::dom::AudioStreamTrack> > audioTracks;
aStream->GetAudioTracks(audioTracks);
nsTArray<nsRefPtr<mozilla::dom::VideoStreamTrack> > videoTracks;
aStream->GetVideoTracks(videoTracks);
// What is inside the track
if (videoTracks.Length() > 0) {
trackType |= DOMMediaStream::HINT_CONTENTS_VIDEO;
}
if (audioTracks.Length() > 0) {
trackType |= DOMMediaStream::HINT_CONTENTS_AUDIO;
}
}
mSession->AfterTracksAdded(trackType);
}
private:
nsRefPtr<Session> mSession;
};
// Main thread task.
@ -154,6 +187,7 @@ class MediaRecorder::Session: public nsIObserver
friend class PushBlobRunnable;
friend class ExtractRunnable;
friend class DestroyRunnable;
friend class TracksAvailableCallback;
public:
Session(MediaRecorder* aRecorder, int32_t aTimeSlice)
@ -169,8 +203,6 @@ public:
// Only DestroyRunnable is allowed to delete Session object.
virtual ~Session()
{
MOZ_ASSERT(NS_IsMainThread());
CleanupStreams();
}
@ -179,22 +211,6 @@ public:
MOZ_ASSERT(NS_IsMainThread());
SetupStreams();
// Create a thread to read encode media data from MediaEncoder.
if (!mReadThread) {
nsresult rv = NS_NewNamedThread("Media Encoder", getter_AddRefs(mReadThread));
if (NS_FAILED(rv)) {
CleanupStreams();
mRecorder->NotifyError(rv);
return;
}
}
// In case source media stream does not notify track end, recieve
// shutdown notification and stop Read Thread.
nsContentUtils::RegisterShutdownObserver(this);
mReadThread->Dispatch(new ExtractRunnable(this), NS_DISPATCH_NORMAL);
}
void Stop()
@ -290,14 +306,57 @@ private:
mInputPort = mTrackUnionStream->AllocateInputPort(mRecorder->mStream->GetStream(), MediaInputPort::FLAG_BLOCK_OUTPUT);
// Allocate encoder and bind with the Track Union Stream.
mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(""));
MOZ_ASSERT(mEncoder, "CreateEncoder failed");
if (mEncoder) {
mTrackUnionStream->AddListener(mEncoder);
}
TracksAvailableCallback* tracksAvailableCallback = new TracksAvailableCallback(mRecorder->mSession);
mRecorder->mStream->OnTracksAvailable(tracksAvailableCallback);
}
void AfterTracksAdded(uint8_t aTrackTypes)
{
MOZ_ASSERT(NS_IsMainThread());
// Allocate encoder and bind with union stream.
// At this stage, the API doesn't allow UA to choose the output mimeType format.
mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(""), aTrackTypes);
if (!mEncoder) {
DoSessionEndTask(NS_ERROR_ABORT);
return;
}
// media stream is ready but has been issued stop command
if (mRecorder->mState == RecordingState::Inactive) {
DoSessionEndTask(NS_OK);
return;
}
mTrackUnionStream->AddListener(mEncoder);
// Create a thread to read encode media data from MediaEncoder.
if (!mReadThread) {
nsresult rv = NS_NewNamedThread("Media Encoder", getter_AddRefs(mReadThread));
if (NS_FAILED(rv)) {
DoSessionEndTask(rv);
return;
}
}
// In case source media stream does not notify track end, recieve
// shutdown notification and stop Read Thread.
nsContentUtils::RegisterShutdownObserver(this);
mReadThread->Dispatch(new ExtractRunnable(this), NS_DISPATCH_NORMAL);
}
// application should get blob and onstop event
void DoSessionEndTask(nsresult rv)
{
MOZ_ASSERT(NS_IsMainThread());
if (NS_FAILED(rv)) {
mRecorder->NotifyError(rv);
}
CleanupStreams();
// Destroy this session object in main thread.
NS_DispatchToMainThread(new PushBlobRunnable(this));
NS_DispatchToMainThread(new DestroyRunnable(already_AddRefed<Session>(this)));
}
void CleanupStreams()
{
if (mInputPort.get()) {
@ -430,11 +489,10 @@ MediaRecorder::Stop(ErrorResult& aResult)
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
mState = RecordingState::Inactive;
mSession->Stop();
mSession = nullptr;
mState = RecordingState::Inactive;
}
void

View File

@ -104,7 +104,7 @@ protected:
// The current state of the MediaRecorder object.
RecordingState mState;
// Current recording session.
Session *mSession;
nsRefPtr<Session> mSession;
// Thread safe for mMimeType.
Mutex mMutex;
// It specifies the container format as well as the audio and video capture formats.

View File

@ -19,9 +19,14 @@ class ContainerWriter {
public:
ContainerWriter()
: mInitialized(false)
, mIsWritingComplete(false)
{}
virtual ~ContainerWriter() {}
// Mapping to DOMLocalMediaStream::TrackTypeHints
enum {
HAS_AUDIO = 1 << 0,
HAS_VIDEO = 1 << 1,
};
enum {
END_OF_STREAM = 1 << 0
};
@ -44,6 +49,11 @@ public:
*/
virtual nsresult SetMetadata(TrackMetadataBase* aMetadata) = 0;
/**
* Indicate if the writer has finished to output data
*/
virtual bool IsWritingComplete() { return mIsWritingComplete; }
enum {
FLUSH_NEEDED = 1 << 0,
GET_HEADER = 1 << 1
@ -59,9 +69,9 @@ public:
*/
virtual nsresult GetContainerData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
uint32_t aFlags = 0) = 0;
protected:
bool mInitialized;
bool mIsWritingComplete;
};
}
#endif

View File

@ -52,6 +52,8 @@ public:
P_FRAME, // predicted frame
B_FRAME, // bidirectionally predicted frame
AUDIO_FRAME, // audio frame
AAC_CSD, // AAC codec specific data
AVC_CSD, // AVC codec specific data
UNKNOW // FrameType not set
};
const nsTArray<uint8_t>& GetFrameData() const

View File

@ -5,6 +5,8 @@
#include "MediaEncoder.h"
#include "MediaDecoder.h"
#include "nsIPrincipal.h"
#include "nsMimeTypes.h"
#include "prlog.h"
#ifdef MOZ_OGG
#include "OggWriter.h"
@ -12,50 +14,30 @@
#ifdef MOZ_OPUS
#include "OpusTrackEncoder.h"
#endif
#ifdef MOZ_WEBM_ENCODER
#include "VorbisTrackEncoder.h"
#include "VP8TrackEncoder.h"
#include "WebMWriter.h"
#endif
#ifdef MOZ_OMX_ENCODER
#include "OmxTrackEncoder.h"
#include "ISOMediaWriter.h"
#endif
#ifdef MOZ_WIDGET_GONK
#include <android/log.h>
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "MediaEncoder", ## args);
#ifdef LOG
#undef LOG
#endif
#ifdef PR_LOGGING
PRLogModuleInfo* gMediaEncoderLog;
#define LOG(type, msg) PR_LOG(gMediaEncoderLog, type, msg)
#else
#define LOG(args,...)
#define LOG(type, msg)
#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
static nsIThread* sEncoderThread = nullptr;
void
MediaEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
@ -67,13 +49,15 @@ MediaEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
{
// Process the incoming raw track data from MediaStreamGraph, called on the
// thread of MediaStreamGraph.
if (aQueuedMedia.GetType() == MediaSegment::AUDIO) {
if (mAudioEncoder && aQueuedMedia.GetType() == MediaSegment::AUDIO) {
mAudioEncoder->NotifyQueuedTrackChanges(aGraph, aID, aTrackRate,
aTrackOffset, aTrackEvents,
aQueuedMedia);
} else {
// Type video is not supported for now.
} else if (mVideoEncoder && aQueuedMedia.GetType() == MediaSegment::VIDEO) {
mVideoEncoder->NotifyQueuedTrackChanges(aGraph, aID, aTrackRate,
aTrackOffset, aTrackEvents,
aQueuedMedia);
}
}
@ -81,49 +65,88 @@ void
MediaEncoder::NotifyRemoved(MediaStreamGraph* aGraph)
{
// In case that MediaEncoder does not receive a TRACK_EVENT_ENDED event.
LOG("NotifyRemoved in [MediaEncoder].");
mAudioEncoder->NotifyRemoved(aGraph);
LOG(PR_LOG_DEBUG, ("NotifyRemoved in [MediaEncoder]."));
if (mAudioEncoder) {
mAudioEncoder->NotifyRemoved(aGraph);
}
if (mVideoEncoder) {
mVideoEncoder->NotifyRemoved(aGraph);
}
}
bool
MediaEncoder::OnEncoderThread()
{
return NS_GetCurrentThread() == sEncoderThread;
}
/* static */
already_AddRefed<MediaEncoder>
MediaEncoder::CreateEncoder(const nsAString& aMIMEType)
MediaEncoder::CreateEncoder(const nsAString& aMIMEType, uint8_t aTrackTypes)
{
#ifdef PR_LOGGING
if (!gMediaEncoderLog) {
gMediaEncoderLog = PR_NewLogModule("MediaEncoder");
}
#endif
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");
nsString mimeType;
if (!aTrackTypes) {
LOG(PR_LOG_ERROR, ("NO TrackTypes!!!"));
return nullptr;
}
bool isAudioOnly = FindInReadable(NS_LITERAL_STRING("audio/"), aMIMEType);
#ifdef MOZ_OGG
if (IsOggType(aMIMEType)) {
writer = new OggWriter();
if (!isAudioOnly) {
// Initialize the videoEncoder.
#ifdef MOZ_WEBM_ENCODER
else if (MediaDecoder::IsWebMEnabled() &&
(aMIMEType.EqualsLiteral(VIDEO_WEBM) ||
(aTrackTypes & ContainerWriter::HAS_VIDEO))) {
if (aTrackTypes & ContainerWriter::HAS_AUDIO) {
audioEncoder = new VorbisTrackEncoder();
NS_ENSURE_TRUE(audioEncoder, nullptr);
}
#ifdef MOZ_OPUS
audioEncoder = new OpusTrackEncoder();
#endif
}
#endif
// If the given mime-type is video but fail to create the video encoder.
if (!isAudioOnly) {
videoEncoder = new VP8TrackEncoder();
writer = new WebMWriter(aTrackTypes);
NS_ENSURE_TRUE(writer, nullptr);
NS_ENSURE_TRUE(videoEncoder, nullptr);
mimeType = NS_LITERAL_STRING(VIDEO_WEBM);
}
// Return null if we fail to create the audio encoder.
NS_ENSURE_TRUE(audioEncoder, nullptr);
#endif //MOZ_WEBM_ENCODER
#ifdef MOZ_OMX_ENCODER
else if (aMIMEType.EqualsLiteral(VIDEO_MP4) ||
(aTrackTypes & ContainerWriter::HAS_VIDEO)) {
if (aTrackTypes & ContainerWriter::HAS_AUDIO) {
audioEncoder = new OmxAudioTrackEncoder();
NS_ENSURE_TRUE(audioEncoder, nullptr);
}
videoEncoder = new OmxVideoTrackEncoder();
writer = new ISOMediaWriter(aTrackTypes);
NS_ENSURE_TRUE(writer, nullptr);
NS_ENSURE_TRUE(videoEncoder, nullptr);
mimeType = NS_LITERAL_STRING(VIDEO_MP4);
}
#endif // MOZ_OMX_ENCODER
#ifdef MOZ_OGG
else if (MediaDecoder::IsOggEnabled() && MediaDecoder::IsOpusEnabled() &&
(aMIMEType.EqualsLiteral(AUDIO_OGG) ||
(aTrackTypes & ContainerWriter::HAS_AUDIO))) {
writer = new OggWriter();
audioEncoder = new OpusTrackEncoder();
NS_ENSURE_TRUE(writer, nullptr);
NS_ENSURE_TRUE(audioEncoder, nullptr);
mimeType = NS_LITERAL_STRING(AUDIO_OGG);
}
#endif // MOZ_OGG
else {
LOG(PR_LOG_ERROR, ("Can not find any encoder to record this media stream"));
return nullptr;
}
LOG(PR_LOG_DEBUG, ("Create encoder result:a[%d] v[%d] w[%d] mimeType = %s.",
audioEncoder != nullptr, videoEncoder != nullptr,
writer != nullptr, mimeType.get()));
encoder = new MediaEncoder(writer.forget(), audioEncoder.forget(),
videoEncoder.forget(), aMIMEType);
videoEncoder.forget(), mimeType);
return encoder.forget();
}
@ -156,75 +179,75 @@ MediaEncoder::GetEncodedData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
nsAString& aMIMEType)
{
MOZ_ASSERT(!NS_IsMainThread());
if (!sEncoderThread) {
sEncoderThread = NS_GetCurrentThread();
}
aMIMEType = mMIMEType;
bool reloop = true;
while (reloop) {
switch (mState) {
case ENCODE_METADDATA: {
nsRefPtr<TrackMetadataBase> meta = mAudioEncoder->GetMetadata();
if (meta == nullptr) {
LOG("ERROR! AudioEncoder get null Metadata!");
mState = ENCODE_ERROR;
LOG(PR_LOG_DEBUG, ("ENCODE_METADDATA TimeStamp = %f", GetEncodeTimeStamp()));
nsresult rv = CopyMetadataToMuxer(mAudioEncoder.get());
if (NS_FAILED(rv)) {
LOG(PR_LOG_ERROR, ("Error! Fail to Set Audio Metadata"));
break;
}
nsresult rv = mWriter->SetMetadata(meta);
rv = CopyMetadataToMuxer(mVideoEncoder.get());
if (NS_FAILED(rv)) {
LOG("ERROR! writer can't accept audio metadata!");
mState = ENCODE_ERROR;
break;
LOG(PR_LOG_ERROR, ("Error! Fail to Set Video Metadata"));
break;
}
rv = mWriter->GetContainerData(aOutputBufs,
ContainerWriter::GET_HEADER);
if (NS_FAILED(rv)) {
LOG("ERROR! writer fail to generate header!");
LOG(PR_LOG_ERROR,("Error! writer fail to generate header!"));
mState = ENCODE_ERROR;
break;
}
LOG(PR_LOG_DEBUG, ("Finish ENCODE_METADDATA TimeStamp = %f", GetEncodeTimeStamp()));
mState = ENCODE_TRACK;
break;
}
case ENCODE_TRACK: {
LOG(PR_LOG_DEBUG, ("ENCODE_TRACK TimeStamp = %f", GetEncodeTimeStamp()));
EncodedFrameContainer encodedData;
nsresult rv = mAudioEncoder->GetEncodedTrack(encodedData);
nsresult rv = NS_OK;
rv = WriteEncodedDataToMuxer(mAudioEncoder.get());
if (NS_FAILED(rv)) {
// Encoding might be canceled.
LOG("ERROR! Fail to get encoded data from encoder.");
mState = ENCODE_ERROR;
LOG(PR_LOG_ERROR, ("Error! Fail to write audio encoder data to muxer"));
break;
}
rv = mWriter->WriteEncodedTrack(encodedData,
mAudioEncoder->IsEncodingComplete() ?
ContainerWriter::END_OF_STREAM : 0);
LOG(PR_LOG_DEBUG, ("Audio encoded TimeStamp = %f", GetEncodeTimeStamp()));
rv = WriteEncodedDataToMuxer(mVideoEncoder.get());
if (NS_FAILED(rv)) {
LOG("ERROR! Fail to write encoded track to the media container.");
mState = ENCODE_ERROR;
LOG(PR_LOG_ERROR, ("Fail to write video encoder data to muxer"));
break;
}
LOG(PR_LOG_DEBUG, ("Video encoded TimeStamp = %f", GetEncodeTimeStamp()));
// In audio only or video only case, let unavailable track's flag to be true.
bool isAudioCompleted = (mAudioEncoder && mAudioEncoder->IsEncodingComplete()) || !mAudioEncoder;
bool isVideoCompleted = (mVideoEncoder && mVideoEncoder->IsEncodingComplete()) || !mVideoEncoder;
rv = mWriter->GetContainerData(aOutputBufs,
mAudioEncoder->IsEncodingComplete() ?
isAudioCompleted && isVideoCompleted ?
ContainerWriter::FLUSH_NEEDED : 0);
if (NS_SUCCEEDED(rv)) {
// Successfully get the copy of final container data from writer.
reloop = false;
}
mState = (mAudioEncoder->IsEncodingComplete()) ? ENCODE_DONE : ENCODE_TRACK;
mState = (mWriter->IsWritingComplete()) ? ENCODE_DONE : ENCODE_TRACK;
LOG(PR_LOG_DEBUG, ("END ENCODE_TRACK TimeStamp = %f "
"mState = %d aComplete %d vComplete %d",
GetEncodeTimeStamp(), mState, isAudioCompleted, isVideoCompleted));
break;
}
case ENCODE_DONE:
LOG("MediaEncoder has been shutdown.");
mShutdown = true;
reloop = false;
break;
case ENCODE_ERROR:
LOG("ERROR! MediaEncoder got error!");
LOG(PR_LOG_DEBUG, ("MediaEncoder has been shutdown."));
mShutdown = true;
reloop = false;
break;
@ -234,4 +257,52 @@ MediaEncoder::GetEncodedData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
}
}
nsresult
MediaEncoder::WriteEncodedDataToMuxer(TrackEncoder *aTrackEncoder)
{
if (aTrackEncoder == nullptr) {
return NS_OK;
}
if (aTrackEncoder->IsEncodingComplete()) {
return NS_OK;
}
EncodedFrameContainer encodedVideoData;
nsresult rv = aTrackEncoder->GetEncodedTrack(encodedVideoData);
if (NS_FAILED(rv)) {
// Encoding might be canceled.
LOG(PR_LOG_ERROR, ("Error! Fail to get encoded data from video encoder."));
mState = ENCODE_ERROR;
return rv;
}
rv = mWriter->WriteEncodedTrack(encodedVideoData,
aTrackEncoder->IsEncodingComplete() ?
ContainerWriter::END_OF_STREAM : 0);
if (NS_FAILED(rv)) {
LOG(PR_LOG_ERROR, ("Error! Fail to write encoded video track to the media container."));
mState = ENCODE_ERROR;
}
return rv;
}
nsresult
MediaEncoder::CopyMetadataToMuxer(TrackEncoder *aTrackEncoder)
{
if (aTrackEncoder == nullptr) {
return NS_OK;
}
nsRefPtr<TrackMetadataBase> meta = aTrackEncoder->GetMetadata();
if (meta == nullptr) {
LOG(PR_LOG_ERROR, ("Error! metadata = null"));
mState = ENCODE_ERROR;
return NS_ERROR_ABORT;
}
nsresult rv = mWriter->SetMetadata(meta);
if (NS_FAILED(rv)) {
LOG(PR_LOG_ERROR, ("Error! SetMetadata fail"));
mState = ENCODE_ERROR;
}
return rv;
}
}

View File

@ -64,6 +64,7 @@ public :
: mWriter(aWriter)
, mAudioEncoder(aAudioEncoder)
, mVideoEncoder(aVideoEncoder)
, mStartTime(TimeStamp::Now())
, mMIMEType(aMIMEType)
, mState(MediaEncoder::ENCODE_METADDATA)
, mShutdown(false)
@ -91,8 +92,12 @@ public :
* 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);
static already_AddRefed<MediaEncoder> CreateEncoder(const nsAString& aMIMEType,
uint8_t aTrackTypes = ContainerWriter::HAS_AUDIO);
/**
* Check if run on Encoder thread
*/
static bool OnEncoderThread();
/**
* 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
@ -128,12 +133,24 @@ public :
}
private:
// Get encoded data from trackEncoder and write to muxer
nsresult WriteEncodedDataToMuxer(TrackEncoder *aTrackEncoder);
// Get metadata from trackEncoder and copy to muxer
nsresult CopyMetadataToMuxer(TrackEncoder* aTrackEncoder);
nsAutoPtr<ContainerWriter> mWriter;
nsAutoPtr<AudioTrackEncoder> mAudioEncoder;
nsAutoPtr<VideoTrackEncoder> mVideoEncoder;
TimeStamp mStartTime;
nsString mMIMEType;
int mState;
bool mShutdown;
// Get duration from create encoder, for logging purpose
double GetEncodeTimeStamp()
{
TimeDuration decodeTime;
decodeTime = TimeStamp::Now() - mStartTime;
return decodeTime.ToMilliseconds();
}
};
}

View File

@ -162,7 +162,9 @@ OggWriter::GetContainerData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
if (rc) {
ProduceOggPage(aOutputBufs);
}
if (aFlags & ContainerWriter::FLUSH_NEEDED) {
mIsWritingComplete = true;
}
return (rc > 0) ? NS_OK : NS_ERROR_FAILURE;
}

View File

@ -31,7 +31,6 @@ public:
// Check metadata type integrity and reject unacceptable track encoder.
nsresult SetMetadata(TrackMetadataBase* aMetadata) MOZ_OVERRIDE;
private:
nsresult Init();

View File

@ -69,13 +69,23 @@ function startTest(test, token) {
'Events fired from ondataavailable should be BlobEvent');
is(evt.type, 'dataavailable',
'Event type should dataavailable');
ok(evt.data.size > 0,
'Blob data received should be greater than zero');
is(evt.data.type, expectedMimeType,
'Blob data received should have type = ' + expectedMimeType);
is(mediaRecorder.mimeType, expectedMimeType,
'Mime type in ondataavailable = ' + expectedMimeType);
// The initialization of encoder can be cancelled.
// On some platforms, the stop method may run after media stream track
// available, so the blob can contain the header data.
if (evt.data.size > 0) {
is(evt.data.type, expectedMimeType,
'Blob data received and should have mime type');
is(mediaRecorder.mimeType, expectedMimeType,
'Media Recorder mime type in ondataavailable = ' + expectedMimeType);
} else if (evt.data.size === 0) {
is(mediaRecorder.mimeType, '',
'Blob data mime type is empty');
is(mediaRecorder.mimeType, '',
'Media Recorder mime type in ondataavailable is empty');
} else {
ok(false, 'Blob size can not be negative');
}
// onstop should not have fired before ondataavailable
if (onStopFired) {

View File

@ -123,7 +123,6 @@ public:
info.spaceType() = mSpaceType;
EnableFMRadio(info);
IFMRadioService::Singleton()->EnableAudio(true);
return NS_OK;
}
@ -302,9 +301,9 @@ FMRadioService::EnableAudio(bool aAudioEnabled)
return;
}
bool AudioEnabled;
audioManager->GetFmRadioAudioEnabled(&AudioEnabled);
if (AudioEnabled != aAudioEnabled) {
bool audioEnabled;
audioManager->GetFmRadioAudioEnabled(&audioEnabled);
if (audioEnabled != aAudioEnabled) {
audioManager->SetFmRadioAudioEnabled(aAudioEnabled);
}
}
@ -736,6 +735,12 @@ FMRadioService::Notify(const FMRadioOperationInformation& aInfo)
// radio is enabled, we have to set the frequency first.
SetFMRadioFrequency(mPendingFrequencyInKHz);
// Bug 949855: enable audio after the FM radio HW is enabled, to make sure
// 'hw.fm.isAnalog' could be detected as |true| during first time launch.
// This case is for audio output on analog path, i.e. 'ro.moz.fm.noAnalog'
// is not |true|.
EnableAudio(true);
// Update the current frequency without sending the`FrequencyChanged`
// event, to make sure the FM app will get the right frequency when the
// `EnabledChange` event is sent.

View File

@ -1769,7 +1769,8 @@ TabChild::UpdateTapState(const WidgetTouchEvent& aEvent, nsEventStatus aStatus)
return;
}
if (aStatus == nsEventStatus_eConsumeNoDefault ||
nsIPresShell::gPreventMouseEvents) {
nsIPresShell::gPreventMouseEvents ||
aEvent.mFlags.mMultipleActionsPrevented) {
return;
}
@ -1872,10 +1873,11 @@ TabChild::RecvRealTouchEvent(const WidgetTouchEvent& aEvent,
nsCOMPtr<nsPIDOMWindow> innerWindow = outerWindow->GetCurrentInnerWindow();
if (innerWindow && innerWindow->HasTouchEventListeners()) {
SendContentReceivedTouch(aGuid, nsIPresShell::gPreventMouseEvents);
SendContentReceivedTouch(aGuid, nsIPresShell::gPreventMouseEvents ||
localEvent.mFlags.mMultipleActionsPrevented);
}
} else {
UpdateTapState(aEvent, status);
UpdateTapState(localEvent, status);
}
return true;

View File

@ -2440,6 +2440,9 @@ add_test(function test_fetch_icc_recodes() {
run_next_test();
});
/**
* Verify SimRecordHelper.readMWIS
*/
add_test(function test_read_mwis() {
let worker = newUint8Worker();
let helper = worker.GsmPDUHelper;
@ -2506,6 +2509,9 @@ add_test(function test_read_mwis() {
run_next_test();
});
/**
* Verify SimRecordHelper.updateMWIS
*/
add_test(function test_update_mwis() {
let worker = newUint8Worker();
let pduHelper = worker.GsmPDUHelper;
@ -2610,3 +2616,110 @@ add_test(function test_update_mwis() {
run_next_test();
});
/**
* Verify the call flow of receiving Class 2 SMS stored in SIM:
* 1. UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM.
* 2. SimRecordHelper.readSMS().
* 3. sendChromeMessage() with rilMessageType == "sms-received".
*/
add_test(function test_read_new_sms_on_sim() {
// Instead of reusing newUint8Worker defined in this file,
// we define our own worker to fake the methods in WorkerBuffer dynamically.
function newSmsOnSimWorkerHelper() {
let _postedMessage;
let _worker = newWorker({
postRILMessage: function fakePostRILMessage(data) {
},
postMessage: function fakePostMessage(message) {
_postedMessage = message;
}
});
_worker.debug = do_print;
return {
get postedMessage() {
return _postedMessage;
},
get worker() {
return _worker;
},
fakeWokerBuffer: function fakeWokerBuffer() {
let index = 0; // index for read
let buf = [];
_worker.Buf.writeUint8 = function (value) {
buf.push(value);
};
_worker.Buf.readUint8 = function () {
return buf[index++];
};
_worker.Buf.seekIncoming = function (offset) {
index += offset;
};
_worker.Buf.getReadAvailable = function () {
return buf.length - index;
};
}
};
}
let workerHelper = newSmsOnSimWorkerHelper();
let worker = workerHelper.worker;
worker.ICCIOHelper.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
// SimStatus: Unread, SMSC:+0123456789, Sender: +9876543210, Text: How are you?
let SimSmsPduHex = "0306911032547698040A9189674523010000208062917314080CC8F71D14969741F977FD07"
// In 4.2.25 EF_SMS Short Messages of 3GPP TS 31.102:
// 1. Record length == 176 bytes.
// 2. Any bytes in the record following the TPDU shall be filled with 'FF'.
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";
workerHelper.fakeWokerBuffer();
worker.Buf.writeString(SimSmsPduHex);
options.recordSize = 176; // Record length is fixed to 176 bytes.
if (options.callback) {
options.callback(options);
}
};
function newSmsOnSimParcel() {
let data = new Uint8Array(4 + 4); // Int32List with 1 element.
let offset = 0;
function writeInt(value) {
data[offset++] = value & 0xFF;
data[offset++] = (value >> 8) & 0xFF;
data[offset++] = (value >> 16) & 0xFF;
data[offset++] = (value >> 24) & 0xFF;
}
writeInt(1); // Length of Int32List
writeInt(1); // RecordNum = 1.
return newIncomingParcel(-1,
RESPONSE_TYPE_UNSOLICITED,
UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM,
data);
}
function do_test() {
worker.onRILMessage(newSmsOnSimParcel());
let postedMessage = workerHelper.postedMessage;
do_check_eq("sms-received", postedMessage.rilMessageType);
do_check_eq("+0123456789", postedMessage.SMSC);
do_check_eq("+9876543210", postedMessage.sender);
do_check_eq("How are you?", postedMessage.fullBody);
}
do_test();
run_next_test();
});

View File

@ -124,16 +124,17 @@ public:
CSSRect GetExpandedScrollableRect() const
{
CSSRect scrollableRect = mScrollableRect;
if (scrollableRect.width < mCompositionBounds.width) {
CSSRect compBounds = CalculateCompositedRectInCssPixels();
if (scrollableRect.width < compBounds.width) {
scrollableRect.x = std::max(0.f,
scrollableRect.x - (mCompositionBounds.width - scrollableRect.width));
scrollableRect.width = mCompositionBounds.width;
scrollableRect.x - (compBounds.width - scrollableRect.width));
scrollableRect.width = compBounds.width;
}
if (scrollableRect.height < mCompositionBounds.height) {
if (scrollableRect.height < compBounds.height) {
scrollableRect.y = std::max(0.f,
scrollableRect.y - (mCompositionBounds.height - scrollableRect.height));
scrollableRect.height = mCompositionBounds.height;
scrollableRect.y - (compBounds.height - scrollableRect.height));
scrollableRect.height = compBounds.height;
}
return scrollableRect;

View File

@ -778,7 +778,10 @@ nsEventStatus AsyncPanZoomController::OnScaleEnd(const PinchGestureInput& aEvent
SetState(PANNING);
mX.StartTouch(aEvent.mFocusPoint.x);
mY.StartTouch(aEvent.mFocusPoint.y);
} else {
SetState(NOTHING);
}
{
ReentrantMonitorAutoEnter lock(mMonitor);
ScheduleComposite();

View File

@ -287,6 +287,8 @@ nsEventStatus GestureEventListener::HandlePinchGestureEvent(const MultiTouchInpu
mState = GESTURE_NONE;
rv = nsEventStatus_eConsumeNoDefault;
} else if (mState == GESTURE_WAITING_PINCH) {
mState = GESTURE_NONE;
}
if (aClearTouches) {