/* -*- 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 "mozilla/Logging.h" #include "VideoUtils.h" #undef LOG #ifdef MOZ_WIDGET_GONK #include #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; TrackEncoder::TrackEncoder() : mReentrantMonitor("media.TrackEncoder") , mEncodingComplete(false) , mEosSetInEncoder(false) , mInitialized(false) , mEndOfStream(false) , mCanceled(false) , mAudioInitCounter(0) , mVideoInitCounter(0) { } void AudioTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, StreamTime aTrackOffset, uint32_t aTrackEvents, const MediaSegment& aQueuedMedia) { if (mCanceled) { return; } const AudioSegment& audio = static_cast(aQueuedMedia); // Check and initialize parameters for codec encoder. if (!mInitialized) { mAudioInitCounter++; TRACK_LOG(LogLevel::Debug, ("Init the audio encoder %d times", mAudioInitCounter)); AudioSegment::ChunkIterator iter(const_cast(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(); } } // 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::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(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) { switch(aChunk.mBufferFormat) { case AUDIO_FORMAT_S16: { nsAutoTArray array; array.SetLength(aOutputChannels); for (uint32_t i = 0; i < array.Length(); i++) { array[i] = static_cast(aChunk.mChannelData[i]); } InterleaveTrackData(array, aDuration, aOutputChannels, aOutput, aChunk.mVolume); break; } case AUDIO_FORMAT_FLOAT32: { nsAutoTArray array; array.SetLength(aOutputChannels); for (uint32_t i = 0; i < array.Length(); i++) { array[i] = static_cast(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::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, StreamTime aTrackOffset, uint32_t aTrackEvents, const MediaSegment& aQueuedMedia) { if (mCanceled) { return; } const VideoSegment& video = static_cast(aQueuedMedia); // Check and initialize parameters for codec encoder. if (!mInitialized) { mVideoInitCounter++; TRACK_LOG(LogLevel::Debug, ("Init the video encoder %d times", mVideoInitCounter)); VideoSegment::ChunkIterator iter(const_cast(video)); 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, aGraph->GraphRate()); if (NS_FAILED(rv)) { LOG("[VideoTrackEncoder]: Fail to initialize the encoder!"); NotifyCancel(); } break; } iter.Next(); } } AppendVideoSegment(video); // The stream has stopped and reached the end of track. if (aTrackEvents == MediaStreamListener::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(aSegment)); while (!iter.IsEnded()) { VideoChunk chunk = *iter; RefPtr image = chunk.mFrame.GetImage(); mRawSegment.AppendFrame(image.forget(), chunk.GetDuration(), chunk.mFrame.GetIntrinsicSize(), chunk.mFrame.GetForceBlack()); 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, DEFAULT_TRACK_RATE); } ReentrantMonitorAutoEnter mon(mReentrantMonitor); mEndOfStream = true; mReentrantMonitor.NotifyAll(); } size_t VideoTrackEncoder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { return mRawSegment.SizeOfExcludingThis(aMallocSizeOf); } } // namespace mozilla