michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 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 michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #include "MediaData.h" michael@0: #include "MediaInfo.h" michael@0: #ifdef MOZ_OMX_DECODER michael@0: #include "GrallocImages.h" michael@0: #include "mozilla/layers/TextureClient.h" michael@0: #endif michael@0: #include "VideoUtils.h" michael@0: #include "ImageContainer.h" michael@0: michael@0: #ifdef MOZ_WIDGET_GONK michael@0: #include michael@0: #endif michael@0: michael@0: namespace mozilla { michael@0: michael@0: using namespace mozilla::gfx; michael@0: using layers::ImageContainer; michael@0: using layers::PlanarYCbCrImage; michael@0: using layers::PlanarYCbCrData; michael@0: michael@0: void michael@0: AudioData::EnsureAudioBuffer() michael@0: { michael@0: if (mAudioBuffer) michael@0: return; michael@0: mAudioBuffer = SharedBuffer::Create(mFrames*mChannels*sizeof(AudioDataValue)); michael@0: michael@0: AudioDataValue* data = static_cast(mAudioBuffer->Data()); michael@0: for (uint32_t i = 0; i < mFrames; ++i) { michael@0: for (uint32_t j = 0; j < mChannels; ++j) { michael@0: data[j*mFrames + i] = mAudioData[i*mChannels + j]; michael@0: } michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: ValidatePlane(const VideoData::YCbCrBuffer::Plane& aPlane) michael@0: { michael@0: return aPlane.mWidth <= PlanarYCbCrImage::MAX_DIMENSION && michael@0: aPlane.mHeight <= PlanarYCbCrImage::MAX_DIMENSION && michael@0: aPlane.mWidth * aPlane.mHeight < MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT && michael@0: aPlane.mStride > 0; michael@0: } michael@0: michael@0: #ifdef MOZ_WIDGET_GONK michael@0: static bool michael@0: IsYV12Format(const VideoData::YCbCrBuffer::Plane& aYPlane, michael@0: const VideoData::YCbCrBuffer::Plane& aCbPlane, michael@0: const VideoData::YCbCrBuffer::Plane& aCrPlane) michael@0: { michael@0: return michael@0: aYPlane.mWidth % 2 == 0 && michael@0: aYPlane.mHeight % 2 == 0 && michael@0: aYPlane.mWidth / 2 == aCbPlane.mWidth && michael@0: aYPlane.mHeight / 2 == aCbPlane.mHeight && michael@0: aCbPlane.mWidth == aCrPlane.mWidth && michael@0: aCbPlane.mHeight == aCrPlane.mHeight; michael@0: } michael@0: michael@0: static bool michael@0: IsInEmulator() michael@0: { michael@0: char propQemu[PROPERTY_VALUE_MAX]; michael@0: property_get("ro.kernel.qemu", propQemu, ""); michael@0: return !strncmp(propQemu, "1", 1); michael@0: } michael@0: michael@0: #endif michael@0: michael@0: VideoData::VideoData(int64_t aOffset, int64_t aTime, int64_t aDuration, int64_t aTimecode) michael@0: : MediaData(VIDEO_FRAME, aOffset, aTime, aDuration), michael@0: mTimecode(aTimecode), michael@0: mDuplicate(true), michael@0: mKeyframe(false) michael@0: { michael@0: MOZ_COUNT_CTOR(VideoData); michael@0: NS_ASSERTION(mDuration >= 0, "Frame must have non-negative duration."); michael@0: } michael@0: michael@0: VideoData::VideoData(int64_t aOffset, michael@0: int64_t aTime, michael@0: int64_t aDuration, michael@0: bool aKeyframe, michael@0: int64_t aTimecode, michael@0: IntSize aDisplay) michael@0: : MediaData(VIDEO_FRAME, aOffset, aTime, aDuration), michael@0: mDisplay(aDisplay), michael@0: mTimecode(aTimecode), michael@0: mDuplicate(false), michael@0: mKeyframe(aKeyframe) michael@0: { michael@0: MOZ_COUNT_CTOR(VideoData); michael@0: NS_ASSERTION(mDuration >= 0, "Frame must have non-negative duration."); michael@0: } michael@0: michael@0: VideoData::~VideoData() michael@0: { michael@0: MOZ_COUNT_DTOR(VideoData); michael@0: } michael@0: michael@0: size_t michael@0: VideoData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t size = aMallocSizeOf(this); michael@0: michael@0: // Currently only PLANAR_YCBCR has a well defined function for determining michael@0: // it's size, so reporting is limited to that type. michael@0: if (mImage && mImage->GetFormat() == ImageFormat::PLANAR_YCBCR) { michael@0: const mozilla::layers::PlanarYCbCrImage* img = michael@0: static_cast(mImage.get()); michael@0: size += img->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: return size; michael@0: } michael@0: michael@0: /* static */ michael@0: VideoData* VideoData::ShallowCopyUpdateDuration(VideoData* aOther, michael@0: int64_t aDuration) michael@0: { michael@0: VideoData* v = new VideoData(aOther->mOffset, michael@0: aOther->mTime, michael@0: aDuration, michael@0: aOther->mKeyframe, michael@0: aOther->mTimecode, michael@0: aOther->mDisplay); michael@0: v->mImage = aOther->mImage; michael@0: return v; michael@0: } michael@0: michael@0: /* static */ michael@0: VideoData* VideoData::ShallowCopyUpdateTimestamp(VideoData* aOther, michael@0: int64_t aTimestamp) michael@0: { michael@0: NS_ENSURE_TRUE(aOther, nullptr); michael@0: VideoData* v = new VideoData(aOther->mOffset, michael@0: aTimestamp, michael@0: aOther->GetEndTime() - aTimestamp, michael@0: aOther->mKeyframe, michael@0: aOther->mTimecode, michael@0: aOther->mDisplay); michael@0: v->mImage = aOther->mImage; michael@0: return v; michael@0: } michael@0: michael@0: /* static */ michael@0: void VideoData::SetVideoDataToImage(PlanarYCbCrImage* aVideoImage, michael@0: VideoInfo& aInfo, michael@0: const YCbCrBuffer &aBuffer, michael@0: const IntRect& aPicture, michael@0: bool aCopyData) michael@0: { michael@0: if (!aVideoImage) { michael@0: return; michael@0: } michael@0: const YCbCrBuffer::Plane &Y = aBuffer.mPlanes[0]; michael@0: const YCbCrBuffer::Plane &Cb = aBuffer.mPlanes[1]; michael@0: const YCbCrBuffer::Plane &Cr = aBuffer.mPlanes[2]; michael@0: michael@0: PlanarYCbCrData data; michael@0: data.mYChannel = Y.mData + Y.mOffset; michael@0: data.mYSize = IntSize(Y.mWidth, Y.mHeight); michael@0: data.mYStride = Y.mStride; michael@0: data.mYSkip = Y.mSkip; michael@0: data.mCbChannel = Cb.mData + Cb.mOffset; michael@0: data.mCrChannel = Cr.mData + Cr.mOffset; michael@0: data.mCbCrSize = IntSize(Cb.mWidth, Cb.mHeight); michael@0: data.mCbCrStride = Cb.mStride; michael@0: data.mCbSkip = Cb.mSkip; michael@0: data.mCrSkip = Cr.mSkip; michael@0: data.mPicX = aPicture.x; michael@0: data.mPicY = aPicture.y; michael@0: data.mPicSize = aPicture.Size(); michael@0: data.mStereoMode = aInfo.mStereoMode; michael@0: michael@0: aVideoImage->SetDelayedConversion(true); michael@0: if (aCopyData) { michael@0: aVideoImage->SetData(data); michael@0: } else { michael@0: aVideoImage->SetDataNoCopy(data); michael@0: } michael@0: } michael@0: michael@0: /* static */ michael@0: VideoData* VideoData::Create(VideoInfo& aInfo, michael@0: ImageContainer* aContainer, michael@0: Image* aImage, michael@0: int64_t aOffset, michael@0: int64_t aTime, michael@0: int64_t aDuration, michael@0: const YCbCrBuffer& aBuffer, michael@0: bool aKeyframe, michael@0: int64_t aTimecode, michael@0: const IntRect& aPicture) michael@0: { michael@0: if (!aImage && !aContainer) { michael@0: // Create a dummy VideoData with no image. This gives us something to michael@0: // send to media streams if necessary. michael@0: nsAutoPtr v(new VideoData(aOffset, michael@0: aTime, michael@0: aDuration, michael@0: aKeyframe, michael@0: aTimecode, michael@0: aInfo.mDisplay.ToIntSize())); michael@0: return v.forget(); michael@0: } michael@0: michael@0: // The following situation should never happen unless there is a bug michael@0: // in the decoder michael@0: if (aBuffer.mPlanes[1].mWidth != aBuffer.mPlanes[2].mWidth || michael@0: aBuffer.mPlanes[1].mHeight != aBuffer.mPlanes[2].mHeight) { michael@0: NS_ERROR("C planes with different sizes"); michael@0: return nullptr; michael@0: } michael@0: michael@0: // The following situations could be triggered by invalid input michael@0: if (aPicture.width <= 0 || aPicture.height <= 0) { michael@0: NS_WARNING("Empty picture rect"); michael@0: return nullptr; michael@0: } michael@0: if (!ValidatePlane(aBuffer.mPlanes[0]) || !ValidatePlane(aBuffer.mPlanes[1]) || michael@0: !ValidatePlane(aBuffer.mPlanes[2])) { michael@0: NS_WARNING("Invalid plane size"); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Ensure the picture size specified in the headers can be extracted out of michael@0: // the frame we've been supplied without indexing out of bounds. michael@0: CheckedUint32 xLimit = aPicture.x + CheckedUint32(aPicture.width); michael@0: CheckedUint32 yLimit = aPicture.y + CheckedUint32(aPicture.height); michael@0: if (!xLimit.isValid() || xLimit.value() > aBuffer.mPlanes[0].mStride || michael@0: !yLimit.isValid() || yLimit.value() > aBuffer.mPlanes[0].mHeight) michael@0: { michael@0: // The specified picture dimensions can't be contained inside the video michael@0: // frame, we'll stomp memory if we try to copy it. Fail. michael@0: NS_WARNING("Overflowing picture rect"); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsAutoPtr v(new VideoData(aOffset, michael@0: aTime, michael@0: aDuration, michael@0: aKeyframe, michael@0: aTimecode, michael@0: aInfo.mDisplay.ToIntSize())); michael@0: #ifdef MOZ_WIDGET_GONK michael@0: const YCbCrBuffer::Plane &Y = aBuffer.mPlanes[0]; michael@0: const YCbCrBuffer::Plane &Cb = aBuffer.mPlanes[1]; michael@0: const YCbCrBuffer::Plane &Cr = aBuffer.mPlanes[2]; michael@0: #endif michael@0: michael@0: if (!aImage) { michael@0: // Currently our decoder only knows how to output to ImageFormat::PLANAR_YCBCR michael@0: // format. michael@0: #ifdef MOZ_WIDGET_GONK michael@0: if (IsYV12Format(Y, Cb, Cr) && !IsInEmulator()) { michael@0: v->mImage = aContainer->CreateImage(ImageFormat::GRALLOC_PLANAR_YCBCR); michael@0: } michael@0: #endif michael@0: if (!v->mImage) { michael@0: v->mImage = aContainer->CreateImage(ImageFormat::PLANAR_YCBCR); michael@0: } michael@0: } else { michael@0: v->mImage = aImage; michael@0: } michael@0: michael@0: if (!v->mImage) { michael@0: return nullptr; michael@0: } michael@0: NS_ASSERTION(v->mImage->GetFormat() == ImageFormat::PLANAR_YCBCR || michael@0: v->mImage->GetFormat() == ImageFormat::GRALLOC_PLANAR_YCBCR, michael@0: "Wrong format?"); michael@0: PlanarYCbCrImage* videoImage = static_cast(v->mImage.get()); michael@0: michael@0: if (!aImage) { michael@0: VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture, michael@0: true /* aCopyData */); michael@0: } else { michael@0: VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture, michael@0: false /* aCopyData */); michael@0: } michael@0: michael@0: #ifdef MOZ_WIDGET_GONK michael@0: if (!videoImage->IsValid() && !aImage && IsYV12Format(Y, Cb, Cr)) { michael@0: // Failed to allocate gralloc. Try fallback. michael@0: v->mImage = aContainer->CreateImage(ImageFormat::PLANAR_YCBCR); michael@0: if (!v->mImage) { michael@0: return nullptr; michael@0: } michael@0: videoImage = static_cast(v->mImage.get()); michael@0: VideoData::SetVideoDataToImage(videoImage, aInfo, aBuffer, aPicture, michael@0: true /* aCopyData */); michael@0: } michael@0: #endif michael@0: return v.forget(); michael@0: } michael@0: michael@0: /* static */ michael@0: VideoData* VideoData::Create(VideoInfo& aInfo, michael@0: ImageContainer* aContainer, michael@0: int64_t aOffset, michael@0: int64_t aTime, michael@0: int64_t aDuration, michael@0: const YCbCrBuffer& aBuffer, michael@0: bool aKeyframe, michael@0: int64_t aTimecode, michael@0: const IntRect& aPicture) michael@0: { michael@0: return Create(aInfo, aContainer, nullptr, aOffset, aTime, aDuration, aBuffer, michael@0: aKeyframe, aTimecode, aPicture); michael@0: } michael@0: michael@0: /* static */ michael@0: VideoData* VideoData::Create(VideoInfo& aInfo, michael@0: Image* aImage, michael@0: int64_t aOffset, michael@0: int64_t aTime, michael@0: int64_t aDuration, michael@0: const YCbCrBuffer& aBuffer, michael@0: bool aKeyframe, michael@0: int64_t aTimecode, michael@0: const IntRect& aPicture) michael@0: { michael@0: return Create(aInfo, nullptr, aImage, aOffset, aTime, aDuration, aBuffer, michael@0: aKeyframe, aTimecode, aPicture); michael@0: } michael@0: michael@0: /* static */ michael@0: VideoData* VideoData::CreateFromImage(VideoInfo& aInfo, michael@0: ImageContainer* aContainer, michael@0: int64_t aOffset, michael@0: int64_t aTime, michael@0: int64_t aDuration, michael@0: const nsRefPtr& aImage, michael@0: bool aKeyframe, michael@0: int64_t aTimecode, michael@0: const IntRect& aPicture) michael@0: { michael@0: nsAutoPtr v(new VideoData(aOffset, michael@0: aTime, michael@0: aDuration, michael@0: aKeyframe, michael@0: aTimecode, michael@0: aInfo.mDisplay.ToIntSize())); michael@0: v->mImage = aImage; michael@0: return v.forget(); michael@0: } michael@0: michael@0: #ifdef MOZ_OMX_DECODER michael@0: /* static */ michael@0: VideoData* VideoData::Create(VideoInfo& aInfo, michael@0: ImageContainer* aContainer, michael@0: int64_t aOffset, michael@0: int64_t aTime, michael@0: int64_t aDuration, michael@0: mozilla::layers::TextureClient* aBuffer, michael@0: bool aKeyframe, michael@0: int64_t aTimecode, michael@0: const IntRect& aPicture) michael@0: { michael@0: if (!aContainer) { michael@0: // Create a dummy VideoData with no image. This gives us something to michael@0: // send to media streams if necessary. michael@0: nsAutoPtr v(new VideoData(aOffset, michael@0: aTime, michael@0: aDuration, michael@0: aKeyframe, michael@0: aTimecode, michael@0: aInfo.mDisplay.ToIntSize())); michael@0: return v.forget(); michael@0: } michael@0: michael@0: // The following situations could be triggered by invalid input michael@0: if (aPicture.width <= 0 || aPicture.height <= 0) { michael@0: NS_WARNING("Empty picture rect"); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Ensure the picture size specified in the headers can be extracted out of michael@0: // the frame we've been supplied without indexing out of bounds. michael@0: CheckedUint32 xLimit = aPicture.x + CheckedUint32(aPicture.width); michael@0: CheckedUint32 yLimit = aPicture.y + CheckedUint32(aPicture.height); michael@0: if (!xLimit.isValid() || !yLimit.isValid()) michael@0: { michael@0: // The specified picture dimensions can't be contained inside the video michael@0: // frame, we'll stomp memory if we try to copy it. Fail. michael@0: NS_WARNING("Overflowing picture rect"); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsAutoPtr v(new VideoData(aOffset, michael@0: aTime, michael@0: aDuration, michael@0: aKeyframe, michael@0: aTimecode, michael@0: aInfo.mDisplay.ToIntSize())); michael@0: michael@0: v->mImage = aContainer->CreateImage(ImageFormat::GRALLOC_PLANAR_YCBCR); michael@0: if (!v->mImage) { michael@0: return nullptr; michael@0: } michael@0: NS_ASSERTION(v->mImage->GetFormat() == ImageFormat::GRALLOC_PLANAR_YCBCR, michael@0: "Wrong format?"); michael@0: typedef mozilla::layers::GrallocImage GrallocImage; michael@0: GrallocImage* videoImage = static_cast(v->mImage.get()); michael@0: GrallocImage::GrallocData data; michael@0: michael@0: data.mPicSize = aPicture.Size(); michael@0: data.mGraphicBuffer = aBuffer; michael@0: michael@0: videoImage->SetData(data); michael@0: michael@0: return v.forget(); michael@0: } michael@0: #endif // MOZ_OMX_DECODER michael@0: michael@0: } // namespace mozilla