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 "OMXCodecWrapper.h" michael@0: #include "OMXCodecDescriptorUtil.h" michael@0: #include "TrackEncoder.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "AudioChannelFormat.h" michael@0: #include michael@0: #include "mozilla/layers/GrallocTextureClient.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::gfx; michael@0: using namespace mozilla::layers; michael@0: michael@0: #define ENCODER_CONFIG_BITRATE 2000000 // bps michael@0: // How many seconds between I-frames. michael@0: #define ENCODER_CONFIG_I_FRAME_INTERVAL 1 michael@0: // Wait up to 5ms for input buffers. michael@0: #define INPUT_BUFFER_TIMEOUT_US (5 * 1000ll) michael@0: // AMR NB kbps michael@0: #define AMRNB_BITRATE 12200 michael@0: michael@0: #define CODEC_ERROR(args...) \ michael@0: do { \ michael@0: __android_log_print(ANDROID_LOG_ERROR, "OMXCodecWrapper", ##args); \ michael@0: } while (0) michael@0: michael@0: namespace android { michael@0: michael@0: OMXAudioEncoder* michael@0: OMXCodecWrapper::CreateAACEncoder() michael@0: { michael@0: nsAutoPtr aac(new OMXAudioEncoder(CodecType::AAC_ENC)); michael@0: // Return the object only when media codec is valid. michael@0: NS_ENSURE_TRUE(aac->IsValid(), nullptr); michael@0: michael@0: return aac.forget(); michael@0: } michael@0: michael@0: OMXAudioEncoder* michael@0: OMXCodecWrapper::CreateAMRNBEncoder() michael@0: { michael@0: nsAutoPtr amr(new OMXAudioEncoder(CodecType::AMR_NB_ENC)); michael@0: // Return the object only when media codec is valid. michael@0: NS_ENSURE_TRUE(amr->IsValid(), nullptr); michael@0: michael@0: return amr.forget(); michael@0: } michael@0: michael@0: OMXVideoEncoder* michael@0: OMXCodecWrapper::CreateAVCEncoder() michael@0: { michael@0: nsAutoPtr avc(new OMXVideoEncoder(CodecType::AVC_ENC)); michael@0: // Return the object only when media codec is valid. michael@0: NS_ENSURE_TRUE(avc->IsValid(), nullptr); michael@0: michael@0: return avc.forget(); michael@0: } michael@0: michael@0: OMXCodecWrapper::OMXCodecWrapper(CodecType aCodecType) michael@0: : mCodecType(aCodecType) michael@0: , mStarted(false) michael@0: , mAMRCSDProvided(false) michael@0: { michael@0: ProcessState::self()->startThreadPool(); michael@0: michael@0: mLooper = new ALooper(); michael@0: mLooper->start(); michael@0: michael@0: if (aCodecType == CodecType::AVC_ENC) { michael@0: mCodec = MediaCodec::CreateByType(mLooper, MEDIA_MIMETYPE_VIDEO_AVC, true); michael@0: } else if (aCodecType == CodecType::AMR_NB_ENC) { michael@0: mCodec = MediaCodec::CreateByType(mLooper, MEDIA_MIMETYPE_AUDIO_AMR_NB, true); michael@0: } else if (aCodecType == CodecType::AAC_ENC) { michael@0: mCodec = MediaCodec::CreateByType(mLooper, MEDIA_MIMETYPE_AUDIO_AAC, true); michael@0: } else { michael@0: NS_ERROR("Unknown codec type."); michael@0: } michael@0: } michael@0: michael@0: OMXCodecWrapper::~OMXCodecWrapper() michael@0: { michael@0: if (mCodec.get()) { michael@0: Stop(); michael@0: mCodec->release(); michael@0: } michael@0: mLooper->stop(); michael@0: } michael@0: michael@0: status_t michael@0: OMXCodecWrapper::Start() michael@0: { michael@0: // Already started. michael@0: NS_ENSURE_FALSE(mStarted, OK); michael@0: michael@0: status_t result = mCodec->start(); michael@0: mStarted = (result == OK); michael@0: michael@0: // Get references to MediaCodec buffers. michael@0: if (result == OK) { michael@0: mCodec->getInputBuffers(&mInputBufs); michael@0: mCodec->getOutputBuffers(&mOutputBufs); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: status_t michael@0: OMXCodecWrapper::Stop() michael@0: { michael@0: // Already stopped. michael@0: NS_ENSURE_TRUE(mStarted, OK); michael@0: michael@0: status_t result = mCodec->stop(); michael@0: mStarted = !(result == OK); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: // Check system property to see if we're running on emulator. michael@0: static bool michael@0: IsRunningOnEmulator() michael@0: { michael@0: char qemu[PROPERTY_VALUE_MAX]; michael@0: property_get("ro.kernel.qemu", qemu, ""); michael@0: return strncmp(qemu, "1", 1) == 0; michael@0: } michael@0: michael@0: nsresult michael@0: OMXVideoEncoder::Configure(int aWidth, int aHeight, int aFrameRate, michael@0: BlobFormat aBlobFormat) michael@0: { michael@0: MOZ_ASSERT(!mStarted, "Configure() was called already."); michael@0: michael@0: NS_ENSURE_TRUE(aWidth > 0 && aHeight > 0 && aFrameRate > 0, michael@0: NS_ERROR_INVALID_ARG); michael@0: michael@0: OMX_VIDEO_AVCLEVELTYPE level = OMX_VIDEO_AVCLevel3; michael@0: OMX_VIDEO_CONTROLRATETYPE bitrateMode = OMX_Video_ControlRateConstant; michael@0: // Limitation of soft AVC/H.264 encoder running on emulator in stagefright. michael@0: static bool emu = IsRunningOnEmulator(); michael@0: if (emu) { michael@0: if (aWidth > 352 || aHeight > 288) { michael@0: CODEC_ERROR("SoftAVCEncoder doesn't support resolution larger than CIF"); michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: level = OMX_VIDEO_AVCLevel2; michael@0: bitrateMode = OMX_Video_ControlRateVariable; michael@0: } michael@0: michael@0: // Set up configuration parameters for AVC/H.264 encoder. michael@0: sp format = new AMessage; michael@0: // Fixed values michael@0: format->setString("mime", MEDIA_MIMETYPE_VIDEO_AVC); michael@0: format->setInt32("bitrate", ENCODER_CONFIG_BITRATE); michael@0: format->setInt32("i-frame-interval", ENCODER_CONFIG_I_FRAME_INTERVAL); michael@0: // See mozilla::layers::GrallocImage, supports YUV 4:2:0, CbCr width and michael@0: // height is half that of Y michael@0: format->setInt32("color-format", OMX_COLOR_FormatYUV420SemiPlanar); michael@0: format->setInt32("profile", OMX_VIDEO_AVCProfileBaseline); michael@0: format->setInt32("level", level); michael@0: format->setInt32("bitrate-mode", bitrateMode); michael@0: format->setInt32("store-metadata-in-buffers", 0); michael@0: format->setInt32("prepend-sps-pps-to-idr-frames", 0); michael@0: // Input values. michael@0: format->setInt32("width", aWidth); michael@0: format->setInt32("height", aHeight); michael@0: format->setInt32("stride", aWidth); michael@0: format->setInt32("slice-height", aHeight); michael@0: format->setInt32("frame-rate", aFrameRate); michael@0: michael@0: status_t result = mCodec->configure(format, nullptr, nullptr, michael@0: MediaCodec::CONFIGURE_FLAG_ENCODE); michael@0: NS_ENSURE_TRUE(result == OK, NS_ERROR_FAILURE); michael@0: michael@0: mWidth = aWidth; michael@0: mHeight = aHeight; michael@0: mBlobFormat = aBlobFormat; michael@0: michael@0: result = Start(); michael@0: michael@0: return result == OK ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Copy pixels from planar YUV (4:4:4/4:2:2/4:2:0) or NV21 (semi-planar 4:2:0) michael@0: // format to NV12 (semi-planar 4:2:0) format for QCOM HW encoder. michael@0: // Planar YUV: YYY...UUU...VVV... michael@0: // NV21: YYY...VUVU... michael@0: // NV12: YYY...UVUV... michael@0: // For 4:4:4/4:2:2 -> 4:2:0, subsample using odd row/column without michael@0: // interpolation. michael@0: // aSource contains info about source image data, and the result will be stored michael@0: // in aDestination, whose size needs to be >= Y plane size * 3 / 2. michael@0: static void michael@0: ConvertPlanarYCbCrToNV12(const PlanarYCbCrData* aSource, uint8_t* aDestination) michael@0: { michael@0: // Fill Y plane. michael@0: uint8_t* y = aSource->mYChannel; michael@0: IntSize ySize = aSource->mYSize; michael@0: michael@0: // Y plane. michael@0: for (int i = 0; i < ySize.height; i++) { michael@0: memcpy(aDestination, y, ySize.width); michael@0: aDestination += ySize.width; michael@0: y += aSource->mYStride; michael@0: } michael@0: michael@0: // Fill interleaved UV plane. michael@0: uint8_t* u = aSource->mCbChannel; michael@0: uint8_t* v = aSource->mCrChannel; michael@0: IntSize uvSize = aSource->mCbCrSize; michael@0: // Subsample to 4:2:0 if source is 4:4:4 or 4:2:2. michael@0: // Y plane width & height should be multiple of U/V plane width & height. michael@0: MOZ_ASSERT(ySize.width % uvSize.width == 0 && michael@0: ySize.height % uvSize.height == 0); michael@0: size_t uvWidth = ySize.width / 2; michael@0: size_t uvHeight = ySize.height / 2; michael@0: size_t horiSubsample = uvSize.width / uvWidth; michael@0: size_t uPixStride = horiSubsample * (1 + aSource->mCbSkip); michael@0: size_t vPixStride = horiSubsample * (1 + aSource->mCrSkip); michael@0: size_t lineStride = uvSize.height / uvHeight * aSource->mCbCrStride; michael@0: michael@0: for (int i = 0; i < uvHeight; i++) { michael@0: // 1st pixel per line. michael@0: uint8_t* uSrc = u; michael@0: uint8_t* vSrc = v; michael@0: for (int j = 0; j < uvWidth; j++) { michael@0: *aDestination++ = *uSrc; michael@0: *aDestination++ = *vSrc; michael@0: // Pick next source pixel. michael@0: uSrc += uPixStride; michael@0: vSrc += vPixStride; michael@0: } michael@0: // Pick next source line. michael@0: u += lineStride; michael@0: v += lineStride; michael@0: } michael@0: } michael@0: michael@0: // Convert pixels in graphic buffer to NV12 format. aSource is the layer image michael@0: // containing source graphic buffer, and aDestination is the destination of michael@0: // conversion. Currently only 2 source format are supported: michael@0: // - NV21/HAL_PIXEL_FORMAT_YCrCb_420_SP (from camera preview window). michael@0: // - YV12/HAL_PIXEL_FORMAT_YV12 (from video decoder). michael@0: static void michael@0: ConvertGrallocImageToNV12(GrallocImage* aSource, uint8_t* aDestination) michael@0: { michael@0: // Get graphic buffer. michael@0: sp graphicBuffer = aSource->GetGraphicBuffer(); michael@0: michael@0: int pixelFormat = graphicBuffer->getPixelFormat(); michael@0: // Only support NV21 (from camera) or YV12 (from HW decoder output) for now. michael@0: NS_ENSURE_TRUE_VOID(pixelFormat == HAL_PIXEL_FORMAT_YCrCb_420_SP || michael@0: pixelFormat == HAL_PIXEL_FORMAT_YV12); michael@0: michael@0: void* imgPtr = nullptr; michael@0: graphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_MASK, &imgPtr); michael@0: // Build PlanarYCbCrData for NV21 or YV12 buffer. michael@0: PlanarYCbCrData yuv; michael@0: switch (pixelFormat) { michael@0: case HAL_PIXEL_FORMAT_YCrCb_420_SP: // From camera. michael@0: yuv.mYChannel = static_cast(imgPtr); michael@0: yuv.mYSkip = 0; michael@0: yuv.mYSize.width = graphicBuffer->getWidth(); michael@0: yuv.mYSize.height = graphicBuffer->getHeight(); michael@0: yuv.mYStride = graphicBuffer->getStride(); michael@0: // 4:2:0. michael@0: yuv.mCbCrSize.width = yuv.mYSize.width / 2; michael@0: yuv.mCbCrSize.height = yuv.mYSize.height / 2; michael@0: // Interleaved VU plane. michael@0: yuv.mCrChannel = yuv.mYChannel + (yuv.mYStride * yuv.mYSize.height); michael@0: yuv.mCrSkip = 1; michael@0: yuv.mCbChannel = yuv.mCrChannel + 1; michael@0: yuv.mCbSkip = 1; michael@0: yuv.mCbCrStride = yuv.mYStride; michael@0: ConvertPlanarYCbCrToNV12(&yuv, aDestination); michael@0: break; michael@0: case HAL_PIXEL_FORMAT_YV12: // From video decoder. michael@0: // Android YV12 format is defined in system/core/include/system/graphics.h michael@0: yuv.mYChannel = static_cast(imgPtr); michael@0: yuv.mYSkip = 0; michael@0: yuv.mYSize.width = graphicBuffer->getWidth(); michael@0: yuv.mYSize.height = graphicBuffer->getHeight(); michael@0: yuv.mYStride = graphicBuffer->getStride(); michael@0: // 4:2:0. michael@0: yuv.mCbCrSize.width = yuv.mYSize.width / 2; michael@0: yuv.mCbCrSize.height = yuv.mYSize.height / 2; michael@0: yuv.mCrChannel = yuv.mYChannel + (yuv.mYStride * yuv.mYSize.height); michael@0: // Aligned to 16 bytes boundary. michael@0: yuv.mCbCrStride = (yuv.mYStride / 2 + 15) & ~0x0F; michael@0: yuv.mCrSkip = 0; michael@0: yuv.mCbChannel = yuv.mCrChannel + (yuv.mCbCrStride * yuv.mCbCrSize.height); michael@0: yuv.mCbSkip = 0; michael@0: ConvertPlanarYCbCrToNV12(&yuv, aDestination); michael@0: break; michael@0: default: michael@0: NS_ERROR("Unsupported input gralloc image type. Should never be here."); michael@0: } michael@0: michael@0: graphicBuffer->unlock(); michael@0: } michael@0: michael@0: nsresult michael@0: OMXVideoEncoder::Encode(const Image* aImage, int aWidth, int aHeight, michael@0: int64_t aTimestamp, int aInputFlags) michael@0: { michael@0: MOZ_ASSERT(mStarted, "Configure() should be called before Encode()."); michael@0: michael@0: NS_ENSURE_TRUE(aWidth == mWidth && aHeight == mHeight && aTimestamp >= 0, michael@0: NS_ERROR_INVALID_ARG); michael@0: michael@0: status_t result; michael@0: michael@0: // Dequeue an input buffer. michael@0: uint32_t index; michael@0: result = mCodec->dequeueInputBuffer(&index, INPUT_BUFFER_TIMEOUT_US); michael@0: NS_ENSURE_TRUE(result == OK, NS_ERROR_FAILURE); michael@0: michael@0: const sp& inBuf = mInputBufs.itemAt(index); michael@0: uint8_t* dst = inBuf->data(); michael@0: size_t dstSize = inBuf->capacity(); michael@0: michael@0: size_t yLen = aWidth * aHeight; michael@0: size_t uvLen = yLen / 2; michael@0: // Buffer should be large enough to hold input image data. michael@0: MOZ_ASSERT(dstSize >= yLen + uvLen); michael@0: michael@0: inBuf->setRange(0, yLen + uvLen); michael@0: michael@0: if (!aImage) { michael@0: // Generate muted/black image directly in buffer. michael@0: dstSize = yLen + uvLen; michael@0: // Fill Y plane. michael@0: memset(dst, 0x10, yLen); michael@0: // Fill UV plane. michael@0: memset(dst + yLen, 0x80, uvLen); michael@0: } else { michael@0: Image* img = const_cast(aImage); michael@0: ImageFormat format = img->GetFormat(); michael@0: michael@0: MOZ_ASSERT(aWidth == img->GetSize().width && michael@0: aHeight == img->GetSize().height); michael@0: michael@0: if (format == ImageFormat::GRALLOC_PLANAR_YCBCR) { michael@0: ConvertGrallocImageToNV12(static_cast(img), dst); michael@0: } else if (format == ImageFormat::PLANAR_YCBCR) { michael@0: ConvertPlanarYCbCrToNV12(static_cast(img)->GetData(), michael@0: dst); michael@0: } else { michael@0: // TODO: support RGB to YUV color conversion. michael@0: NS_ERROR("Unsupported input image type."); michael@0: } michael@0: } michael@0: michael@0: // Queue this input buffer. michael@0: result = mCodec->queueInputBuffer(index, 0, dstSize, aTimestamp, aInputFlags); michael@0: michael@0: return result == OK ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: status_t michael@0: OMXVideoEncoder::AppendDecoderConfig(nsTArray* aOutputBuf, michael@0: ABuffer* aData) michael@0: { michael@0: // Codec already parsed aData. Using its result makes generating config blob michael@0: // much easier. michael@0: sp format; michael@0: mCodec->getOutputFormat(&format); michael@0: michael@0: // NAL unit format is needed by WebRTC for RTP packets; AVC/H.264 decoder michael@0: // config descriptor is needed to construct MP4 'avcC' box. michael@0: status_t result = GenerateAVCDescriptorBlob(format, aOutputBuf, mBlobFormat); michael@0: mHasConfigBlob = (result == OK); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: // Override to replace NAL unit start code with 4-bytes unit length. michael@0: // See ISO/IEC 14496-15 5.2.3. michael@0: void michael@0: OMXVideoEncoder::AppendFrame(nsTArray* aOutputBuf, michael@0: const uint8_t* aData, size_t aSize) michael@0: { michael@0: aOutputBuf->SetCapacity(aSize); michael@0: michael@0: if (mBlobFormat == BlobFormat::AVC_NAL) { michael@0: // Append NAL format data without modification. michael@0: aOutputBuf->AppendElements(aData, aSize); michael@0: return; michael@0: } michael@0: // Replace start code with data length. michael@0: uint8_t length[] = { michael@0: (aSize >> 24) & 0xFF, michael@0: (aSize >> 16) & 0xFF, michael@0: (aSize >> 8) & 0xFF, michael@0: aSize & 0xFF, michael@0: }; michael@0: aOutputBuf->AppendElements(length, sizeof(length)); michael@0: aOutputBuf->AppendElements(aData + sizeof(length), aSize); michael@0: } michael@0: michael@0: nsresult michael@0: OMXVideoEncoder::GetCodecConfig(nsTArray* aOutputBuf) michael@0: { michael@0: MOZ_ASSERT(mHasConfigBlob, "Haven't received codec config yet."); michael@0: michael@0: return AppendDecoderConfig(aOutputBuf, nullptr) == OK ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // MediaCodec::setParameters() is available only after API level 18. michael@0: #if ANDROID_VERSION >= 18 michael@0: nsresult michael@0: OMXVideoEncoder::SetBitrate(int32_t aKbps) michael@0: { michael@0: sp msg = new AMessage(); michael@0: msg->setInt32("videoBitrate", aKbps * 1000 /* kbps -> bps */); michael@0: status_t result = mCodec->setParameters(msg); michael@0: MOZ_ASSERT(result == OK); michael@0: return result == OK ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: #endif michael@0: michael@0: nsresult michael@0: OMXAudioEncoder::Configure(int aChannels, int aInputSampleRate, michael@0: int aEncodedSampleRate) michael@0: { michael@0: MOZ_ASSERT(!mStarted); michael@0: michael@0: NS_ENSURE_TRUE(aChannels > 0 && aInputSampleRate > 0 && aEncodedSampleRate >= 0, michael@0: NS_ERROR_INVALID_ARG); michael@0: michael@0: if (aInputSampleRate != aEncodedSampleRate) { michael@0: int error; michael@0: mResampler = speex_resampler_init(aChannels, michael@0: aInputSampleRate, michael@0: aEncodedSampleRate, michael@0: SPEEX_RESAMPLER_QUALITY_DEFAULT, michael@0: &error); michael@0: michael@0: if (error != RESAMPLER_ERR_SUCCESS) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: speex_resampler_skip_zeros(mResampler); michael@0: } michael@0: // Set up configuration parameters for AAC encoder. michael@0: sp format = new AMessage; michael@0: // Fixed values. michael@0: if (mCodecType == AAC_ENC) { michael@0: format->setString("mime", MEDIA_MIMETYPE_AUDIO_AAC); michael@0: format->setInt32("aac-profile", OMX_AUDIO_AACObjectLC); michael@0: format->setInt32("bitrate", kAACBitrate); michael@0: format->setInt32("sample-rate", aInputSampleRate); michael@0: } else if (mCodecType == AMR_NB_ENC) { michael@0: format->setString("mime", MEDIA_MIMETYPE_AUDIO_AMR_NB); michael@0: format->setInt32("bitrate", AMRNB_BITRATE); michael@0: format->setInt32("sample-rate", aEncodedSampleRate); michael@0: } else { michael@0: MOZ_ASSERT(false, "Can't support this codec type!!"); michael@0: } michael@0: // Input values. michael@0: format->setInt32("channel-count", aChannels); michael@0: michael@0: status_t result = mCodec->configure(format, nullptr, nullptr, michael@0: MediaCodec::CONFIGURE_FLAG_ENCODE); michael@0: NS_ENSURE_TRUE(result == OK, NS_ERROR_FAILURE); michael@0: michael@0: mChannels = aChannels; michael@0: mSampleDuration = 1000000 / aInputSampleRate; michael@0: mResamplingRatio = aEncodedSampleRate > 0 ? 1.0 * michael@0: aEncodedSampleRate / aInputSampleRate : 1.0; michael@0: result = Start(); michael@0: michael@0: return result == OK ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: class InputBufferHelper MOZ_FINAL { michael@0: public: michael@0: InputBufferHelper(sp& aCodec, Vector >& aBuffers) michael@0: : mCodec(aCodec) michael@0: , mBuffers(aBuffers) michael@0: , mIndex(0) michael@0: , mData(nullptr) michael@0: , mOffset(0) michael@0: , mCapicity(0) michael@0: {} michael@0: michael@0: ~InputBufferHelper() michael@0: { michael@0: // Unflushed data in buffer. michael@0: MOZ_ASSERT(!mData); michael@0: } michael@0: michael@0: status_t Dequeue() michael@0: { michael@0: // Shouldn't have dequeued buffer. michael@0: MOZ_ASSERT(!mData); michael@0: michael@0: status_t result = mCodec->dequeueInputBuffer(&mIndex, michael@0: INPUT_BUFFER_TIMEOUT_US); michael@0: NS_ENSURE_TRUE(result == OK, result); michael@0: sp inBuf = mBuffers.itemAt(mIndex); michael@0: mData = inBuf->data(); michael@0: mCapicity = inBuf->capacity(); michael@0: mOffset = 0; michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: uint8_t* GetPointer() { return mData + mOffset; } michael@0: michael@0: const size_t AvailableSize() { return mCapicity - mOffset; } michael@0: michael@0: void IncreaseOffset(size_t aValue) michael@0: { michael@0: // Should never out of bound. michael@0: MOZ_ASSERT(mOffset + aValue <= mCapicity); michael@0: mOffset += aValue; michael@0: } michael@0: michael@0: status_t Enqueue(int64_t aTimestamp, int aFlags) michael@0: { michael@0: // Should have dequeued buffer. michael@0: MOZ_ASSERT(mData); michael@0: michael@0: // Queue this buffer. michael@0: status_t result = mCodec->queueInputBuffer(mIndex, 0, mOffset, aTimestamp, michael@0: aFlags); michael@0: NS_ENSURE_TRUE(result == OK, result); michael@0: mData = nullptr; michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: private: michael@0: sp& mCodec; michael@0: Vector >& mBuffers; michael@0: size_t mIndex; michael@0: uint8_t* mData; michael@0: size_t mCapicity; michael@0: size_t mOffset; michael@0: }; michael@0: michael@0: OMXAudioEncoder::~OMXAudioEncoder() michael@0: { michael@0: if (mResampler) { michael@0: speex_resampler_destroy(mResampler); michael@0: mResampler = nullptr; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: OMXAudioEncoder::Encode(AudioSegment& aSegment, int aInputFlags) michael@0: { michael@0: #ifndef MOZ_SAMPLE_TYPE_S16 michael@0: #error MediaCodec accepts only 16-bit PCM data. michael@0: #endif michael@0: michael@0: MOZ_ASSERT(mStarted, "Configure() should be called before Encode()."); michael@0: michael@0: size_t numSamples = aSegment.GetDuration(); michael@0: michael@0: // Get input buffer. michael@0: InputBufferHelper buffer(mCodec, mInputBufs); michael@0: status_t result = buffer.Dequeue(); michael@0: if (result == -EAGAIN) { michael@0: // All input buffers are full. Caller can try again later after consuming michael@0: // some output buffers. michael@0: return NS_OK; michael@0: } michael@0: NS_ENSURE_TRUE(result == OK, NS_ERROR_FAILURE); michael@0: michael@0: size_t sourceSamplesCopied = 0; // Number of copied samples. michael@0: michael@0: if (numSamples > 0) { michael@0: // Copy input PCM data to input buffer until queue is empty. michael@0: AudioSegment::ChunkIterator iter(const_cast(aSegment)); michael@0: while (!iter.IsEnded()) { michael@0: AudioChunk chunk = *iter; michael@0: size_t sourceSamplesToCopy = chunk.GetDuration(); // Number of samples to copy. michael@0: size_t bytesToCopy = sourceSamplesToCopy * mChannels * michael@0: sizeof(AudioDataValue) * mResamplingRatio; michael@0: if (bytesToCopy > buffer.AvailableSize()) { michael@0: // Not enough space left in input buffer. Send it to encoder and get a michael@0: // new one. michael@0: result = buffer.Enqueue(mTimestamp, aInputFlags & ~BUFFER_EOS); michael@0: NS_ENSURE_TRUE(result == OK, NS_ERROR_FAILURE); michael@0: michael@0: result = buffer.Dequeue(); michael@0: if (result == -EAGAIN) { michael@0: // All input buffers are full. Caller can try again later after michael@0: // consuming some output buffers. michael@0: aSegment.RemoveLeading(sourceSamplesCopied); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mTimestamp += sourceSamplesCopied * mSampleDuration; michael@0: sourceSamplesCopied = 0; michael@0: michael@0: NS_ENSURE_TRUE(result == OK, NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: AudioDataValue* dst = reinterpret_cast(buffer.GetPointer()); michael@0: uint32_t dstSamplesCopied = sourceSamplesToCopy; michael@0: if (!chunk.IsNull()) { michael@0: if (mResampler) { michael@0: nsAutoTArray pcm; michael@0: pcm.SetLength(bytesToCopy); michael@0: // Append the interleaved data to input buffer. michael@0: AudioTrackEncoder::InterleaveTrackData(chunk, sourceSamplesToCopy, michael@0: mChannels, michael@0: pcm.Elements()); michael@0: uint32_t inframes = sourceSamplesToCopy; michael@0: short* in = reinterpret_cast(pcm.Elements()); michael@0: speex_resampler_process_interleaved_int(mResampler, in, &inframes, michael@0: dst, &dstSamplesCopied); michael@0: } else { michael@0: AudioTrackEncoder::InterleaveTrackData(chunk, sourceSamplesToCopy, michael@0: mChannels, michael@0: dst); michael@0: dstSamplesCopied = sourceSamplesToCopy * mChannels; michael@0: } michael@0: } else { michael@0: // Silence. michael@0: memset(dst, 0, mResamplingRatio * sourceSamplesToCopy * sizeof(AudioDataValue)); michael@0: } michael@0: michael@0: sourceSamplesCopied += sourceSamplesToCopy; michael@0: buffer.IncreaseOffset(dstSamplesCopied * sizeof(AudioDataValue)); michael@0: iter.Next(); michael@0: } michael@0: if (sourceSamplesCopied > 0) { michael@0: aSegment.RemoveLeading(sourceSamplesCopied); michael@0: } michael@0: } else if (aInputFlags & BUFFER_EOS) { michael@0: // No audio data left in segment but we still have to feed something to michael@0: // MediaCodec in order to notify EOS. michael@0: size_t bytesToCopy = mChannels * sizeof(AudioDataValue); michael@0: memset(buffer.GetPointer(), 0, bytesToCopy); michael@0: buffer.IncreaseOffset(bytesToCopy); michael@0: sourceSamplesCopied = 1; michael@0: } michael@0: michael@0: if (sourceSamplesCopied > 0) { michael@0: int flags = aInputFlags; michael@0: if (aSegment.GetDuration() > 0) { michael@0: // Don't signal EOS until source segment is empty. michael@0: flags &= ~BUFFER_EOS; michael@0: } michael@0: result = buffer.Enqueue(mTimestamp, flags); michael@0: NS_ENSURE_TRUE(result == OK, NS_ERROR_FAILURE); michael@0: michael@0: mTimestamp += sourceSamplesCopied * mSampleDuration; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Generate decoder config descriptor (defined in ISO/IEC 14496-1 8.3.4.1) for michael@0: // AAC. The hard-coded bytes are copied from michael@0: // MPEG4Writer::Track::writeMp4aEsdsBox() implementation in libstagefright. michael@0: status_t michael@0: OMXAudioEncoder::AppendDecoderConfig(nsTArray* aOutputBuf, michael@0: ABuffer* aData) michael@0: { michael@0: MOZ_ASSERT(aData); michael@0: michael@0: const size_t csdSize = aData->size(); michael@0: michael@0: // See michael@0: // http://wiki.multimedia.cx/index.php?title=Understanding_AAC#Packaging.2FEncapsulation_And_Setup_Data michael@0: // AAC decoder specific descriptor contains 2 bytes. michael@0: NS_ENSURE_TRUE(csdSize == 2, ERROR_MALFORMED); michael@0: // Encoder output must be consistent with kAACFrameDuration: michael@0: // 14th bit (frame length flag) == 0 => 1024 (kAACFrameDuration) samples. michael@0: NS_ENSURE_TRUE((aData->data()[1] & 0x04) == 0, ERROR_MALFORMED); michael@0: michael@0: // Decoder config descriptor michael@0: const uint8_t decConfig[] = { michael@0: 0x04, // Decoder config descriptor tag. michael@0: 15 + csdSize, // Size: following bytes + csd size. michael@0: 0x40, // Object type: MPEG-4 audio. michael@0: 0x15, // Stream type: audio, reserved: 1. michael@0: 0x00, 0x03, 0x00, // Buffer size: 768 (kAACFrameSize). michael@0: 0x00, 0x01, 0x77, 0x00, // Max bitrate: 96000 (kAACBitrate). michael@0: 0x00, 0x01, 0x77, 0x00, // Avg bitrate: 96000 (kAACBitrate). michael@0: 0x05, // Decoder specific descriptor tag. michael@0: csdSize, // Data size. michael@0: }; michael@0: // SL config descriptor. michael@0: const uint8_t slConfig[] = { michael@0: 0x06, // SL config descriptor tag. michael@0: 0x01, // Size. michael@0: 0x02, // Fixed value. michael@0: }; michael@0: michael@0: aOutputBuf->SetCapacity(sizeof(decConfig) + csdSize + sizeof(slConfig)); michael@0: aOutputBuf->AppendElements(decConfig, sizeof(decConfig)); michael@0: aOutputBuf->AppendElements(aData->data(), csdSize); michael@0: aOutputBuf->AppendElements(slConfig, sizeof(slConfig)); michael@0: michael@0: return OK; michael@0: } michael@0: michael@0: nsresult michael@0: OMXCodecWrapper::GetNextEncodedFrame(nsTArray* aOutputBuf, michael@0: int64_t* aOutputTimestamp, michael@0: int* aOutputFlags, int64_t aTimeOut) michael@0: { michael@0: MOZ_ASSERT(mStarted, michael@0: "Configure() should be called before GetNextEncodedFrame()."); michael@0: michael@0: // Dequeue a buffer from output buffers. michael@0: size_t index = 0; michael@0: size_t outOffset = 0; michael@0: size_t outSize = 0; michael@0: int64_t outTimeUs = 0; michael@0: uint32_t outFlags = 0; michael@0: bool retry = false; michael@0: do { michael@0: status_t result = mCodec->dequeueOutputBuffer(&index, &outOffset, &outSize, michael@0: &outTimeUs, &outFlags, michael@0: aTimeOut); michael@0: switch (result) { michael@0: case OK: michael@0: break; michael@0: case INFO_OUTPUT_BUFFERS_CHANGED: michael@0: // Update our references to new buffers. michael@0: result = mCodec->getOutputBuffers(&mOutputBufs); michael@0: // Get output from a new buffer. michael@0: retry = true; michael@0: break; michael@0: case INFO_FORMAT_CHANGED: michael@0: // It's okay: for encoder, MediaCodec reports this only to inform caller michael@0: // that there will be a codec config buffer next. michael@0: return NS_OK; michael@0: case -EAGAIN: michael@0: // Output buffer not available. Caller can try again later. michael@0: return NS_OK; michael@0: default: michael@0: CODEC_ERROR("MediaCodec error:%d", result); michael@0: MOZ_ASSERT(false, "MediaCodec error."); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } while (retry); michael@0: michael@0: if (aOutputBuf) { michael@0: aOutputBuf->Clear(); michael@0: const sp omxBuf = mOutputBufs.itemAt(index); michael@0: if (outFlags & MediaCodec::BUFFER_FLAG_CODECCONFIG) { michael@0: // Codec specific data. michael@0: if (AppendDecoderConfig(aOutputBuf, omxBuf.get()) != OK) { michael@0: mCodec->releaseOutputBuffer(index); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } else if ((mCodecType == AMR_NB_ENC) && !mAMRCSDProvided){ michael@0: // OMX AMR codec won't provide csd data, need to generate a fake one. michael@0: nsRefPtr audiodata = new EncodedFrame(); michael@0: // Decoder config descriptor michael@0: const uint8_t decConfig[] = { michael@0: 0x0, 0x0, 0x0, 0x0, // vendor: 4 bytes michael@0: 0x0, // decoder version michael@0: 0x83, 0xFF, // mode set: all enabled michael@0: 0x00, // mode change period michael@0: 0x01, // frames per sample michael@0: }; michael@0: aOutputBuf->AppendElements(decConfig, sizeof(decConfig)); michael@0: outFlags |= MediaCodec::BUFFER_FLAG_CODECCONFIG; michael@0: mAMRCSDProvided = true; michael@0: } else { michael@0: AppendFrame(aOutputBuf, omxBuf->data(), omxBuf->size()); michael@0: } michael@0: } michael@0: mCodec->releaseOutputBuffer(index); michael@0: michael@0: if (aOutputTimestamp) { michael@0: *aOutputTimestamp = outTimeUs; michael@0: } michael@0: michael@0: if (aOutputFlags) { michael@0: *aOutputFlags = outFlags; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: }