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 michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "MediaDecoderStateMachine.h" michael@0: #include "AbstractMediaDecoder.h" michael@0: #include "RawReader.h" michael@0: #include "RawDecoder.h" michael@0: #include "VideoUtils.h" michael@0: #include "nsISeekableStream.h" michael@0: #include "gfx2DGlue.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: RawReader::RawReader(AbstractMediaDecoder* aDecoder) michael@0: : MediaDecoderReader(aDecoder), michael@0: mCurrentFrame(0), mFrameSize(0) michael@0: { michael@0: MOZ_COUNT_CTOR(RawReader); michael@0: } michael@0: michael@0: RawReader::~RawReader() michael@0: { michael@0: MOZ_COUNT_DTOR(RawReader); michael@0: } michael@0: michael@0: nsresult RawReader::Init(MediaDecoderReader* aCloneDonor) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult RawReader::ResetDecode() michael@0: { michael@0: mCurrentFrame = 0; michael@0: return MediaDecoderReader::ResetDecode(); michael@0: } michael@0: michael@0: nsresult RawReader::ReadMetadata(MediaInfo* aInfo, michael@0: MetadataTags** aTags) michael@0: { michael@0: NS_ASSERTION(mDecoder->OnDecodeThread(), michael@0: "Should be on decode thread."); michael@0: michael@0: MediaResource* resource = mDecoder->GetResource(); michael@0: NS_ASSERTION(resource, "Decoder has no media resource"); michael@0: michael@0: if (!ReadFromResource(resource, reinterpret_cast(&mMetadata), michael@0: sizeof(mMetadata))) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Validate the header michael@0: if (!(mMetadata.headerPacketID == 0 /* Packet ID of 0 for the header*/ && michael@0: mMetadata.codecID == RAW_ID /* "YUV" */ && michael@0: mMetadata.majorVersion == 0 && michael@0: mMetadata.minorVersion == 1)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: CheckedUint32 dummy = CheckedUint32(static_cast(mMetadata.frameWidth)) * michael@0: static_cast(mMetadata.frameHeight); michael@0: NS_ENSURE_TRUE(dummy.isValid(), NS_ERROR_FAILURE); michael@0: michael@0: if (mMetadata.aspectDenominator == 0 || michael@0: mMetadata.framerateDenominator == 0) michael@0: return NS_ERROR_FAILURE; // Invalid data michael@0: michael@0: // Determine and verify frame display size. michael@0: float pixelAspectRatio = static_cast(mMetadata.aspectNumerator) / michael@0: mMetadata.aspectDenominator; michael@0: nsIntSize display(mMetadata.frameWidth, mMetadata.frameHeight); michael@0: ScaleDisplayByAspectRatio(display, pixelAspectRatio); michael@0: mPicture = nsIntRect(0, 0, mMetadata.frameWidth, mMetadata.frameHeight); michael@0: nsIntSize frameSize(mMetadata.frameWidth, mMetadata.frameHeight); michael@0: if (!IsValidVideoRegion(frameSize, mPicture, display)) { michael@0: // Video track's frame sizes will overflow. Fail. michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mInfo.mVideo.mHasVideo = true; michael@0: mInfo.mVideo.mDisplay = display; michael@0: michael@0: mFrameRate = static_cast(mMetadata.framerateNumerator) / michael@0: mMetadata.framerateDenominator; michael@0: michael@0: // Make some sanity checks michael@0: if (mFrameRate > 45 || michael@0: mFrameRate == 0 || michael@0: pixelAspectRatio == 0 || michael@0: mMetadata.frameWidth > 2000 || michael@0: mMetadata.frameHeight > 2000 || michael@0: mMetadata.chromaChannelBpp != 4 || michael@0: mMetadata.lumaChannelBpp != 8 || michael@0: mMetadata.colorspace != 1 /* 4:2:0 */) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: mFrameSize = mMetadata.frameWidth * mMetadata.frameHeight * michael@0: (mMetadata.lumaChannelBpp + mMetadata.chromaChannelBpp) / 8.0 + michael@0: sizeof(RawPacketHeader); michael@0: michael@0: int64_t length = resource->GetLength(); michael@0: if (length != -1) { michael@0: ReentrantMonitorAutoEnter autoMonitor(mDecoder->GetReentrantMonitor()); michael@0: mDecoder->SetMediaDuration(USECS_PER_S * michael@0: (length - sizeof(RawVideoHeader)) / michael@0: (mFrameSize * mFrameRate)); michael@0: } michael@0: michael@0: *aInfo = mInfo; michael@0: michael@0: *aTags = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool RawReader::DecodeAudioData() michael@0: { michael@0: NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(), michael@0: "Should be on state machine thread or decode thread."); michael@0: return false; michael@0: } michael@0: michael@0: // Helper method that either reads until it gets aLength bytes michael@0: // or returns false michael@0: bool RawReader::ReadFromResource(MediaResource *aResource, uint8_t* aBuf, michael@0: uint32_t aLength) michael@0: { michael@0: while (aLength > 0) { michael@0: uint32_t bytesRead = 0; michael@0: nsresult rv; michael@0: michael@0: rv = aResource->Read(reinterpret_cast(aBuf), aLength, &bytesRead); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: if (bytesRead == 0) { michael@0: return false; michael@0: } michael@0: michael@0: aLength -= bytesRead; michael@0: aBuf += bytesRead; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool RawReader::DecodeVideoFrame(bool &aKeyframeSkip, michael@0: int64_t aTimeThreshold) michael@0: { michael@0: NS_ASSERTION(mDecoder->OnDecodeThread(), michael@0: "Should be on decode thread."); michael@0: michael@0: // Record number of frames decoded and parsed. Automatically update the michael@0: // stats counters using the AutoNotifyDecoded stack-based class. michael@0: uint32_t parsed = 0, decoded = 0; michael@0: AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded); michael@0: michael@0: if (!mFrameSize) michael@0: return false; // Metadata read failed. We should refuse to play. michael@0: michael@0: int64_t currentFrameTime = USECS_PER_S * mCurrentFrame / mFrameRate; michael@0: uint32_t length = mFrameSize - sizeof(RawPacketHeader); michael@0: michael@0: nsAutoArrayPtr buffer(new uint8_t[length]); michael@0: MediaResource* resource = mDecoder->GetResource(); michael@0: NS_ASSERTION(resource, "Decoder has no media resource"); michael@0: michael@0: // We're always decoding one frame when called michael@0: while(true) { michael@0: RawPacketHeader header; michael@0: michael@0: // Read in a packet header and validate michael@0: if (!(ReadFromResource(resource, reinterpret_cast(&header), michael@0: sizeof(header))) || michael@0: !(header.packetID == 0xFF && header.codecID == RAW_ID /* "YUV" */)) { michael@0: return false; michael@0: } michael@0: michael@0: if (!ReadFromResource(resource, buffer, length)) { michael@0: return false; michael@0: } michael@0: michael@0: parsed++; michael@0: michael@0: if (currentFrameTime >= aTimeThreshold) michael@0: break; michael@0: michael@0: mCurrentFrame++; michael@0: currentFrameTime += static_cast(USECS_PER_S) / mFrameRate; michael@0: } michael@0: michael@0: VideoData::YCbCrBuffer b; michael@0: b.mPlanes[0].mData = buffer; michael@0: b.mPlanes[0].mStride = mMetadata.frameWidth * mMetadata.lumaChannelBpp / 8.0; michael@0: b.mPlanes[0].mHeight = mMetadata.frameHeight; michael@0: b.mPlanes[0].mWidth = mMetadata.frameWidth; michael@0: b.mPlanes[0].mOffset = b.mPlanes[0].mSkip = 0; michael@0: michael@0: uint32_t cbcrStride = mMetadata.frameWidth * mMetadata.chromaChannelBpp / 8.0; michael@0: michael@0: b.mPlanes[1].mData = buffer + mMetadata.frameHeight * b.mPlanes[0].mStride; michael@0: b.mPlanes[1].mStride = cbcrStride; michael@0: b.mPlanes[1].mHeight = mMetadata.frameHeight / 2; michael@0: b.mPlanes[1].mWidth = mMetadata.frameWidth / 2; michael@0: b.mPlanes[1].mOffset = b.mPlanes[1].mSkip = 0; michael@0: michael@0: b.mPlanes[2].mData = b.mPlanes[1].mData + mMetadata.frameHeight * cbcrStride / 2; michael@0: b.mPlanes[2].mStride = cbcrStride; michael@0: b.mPlanes[2].mHeight = mMetadata.frameHeight / 2; michael@0: b.mPlanes[2].mWidth = mMetadata.frameWidth / 2; michael@0: b.mPlanes[2].mOffset = b.mPlanes[2].mSkip = 0; michael@0: michael@0: VideoData *v = VideoData::Create(mInfo.mVideo, michael@0: mDecoder->GetImageContainer(), michael@0: -1, michael@0: currentFrameTime, michael@0: (USECS_PER_S / mFrameRate), michael@0: b, michael@0: 1, // In raw video every frame is a keyframe michael@0: -1, michael@0: ToIntRect(mPicture)); michael@0: if (!v) michael@0: return false; michael@0: michael@0: mVideoQueue.Push(v); michael@0: mCurrentFrame++; michael@0: decoded++; michael@0: currentFrameTime += USECS_PER_S / mFrameRate; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsresult RawReader::Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) michael@0: { michael@0: NS_ASSERTION(mDecoder->OnDecodeThread(), michael@0: "Should be on decode thread."); michael@0: michael@0: MediaResource *resource = mDecoder->GetResource(); michael@0: NS_ASSERTION(resource, "Decoder has no media resource"); michael@0: michael@0: uint32_t frame = mCurrentFrame; michael@0: if (aTime >= UINT_MAX) michael@0: return NS_ERROR_FAILURE; michael@0: mCurrentFrame = aTime * mFrameRate / USECS_PER_S; michael@0: michael@0: CheckedUint32 offset = CheckedUint32(mCurrentFrame) * mFrameSize; michael@0: offset += sizeof(RawVideoHeader); michael@0: NS_ENSURE_TRUE(offset.isValid(), NS_ERROR_FAILURE); michael@0: michael@0: nsresult rv = resource->Seek(nsISeekableStream::NS_SEEK_SET, offset.value()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mVideoQueue.Reset(); michael@0: michael@0: while(mVideoQueue.GetSize() == 0) { michael@0: bool keyframeSkip = false; michael@0: if (!DecodeVideoFrame(keyframeSkip, 0)) { michael@0: mCurrentFrame = frame; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: { michael@0: ReentrantMonitorAutoEnter autoMonitor(mDecoder->GetReentrantMonitor()); michael@0: if (mDecoder->IsShutdown()) { michael@0: mCurrentFrame = frame; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: nsAutoPtr video(mVideoQueue.PeekFront()); michael@0: if (video && video->GetEndTime() < aTime) { michael@0: mVideoQueue.PopFront(); michael@0: video = nullptr; michael@0: } else { michael@0: video.forget(); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult RawReader::GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime) michael@0: { michael@0: return NS_OK; michael@0: }