diff options
Diffstat (limited to 'dom/media/platforms/agnostic/TheoraDecoder.cpp')
-rw-r--r-- | dom/media/platforms/agnostic/TheoraDecoder.cpp | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/dom/media/platforms/agnostic/TheoraDecoder.cpp b/dom/media/platforms/agnostic/TheoraDecoder.cpp new file mode 100644 index 0000000000..468eda9014 --- /dev/null +++ b/dom/media/platforms/agnostic/TheoraDecoder.cpp @@ -0,0 +1,267 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "TheoraDecoder.h" + +#include <algorithm> + +#include "ImageContainer.h" +#include "TimeUnits.h" +#include "XiphExtradata.h" +#include "gfx2DGlue.h" +#include "mozilla/PodOperations.h" +#include "mozilla/TaskQueue.h" +#include "nsError.h" +#include "PerformanceRecorder.h" +#include "VideoUtils.h" + +#undef LOG +#define LOG(arg, ...) \ + DDMOZ_LOG(gMediaDecoderLog, mozilla::LogLevel::Debug, "::%s: " arg, \ + __func__, ##__VA_ARGS__) + +namespace mozilla { + +using namespace gfx; +using namespace layers; + +extern LazyLogModule gMediaDecoderLog; + +ogg_packet InitTheoraPacket(const unsigned char* aData, size_t aLength, + bool aBOS, bool aEOS, int64_t aGranulepos, + int64_t aPacketNo) { + ogg_packet packet; + packet.packet = const_cast<unsigned char*>(aData); + packet.bytes = aLength; + packet.b_o_s = aBOS; + packet.e_o_s = aEOS; + packet.granulepos = aGranulepos; + packet.packetno = aPacketNo; + return packet; +} + +TheoraDecoder::TheoraDecoder(const CreateDecoderParams& aParams) + : mImageAllocator(aParams.mKnowsCompositor), + mImageContainer(aParams.mImageContainer), + mTaskQueue(TaskQueue::Create( + GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER), + "TheoraDecoder")), + mTheoraInfo{}, + mTheoraComment{}, + mTheoraSetupInfo(nullptr), + mTheoraDecoderContext(nullptr), + mPacketCount(0), + mInfo(aParams.VideoConfig()), + mTrackingId(aParams.mTrackingId) { + MOZ_COUNT_CTOR(TheoraDecoder); +} + +TheoraDecoder::~TheoraDecoder() { + MOZ_COUNT_DTOR(TheoraDecoder); + th_setup_free(mTheoraSetupInfo); + th_comment_clear(&mTheoraComment); + th_info_clear(&mTheoraInfo); +} + +RefPtr<ShutdownPromise> TheoraDecoder::Shutdown() { + RefPtr<TheoraDecoder> self = this; + return InvokeAsync(mTaskQueue, __func__, [self, this]() { + if (mTheoraDecoderContext) { + th_decode_free(mTheoraDecoderContext); + mTheoraDecoderContext = nullptr; + } + return mTaskQueue->BeginShutdown(); + }); +} + +RefPtr<MediaDataDecoder::InitPromise> TheoraDecoder::Init() { + th_comment_init(&mTheoraComment); + th_info_init(&mTheoraInfo); + + nsTArray<unsigned char*> headers; + nsTArray<size_t> headerLens; + if (!XiphExtradataToHeaders(headers, headerLens, + mInfo.mCodecSpecificConfig->Elements(), + mInfo.mCodecSpecificConfig->Length())) { + return InitPromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Could not get theora header.")), + __func__); + } + for (size_t i = 0; i < headers.Length(); i++) { + if (NS_FAILED(DoDecodeHeader(headers[i], headerLens[i]))) { + return InitPromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Could not decode theora header.")), + __func__); + } + } + if (mPacketCount != 3) { + return InitPromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Packet count is wrong.")), + __func__); + } + + mTheoraDecoderContext = th_decode_alloc(&mTheoraInfo, mTheoraSetupInfo); + if (mTheoraDecoderContext) { + return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__); + } else { + return InitPromise::CreateAndReject( + MediaResult(NS_ERROR_OUT_OF_MEMORY, + RESULT_DETAIL("Could not allocate theora decoder.")), + __func__); + } +} + +RefPtr<MediaDataDecoder::FlushPromise> TheoraDecoder::Flush() { + return InvokeAsync(mTaskQueue, __func__, []() { + return FlushPromise::CreateAndResolve(true, __func__); + }); +} + +nsresult TheoraDecoder::DoDecodeHeader(const unsigned char* aData, + size_t aLength) { + bool bos = mPacketCount == 0; + ogg_packet pkt = + InitTheoraPacket(aData, aLength, bos, false, 0, mPacketCount++); + + int r = th_decode_headerin(&mTheoraInfo, &mTheoraComment, &mTheoraSetupInfo, + &pkt); + return r > 0 ? NS_OK : NS_ERROR_FAILURE; +} + +RefPtr<MediaDataDecoder::DecodePromise> TheoraDecoder::ProcessDecode( + MediaRawData* aSample) { + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + + MediaInfoFlag flag = MediaInfoFlag::None; + flag |= (aSample->mKeyframe ? MediaInfoFlag::KeyFrame + : MediaInfoFlag::NonKeyFrame); + flag |= MediaInfoFlag::SoftwareDecoding; + flag |= MediaInfoFlag::VIDEO_THEORA; + Maybe<PerformanceRecorder<DecodeStage>> rec = + mTrackingId.map([&](const auto& aId) { + return PerformanceRecorder<DecodeStage>("TheoraDecoder"_ns, aId, flag); + }); + + const unsigned char* aData = aSample->Data(); + size_t aLength = aSample->Size(); + + bool bos = mPacketCount == 0; + ogg_packet pkt = + InitTheoraPacket(aData, aLength, bos, false, + aSample->mTimecode.ToMicroseconds(), mPacketCount++); + + int ret = th_decode_packetin(mTheoraDecoderContext, &pkt, nullptr); + if (ret == 0 || ret == TH_DUPFRAME) { + th_ycbcr_buffer ycbcr; + th_decode_ycbcr_out(mTheoraDecoderContext, ycbcr); + + int hdec = !(mTheoraInfo.pixel_fmt & 1); + int vdec = !(mTheoraInfo.pixel_fmt & 2); + + VideoData::YCbCrBuffer b; + b.mPlanes[0].mData = ycbcr[0].data; + b.mPlanes[0].mStride = ycbcr[0].stride; + b.mPlanes[0].mHeight = mTheoraInfo.frame_height; + b.mPlanes[0].mWidth = mTheoraInfo.frame_width; + b.mPlanes[0].mSkip = 0; + + b.mPlanes[1].mData = ycbcr[1].data; + b.mPlanes[1].mStride = ycbcr[1].stride; + b.mPlanes[1].mHeight = mTheoraInfo.frame_height >> vdec; + b.mPlanes[1].mWidth = mTheoraInfo.frame_width >> hdec; + b.mPlanes[1].mSkip = 0; + + b.mPlanes[2].mData = ycbcr[2].data; + b.mPlanes[2].mStride = ycbcr[2].stride; + b.mPlanes[2].mHeight = mTheoraInfo.frame_height >> vdec; + b.mPlanes[2].mWidth = mTheoraInfo.frame_width >> hdec; + b.mPlanes[2].mSkip = 0; + + if (vdec) { + b.mChromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT; + } else if (hdec) { + b.mChromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH; + } + + b.mYUVColorSpace = + DefaultColorSpace({mTheoraInfo.frame_width, mTheoraInfo.frame_height}); + + IntRect pictureArea(mTheoraInfo.pic_x, mTheoraInfo.pic_y, + mTheoraInfo.pic_width, mTheoraInfo.pic_height); + + VideoInfo info; + info.mDisplay = mInfo.mDisplay; + RefPtr<VideoData> v = VideoData::CreateAndCopyData( + info, mImageContainer, aSample->mOffset, aSample->mTime, + aSample->mDuration, b, aSample->mKeyframe, aSample->mTimecode, + mInfo.ScaledImageRect(mTheoraInfo.frame_width, + mTheoraInfo.frame_height), + mImageAllocator); + if (!v) { + LOG("Image allocation error source %ux%u display %ux%u picture %ux%u", + mTheoraInfo.frame_width, mTheoraInfo.frame_height, + mInfo.mDisplay.width, mInfo.mDisplay.height, mInfo.mImage.width, + mInfo.mImage.height); + return DecodePromise::CreateAndReject( + MediaResult(NS_ERROR_OUT_OF_MEMORY, + RESULT_DETAIL("Insufficient memory")), + __func__); + } + + rec.apply([&](auto& aRec) { + aRec.Record([&](DecodeStage& aStage) { + aStage.SetResolution(static_cast<int>(mTheoraInfo.frame_width), + static_cast<int>(mTheoraInfo.frame_height)); + auto format = [&]() -> Maybe<DecodeStage::ImageFormat> { + switch (mTheoraInfo.pixel_fmt) { + case TH_PF_420: + return Some(DecodeStage::YUV420P); + case TH_PF_422: + return Some(DecodeStage::YUV422P); + case TH_PF_444: + return Some(DecodeStage::YUV444P); + default: + return Nothing(); + } + }(); + format.apply([&](auto& aFmt) { aStage.SetImageFormat(aFmt); }); + aStage.SetYUVColorSpace(b.mYUVColorSpace); + aStage.SetColorRange(b.mColorRange); + aStage.SetColorDepth(b.mColorDepth); + }); + }); + + return DecodePromise::CreateAndResolve(DecodedData{v}, __func__); + } + LOG("Theora Decode error: %d", ret); + return DecodePromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + RESULT_DETAIL("Theora decode error:%d", ret)), + __func__); +} + +RefPtr<MediaDataDecoder::DecodePromise> TheoraDecoder::Decode( + MediaRawData* aSample) { + return InvokeAsync<MediaRawData*>(mTaskQueue, this, __func__, + &TheoraDecoder::ProcessDecode, aSample); +} + +RefPtr<MediaDataDecoder::DecodePromise> TheoraDecoder::Drain() { + return InvokeAsync(mTaskQueue, __func__, [] { + return DecodePromise::CreateAndResolve(DecodedData(), __func__); + }); +} + +/* static */ +bool TheoraDecoder::IsTheora(const nsACString& aMimeType) { + return aMimeType.EqualsLiteral("video/theora"); +} + +} // namespace mozilla +#undef LOG |