summaryrefslogtreecommitdiffstats
path: root/dom/media/encoder/TrackEncoder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/encoder/TrackEncoder.cpp')
-rw-r--r--dom/media/encoder/TrackEncoder.cpp342
1 files changed, 342 insertions, 0 deletions
diff --git a/dom/media/encoder/TrackEncoder.cpp b/dom/media/encoder/TrackEncoder.cpp
new file mode 100644
index 000000000..ea39bb5a6
--- /dev/null
+++ b/dom/media/encoder/TrackEncoder.cpp
@@ -0,0 +1,342 @@
+/* -*- 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 "AudioChannelFormat.h"
+#include "MediaStreamGraph.h"
+#include "MediaStreamListener.h"
+#include "mozilla/Logging.h"
+#include "VideoUtils.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 {
+
+LazyLogModule gTrackEncoderLog("TrackEncoder");
+#define TRACK_LOG(type, msg) MOZ_LOG(gTrackEncoderLog, type, msg)
+
+static const int DEFAULT_CHANNELS = 1;
+static const int DEFAULT_SAMPLING_RATE = 16000;
+static const int DEFAULT_FRAME_WIDTH = 640;
+static const int DEFAULT_FRAME_HEIGHT = 480;
+static const int DEFAULT_TRACK_RATE = USECS_PER_S;
+// 30 seconds threshold if the encoder still can't not be initialized.
+static const int INIT_FAILED_DURATION = 30;
+
+TrackEncoder::TrackEncoder()
+ : mReentrantMonitor("media.TrackEncoder")
+ , mEncodingComplete(false)
+ , mEosSetInEncoder(false)
+ , mInitialized(false)
+ , mEndOfStream(false)
+ , mCanceled(false)
+ , mInitCounter(0)
+ , mNotInitDuration(0)
+{
+}
+
+void TrackEncoder::NotifyEvent(MediaStreamGraph* aGraph,
+ MediaStreamGraphEvent event)
+{
+ if (event == MediaStreamGraphEvent::EVENT_REMOVED) {
+ NotifyEndOfStream();
+ }
+}
+
+void
+AudioTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
+ TrackID aID,
+ StreamTime aTrackOffset,
+ uint32_t aTrackEvents,
+ const MediaSegment& aQueuedMedia)
+{
+ if (mCanceled) {
+ return;
+ }
+
+ const AudioSegment& audio = static_cast<const AudioSegment&>(aQueuedMedia);
+
+ // Check and initialize parameters for codec encoder.
+ if (!mInitialized) {
+ mInitCounter++;
+ TRACK_LOG(LogLevel::Debug, ("Init the audio encoder %d times", mInitCounter));
+ AudioSegment::ChunkIterator iter(const_cast<AudioSegment&>(audio));
+ while (!iter.IsEnded()) {
+ AudioChunk chunk = *iter;
+
+ // The number of channels is determined by the first non-null chunk, and
+ // thus the audio encoder is initialized at this time.
+ if (!chunk.IsNull()) {
+ nsresult rv = Init(chunk.mChannelData.Length(), aGraph->GraphRate());
+ if (NS_FAILED(rv)) {
+ LOG("[AudioTrackEncoder]: Fail to initialize the encoder!");
+ NotifyCancel();
+ }
+ break;
+ }
+
+ iter.Next();
+ }
+
+ mNotInitDuration += aQueuedMedia.GetDuration();
+ if (!mInitialized &&
+ (mNotInitDuration / aGraph->GraphRate() > INIT_FAILED_DURATION) &&
+ mInitCounter > 1) {
+ LOG("[AudioTrackEncoder]: Initialize failed for 30s.");
+ NotifyEndOfStream();
+ return;
+ }
+ }
+
+ // Append and consume this raw segment.
+ AppendAudioSegment(audio);
+
+
+ // The stream has stopped and reached the end of track.
+ if (aTrackEvents == TrackEventCommand::TRACK_EVENT_ENDED) {
+ LOG("[AudioTrackEncoder]: Receive TRACK_EVENT_ENDED .");
+ NotifyEndOfStream();
+ }
+}
+
+void
+AudioTrackEncoder::NotifyEndOfStream()
+{
+ // If source audio track is completely silent till the end of encoding,
+ // initialize the encoder with default channel counts and sampling rate.
+ if (!mCanceled && !mInitialized) {
+ Init(DEFAULT_CHANNELS, DEFAULT_SAMPLING_RATE);
+ }
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mEndOfStream = true;
+ mReentrantMonitor.NotifyAll();
+}
+
+nsresult
+AudioTrackEncoder::AppendAudioSegment(const AudioSegment& aSegment)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ AudioSegment::ChunkIterator iter(const_cast<AudioSegment&>(aSegment));
+ while (!iter.IsEnded()) {
+ AudioChunk chunk = *iter;
+ // Append and consume both non-null and null chunks.
+ mRawSegment.AppendAndConsumeChunk(&chunk);
+ iter.Next();
+ }
+
+ if (mRawSegment.GetDuration() >= GetPacketDuration()) {
+ mReentrantMonitor.NotifyAll();
+ }
+
+ return NS_OK;
+}
+
+/*static*/
+void
+AudioTrackEncoder::InterleaveTrackData(AudioChunk& aChunk,
+ int32_t aDuration,
+ uint32_t aOutputChannels,
+ AudioDataValue* aOutput)
+{
+ uint32_t numChannelsToCopy = std::min(aOutputChannels,
+ static_cast<uint32_t>(aChunk.mChannelData.Length()));
+ switch(aChunk.mBufferFormat) {
+ case AUDIO_FORMAT_S16: {
+ AutoTArray<const int16_t*, 2> array;
+ array.SetLength(numChannelsToCopy);
+ for (uint32_t i = 0; i < array.Length(); i++) {
+ array[i] = static_cast<const int16_t*>(aChunk.mChannelData[i]);
+ }
+ InterleaveTrackData(array, aDuration, aOutputChannels, aOutput, aChunk.mVolume);
+ break;
+ }
+ case AUDIO_FORMAT_FLOAT32: {
+ AutoTArray<const float*, 2> array;
+ array.SetLength(numChannelsToCopy);
+ for (uint32_t i = 0; i < array.Length(); i++) {
+ array[i] = static_cast<const float*>(aChunk.mChannelData[i]);
+ }
+ InterleaveTrackData(array, aDuration, aOutputChannels, aOutput, aChunk.mVolume);
+ break;
+ }
+ case AUDIO_FORMAT_SILENCE: {
+ MOZ_ASSERT(false, "To implement.");
+ }
+ };
+}
+
+/*static*/
+void
+AudioTrackEncoder::DeInterleaveTrackData(AudioDataValue* aInput,
+ int32_t aDuration,
+ int32_t aChannels,
+ AudioDataValue* aOutput)
+{
+ for (int32_t i = 0; i < aChannels; ++i) {
+ for(int32_t j = 0; j < aDuration; ++j) {
+ aOutput[i * aDuration + j] = aInput[i + j * aChannels];
+ }
+ }
+}
+
+size_t
+AudioTrackEncoder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ return mRawSegment.SizeOfExcludingThis(aMallocSizeOf);
+}
+
+void
+VideoTrackEncoder::Init(const VideoSegment& aSegment)
+{
+ if (mInitialized) {
+ return;
+ }
+
+ mInitCounter++;
+ TRACK_LOG(LogLevel::Debug, ("Init the video encoder %d times", mInitCounter));
+ VideoSegment::ConstChunkIterator iter(aSegment);
+ while (!iter.IsEnded()) {
+ VideoChunk chunk = *iter;
+ if (!chunk.IsNull()) {
+ gfx::IntSize imgsize = chunk.mFrame.GetImage()->GetSize();
+ gfx::IntSize intrinsicSize = chunk.mFrame.GetIntrinsicSize();
+ nsresult rv = Init(imgsize.width, imgsize.height,
+ intrinsicSize.width, intrinsicSize.height);
+
+ if (NS_FAILED(rv)) {
+ LOG("[VideoTrackEncoder]: Fail to initialize the encoder!");
+ NotifyCancel();
+ }
+ break;
+ }
+
+ iter.Next();
+ }
+
+ mNotInitDuration += aSegment.GetDuration();
+ if ((mNotInitDuration / mTrackRate > INIT_FAILED_DURATION) &&
+ mInitCounter > 1) {
+ LOG("[VideoTrackEncoder]: Initialize failed for %ds.", INIT_FAILED_DURATION);
+ NotifyEndOfStream();
+ return;
+ }
+
+}
+
+void
+VideoTrackEncoder::SetCurrentFrames(const VideoSegment& aSegment)
+{
+ if (mCanceled) {
+ return;
+ }
+
+ Init(aSegment);
+ AppendVideoSegment(aSegment);
+}
+
+void
+VideoTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
+ TrackID aID,
+ StreamTime aTrackOffset,
+ uint32_t aTrackEvents,
+ const MediaSegment& aQueuedMedia)
+{
+ if (mCanceled) {
+ return;
+ }
+
+ if (!(aTrackEvents == TRACK_EVENT_CREATED ||
+ aTrackEvents == TRACK_EVENT_ENDED)) {
+ return;
+ }
+
+ const VideoSegment& video = static_cast<const VideoSegment&>(aQueuedMedia);
+
+ // Check and initialize parameters for codec encoder.
+ Init(video);
+
+ AppendVideoSegment(video);
+
+ // The stream has stopped and reached the end of track.
+ if (aTrackEvents == TrackEventCommand::TRACK_EVENT_ENDED) {
+ LOG("[VideoTrackEncoder]: Receive TRACK_EVENT_ENDED .");
+ NotifyEndOfStream();
+ }
+
+}
+
+nsresult
+VideoTrackEncoder::AppendVideoSegment(const VideoSegment& aSegment)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // Append all video segments from MediaStreamGraph, including null an
+ // non-null frames.
+ VideoSegment::ChunkIterator iter(const_cast<VideoSegment&>(aSegment));
+ while (!iter.IsEnded()) {
+ VideoChunk chunk = *iter;
+ mLastFrameDuration += chunk.GetDuration();
+ // Send only the unique video frames for encoding.
+ // Or if we got the same video chunks more than 1 seconds,
+ // force to send into encoder.
+ if ((mLastFrame != chunk.mFrame) ||
+ (mLastFrameDuration >= mTrackRate)) {
+ RefPtr<layers::Image> image = chunk.mFrame.GetImage();
+
+ // Because we may get chunks with a null image (due to input blocking),
+ // accumulate duration and give it to the next frame that arrives.
+ // Canonically incorrect - the duration should go to the previous frame
+ // - but that would require delaying until the next frame arrives.
+ // Best would be to do like OMXEncoder and pass an effective timestamp
+ // in with each frame.
+ if (image) {
+ mRawSegment.AppendFrame(image.forget(),
+ mLastFrameDuration,
+ chunk.mFrame.GetIntrinsicSize(),
+ PRINCIPAL_HANDLE_NONE,
+ chunk.mFrame.GetForceBlack());
+ mLastFrameDuration = 0;
+ }
+ }
+ mLastFrame.TakeFrom(&chunk.mFrame);
+ iter.Next();
+ }
+
+ if (mRawSegment.GetDuration() > 0) {
+ mReentrantMonitor.NotifyAll();
+ }
+
+ return NS_OK;
+}
+
+void
+VideoTrackEncoder::NotifyEndOfStream()
+{
+ // If source video track is muted till the end of encoding, initialize the
+ // encoder with default frame width, frame height, and track rate.
+ if (!mCanceled && !mInitialized) {
+ Init(DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT,
+ DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT);
+ }
+
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ mEndOfStream = true;
+ mReentrantMonitor.NotifyAll();
+}
+
+size_t
+VideoTrackEncoder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ return mRawSegment.SizeOfExcludingThis(aMallocSizeOf);
+}
+
+} // namespace mozilla