michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "OmxTrackEncoder.h" michael@0: #include "OMXCodecWrapper.h" michael@0: #include "VideoUtils.h" michael@0: #include "ISOTrackMetadata.h" michael@0: michael@0: #ifdef MOZ_WIDGET_GONK michael@0: #include michael@0: #define OMX_LOG(args...) \ michael@0: do { \ michael@0: __android_log_print(ANDROID_LOG_INFO, "OmxTrackEncoder", ##args); \ michael@0: } while (0) michael@0: #else michael@0: #define OMX_LOG(args, ...) michael@0: #endif michael@0: michael@0: using namespace android; michael@0: michael@0: namespace mozilla { michael@0: michael@0: #define ENCODER_CONFIG_FRAME_RATE 30 // fps michael@0: #define GET_ENCODED_VIDEO_FRAME_TIMEOUT 100000 // microseconds michael@0: michael@0: nsresult michael@0: OmxVideoTrackEncoder::Init(int aWidth, int aHeight, int aDisplayWidth, michael@0: int aDisplayHeight, TrackRate aTrackRate) michael@0: { michael@0: mFrameWidth = aWidth; michael@0: mFrameHeight = aHeight; michael@0: mTrackRate = aTrackRate; michael@0: mDisplayWidth = aDisplayWidth; michael@0: mDisplayHeight = aDisplayHeight; michael@0: michael@0: mEncoder = OMXCodecWrapper::CreateAVCEncoder(); michael@0: NS_ENSURE_TRUE(mEncoder, NS_ERROR_FAILURE); michael@0: michael@0: nsresult rv = mEncoder->Configure(mFrameWidth, mFrameHeight, michael@0: ENCODER_CONFIG_FRAME_RATE); michael@0: michael@0: ReentrantMonitorAutoEnter mon(mReentrantMonitor); michael@0: mInitialized = (rv == NS_OK); michael@0: michael@0: mReentrantMonitor.NotifyAll(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: already_AddRefed michael@0: OmxVideoTrackEncoder::GetMetadata() michael@0: { michael@0: { michael@0: // Wait if mEncoder is not initialized nor is being canceled. michael@0: ReentrantMonitorAutoEnter mon(mReentrantMonitor); michael@0: while (!mCanceled && !mInitialized) { michael@0: mReentrantMonitor.Wait(); michael@0: } michael@0: } michael@0: michael@0: if (mCanceled || mEncodingComplete) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr meta = new AVCTrackMetadata(); michael@0: meta->mWidth = mFrameWidth; michael@0: meta->mHeight = mFrameHeight; michael@0: meta->mDisplayWidth = mDisplayWidth; michael@0: meta->mDisplayHeight = mDisplayHeight; michael@0: meta->mFrameRate = ENCODER_CONFIG_FRAME_RATE; michael@0: return meta.forget(); michael@0: } michael@0: michael@0: nsresult michael@0: OmxVideoTrackEncoder::GetEncodedTrack(EncodedFrameContainer& aData) michael@0: { michael@0: VideoSegment segment; michael@0: { michael@0: // Move all the samples from mRawSegment to segment. We only hold the michael@0: // monitor in this block. michael@0: ReentrantMonitorAutoEnter mon(mReentrantMonitor); michael@0: michael@0: // Wait if mEncoder is not initialized nor is being canceled. michael@0: while (!mCanceled && (!mInitialized || michael@0: (mRawSegment.GetDuration() == 0 && !mEndOfStream))) { michael@0: mReentrantMonitor.Wait(); michael@0: } michael@0: michael@0: if (mCanceled || mEncodingComplete) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: segment.AppendFrom(&mRawSegment); michael@0: } michael@0: michael@0: // Start queuing raw frames to the input buffers of OMXCodecWrapper. michael@0: VideoSegment::ChunkIterator iter(segment); michael@0: while (!iter.IsEnded()) { michael@0: VideoChunk chunk = *iter; michael@0: michael@0: // Send only the unique video frames to OMXCodecWrapper. michael@0: if (mLastFrame != chunk.mFrame) { michael@0: uint64_t totalDurationUs = mTotalFrameDuration * USECS_PER_S / mTrackRate; michael@0: layers::Image* img = (chunk.IsNull() || chunk.mFrame.GetForceBlack()) ? michael@0: nullptr : chunk.mFrame.GetImage(); michael@0: mEncoder->Encode(img, mFrameWidth, mFrameHeight, totalDurationUs); michael@0: } michael@0: michael@0: mLastFrame.TakeFrom(&chunk.mFrame); michael@0: mTotalFrameDuration += chunk.GetDuration(); michael@0: michael@0: iter.Next(); michael@0: } michael@0: michael@0: // Send the EOS signal to OMXCodecWrapper. michael@0: if (mEndOfStream && iter.IsEnded() && !mEosSetInEncoder) { michael@0: uint64_t totalDurationUs = mTotalFrameDuration * USECS_PER_S / mTrackRate; michael@0: layers::Image* img = (!mLastFrame.GetImage() || mLastFrame.GetForceBlack()) michael@0: ? nullptr : mLastFrame.GetImage(); michael@0: nsresult result = mEncoder->Encode(img, mFrameWidth, mFrameHeight, michael@0: totalDurationUs, michael@0: OMXCodecWrapper::BUFFER_EOS); michael@0: // Keep sending EOS signal until OMXVideoEncoder gets it. michael@0: if (result == NS_OK) { michael@0: mEosSetInEncoder = true; michael@0: } michael@0: } michael@0: michael@0: // Dequeue an encoded frame from the output buffers of OMXCodecWrapper. michael@0: nsresult rv; michael@0: nsTArray buffer; michael@0: int outFlags = 0; michael@0: int64_t outTimeStampUs = 0; michael@0: mEncoder->GetNextEncodedFrame(&buffer, &outTimeStampUs, &outFlags, michael@0: GET_ENCODED_VIDEO_FRAME_TIMEOUT); michael@0: if (!buffer.IsEmpty()) { michael@0: nsRefPtr videoData = new EncodedFrame(); michael@0: if (outFlags & OMXCodecWrapper::BUFFER_CODEC_CONFIG) { michael@0: videoData->SetFrameType(EncodedFrame::AVC_CSD); michael@0: } else { michael@0: videoData->SetFrameType((outFlags & OMXCodecWrapper::BUFFER_SYNC_FRAME) ? michael@0: EncodedFrame::AVC_I_FRAME : EncodedFrame::AVC_P_FRAME); michael@0: } michael@0: rv = videoData->SwapInFrameData(buffer); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: videoData->SetTimeStamp(outTimeStampUs); michael@0: aData.AppendEncodedFrame(videoData); michael@0: } michael@0: michael@0: if (outFlags & OMXCodecWrapper::BUFFER_EOS) { michael@0: mEncodingComplete = true; michael@0: OMX_LOG("Done encoding video."); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: OmxAudioTrackEncoder::AppendEncodedFrames(EncodedFrameContainer& aContainer) michael@0: { michael@0: nsTArray frameData; michael@0: int outFlags = 0; michael@0: int64_t outTimeUs = -1; michael@0: michael@0: nsresult rv = mEncoder->GetNextEncodedFrame(&frameData, &outTimeUs, &outFlags, michael@0: 3000); // wait up to 3ms michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!frameData.IsEmpty()) { michael@0: bool isCSD = false; michael@0: if (outFlags & OMXCodecWrapper::BUFFER_CODEC_CONFIG) { // codec specific data michael@0: isCSD = true; michael@0: } else if (outFlags & OMXCodecWrapper::BUFFER_EOS) { // last frame michael@0: mEncodingComplete = true; michael@0: } michael@0: michael@0: nsRefPtr audiodata = new EncodedFrame(); michael@0: if (mEncoder->GetCodecType() == OMXCodecWrapper::AAC_ENC) { michael@0: audiodata->SetFrameType(isCSD ? michael@0: EncodedFrame::AAC_CSD : EncodedFrame::AAC_AUDIO_FRAME); michael@0: } else if (mEncoder->GetCodecType() == OMXCodecWrapper::AMR_NB_ENC){ michael@0: audiodata->SetFrameType(isCSD ? michael@0: EncodedFrame::AMR_AUDIO_CSD : EncodedFrame::AMR_AUDIO_FRAME); michael@0: } else { michael@0: MOZ_ASSERT("audio codec not supported"); michael@0: } michael@0: audiodata->SetTimeStamp(outTimeUs); michael@0: rv = audiodata->SwapInFrameData(frameData); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: aContainer.AppendEncodedFrame(audiodata); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: OmxAudioTrackEncoder::GetEncodedTrack(EncodedFrameContainer& aData) michael@0: { michael@0: AudioSegment segment; michael@0: // Move all the samples from mRawSegment to segment. We only hold michael@0: // the monitor in this block. michael@0: { michael@0: ReentrantMonitorAutoEnter mon(mReentrantMonitor); michael@0: michael@0: // Wait if mEncoder is not initialized nor canceled. michael@0: while (!mInitialized && !mCanceled) { michael@0: mReentrantMonitor.Wait(); michael@0: } michael@0: michael@0: if (mCanceled || mEncodingComplete) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: segment.AppendFrom(&mRawSegment); michael@0: } michael@0: michael@0: nsresult rv; michael@0: if (segment.GetDuration() == 0) { michael@0: // Notify EOS at least once, even if segment is empty. michael@0: if (mEndOfStream && !mEosSetInEncoder) { michael@0: mEosSetInEncoder = true; michael@0: rv = mEncoder->Encode(segment, OMXCodecWrapper::BUFFER_EOS); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: // Nothing to encode but encoder could still have encoded data for earlier michael@0: // input. michael@0: return AppendEncodedFrames(aData); michael@0: } michael@0: michael@0: // OMX encoder has limited input buffers only so we have to feed input and get michael@0: // output more than once if there are too many samples pending in segment. michael@0: while (segment.GetDuration() > 0) { michael@0: rv = mEncoder->Encode(segment, michael@0: mEndOfStream ? OMXCodecWrapper::BUFFER_EOS : 0); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = AppendEncodedFrames(aData); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: OmxAACAudioTrackEncoder::Init(int aChannels, int aSamplingRate) michael@0: { michael@0: mChannels = aChannels; michael@0: mSamplingRate = aSamplingRate; michael@0: michael@0: mEncoder = OMXCodecWrapper::CreateAACEncoder(); michael@0: NS_ENSURE_TRUE(mEncoder, NS_ERROR_FAILURE); michael@0: michael@0: nsresult rv = mEncoder->Configure(mChannels, mSamplingRate, mSamplingRate); michael@0: michael@0: ReentrantMonitorAutoEnter mon(mReentrantMonitor); michael@0: mInitialized = (rv == NS_OK); michael@0: michael@0: mReentrantMonitor.NotifyAll(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: OmxAACAudioTrackEncoder::GetMetadata() michael@0: { michael@0: { michael@0: // Wait if mEncoder is not initialized nor is being canceled. michael@0: ReentrantMonitorAutoEnter mon(mReentrantMonitor); michael@0: while (!mCanceled && !mInitialized) { michael@0: mReentrantMonitor.Wait(); michael@0: } michael@0: } michael@0: michael@0: if (mCanceled || mEncodingComplete) { michael@0: return nullptr; michael@0: } michael@0: nsRefPtr meta = new AACTrackMetadata(); michael@0: meta->mChannels = mChannels; michael@0: meta->mSampleRate = mSamplingRate; michael@0: meta->mFrameSize = OMXCodecWrapper::kAACFrameSize; michael@0: meta->mFrameDuration = OMXCodecWrapper::kAACFrameDuration; michael@0: return meta.forget(); michael@0: } michael@0: michael@0: nsresult michael@0: OmxAMRAudioTrackEncoder::Init(int aChannels, int aSamplingRate) michael@0: { michael@0: mChannels = aChannels; michael@0: mSamplingRate = aSamplingRate; michael@0: michael@0: mEncoder = OMXCodecWrapper::CreateAMRNBEncoder(); michael@0: NS_ENSURE_TRUE(mEncoder, NS_ERROR_FAILURE); michael@0: michael@0: nsresult rv = mEncoder->Configure(mChannels, mSamplingRate, AMR_NB_SAMPLERATE); michael@0: ReentrantMonitorAutoEnter mon(mReentrantMonitor); michael@0: mInitialized = (rv == NS_OK); michael@0: michael@0: mReentrantMonitor.NotifyAll(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: OmxAMRAudioTrackEncoder::GetMetadata() michael@0: { michael@0: { michael@0: // Wait if mEncoder is not initialized nor is being canceled. michael@0: ReentrantMonitorAutoEnter mon(mReentrantMonitor); michael@0: while (!mCanceled && !mInitialized) { michael@0: mReentrantMonitor.Wait(); michael@0: } michael@0: } michael@0: michael@0: if (mCanceled || mEncodingComplete) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr meta = new AMRTrackMetadata(); michael@0: return meta.forget(); michael@0: } michael@0: michael@0: }