Bug 1168674: [ogg] P3. Add theora MediaDataDecoder. r?jya
MozReview-Commit-ID: 7ZJD21JMXBY
--- a/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
@@ -5,40 +5,44 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AgnosticDecoderModule.h"
#include "mozilla/Logging.h"
#include "OpusDecoder.h"
#include "VorbisDecoder.h"
#include "VPXDecoder.h"
#include "WAVDecoder.h"
+#include "TheoraDecoder.h"
namespace mozilla {
bool
AgnosticDecoderModule::SupportsMimeType(const nsACString& aMimeType,
DecoderDoctorDiagnostics* aDiagnostics) const
{
bool supports =
VPXDecoder::IsVPX(aMimeType) ||
OpusDataDecoder::IsOpus(aMimeType) ||
VorbisDataDecoder::IsVorbis(aMimeType) ||
- WaveDataDecoder::IsWave(aMimeType);
+ WaveDataDecoder::IsWave(aMimeType) ||
+ TheoraDecoder::IsTheora(aMimeType);
MOZ_LOG(sPDMLog, LogLevel::Debug, ("Agnostic decoder %s requested type",
supports ? "supports" : "rejects"));
return supports;
}
already_AddRefed<MediaDataDecoder>
AgnosticDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
{
RefPtr<MediaDataDecoder> m;
if (VPXDecoder::IsVPX(aParams.mConfig.mMimeType)) {
m = new VPXDecoder(aParams);
+ } else if (TheoraDecoder::IsTheora(aParams.mConfig.mMimeType)) {
+ m = new TheoraDecoder(aParams);
}
return m.forget();
}
already_AddRefed<MediaDataDecoder>
AgnosticDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
{
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/agnostic/TheoraDecoder.cpp
@@ -0,0 +1,243 @@
+/* -*- 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 "XiphExtradata.h"
+#include "gfx2DGlue.h"
+#include "nsError.h"
+#include "TimeUnits.h"
+#include "mozilla/PodOperations.h"
+
+#include <algorithm>
+
+#undef LOG
+#define LOG(arg, ...) MOZ_LOG(gMediaDecoderLog, mozilla::LogLevel::Debug, ("TheoraDecoder(%p)::%s: " arg, this, __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)
+ : mImageContainer(aParams.mImageContainer)
+ , mTaskQueue(aParams.mTaskQueue)
+ , mCallback(aParams.mCallback)
+ , mIsFlushing(false)
+ , mTheoraSetupInfo(nullptr)
+ , mTheoraDecoderContext(nullptr)
+ , mPacketCount(0)
+ , mInfo(aParams.VideoConfig())
+{
+ MOZ_COUNT_CTOR(TheoraDecoder);
+}
+
+TheoraDecoder::~TheoraDecoder()
+{
+ MOZ_COUNT_DTOR(TheoraDecoder);
+ th_setup_free(mTheoraSetupInfo);
+ th_comment_clear(&mTheoraComment);
+ th_info_clear(&mTheoraInfo);
+}
+
+nsresult
+TheoraDecoder::Shutdown()
+{
+ if (mTheoraDecoderContext) {
+ th_decode_free(mTheoraDecoderContext);
+ mTheoraDecoderContext = nullptr;
+ }
+ return NS_OK;
+}
+
+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(DecoderFailureReason::INIT_ERROR, __func__);
+ }
+ for (size_t i = 0; i < headers.Length(); i++) {
+ if (NS_FAILED(DoDecodeHeader(headers[i], headerLens[i]))) {
+ return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
+ }
+ }
+ if (mPacketCount != 3) {
+ return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
+ }
+
+ mTheoraDecoderContext = th_decode_alloc(&mTheoraInfo, mTheoraSetupInfo);
+ if (mTheoraDecoderContext) {
+ return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
+ } else {
+ return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
+ }
+
+}
+
+nsresult
+TheoraDecoder::Flush()
+{
+ MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+ mIsFlushing = true;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([this] () {
+ // nothing to do for now.
+ });
+ SyncRunnable::DispatchToThread(mTaskQueue, r);
+ mIsFlushing = false;
+ return NS_OK;
+}
+
+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;
+}
+
+int
+TheoraDecoder::DoDecode(MediaRawData* aSample)
+{
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+
+ 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, 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].mOffset = 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].mOffset = 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].mOffset = b.mPlanes[2].mSkip = 0;
+
+ IntRect pictureArea(mTheoraInfo.pic_x, mTheoraInfo.pic_y,
+ mTheoraInfo.pic_width, mTheoraInfo.pic_height);
+
+ VideoInfo info;
+ info.mDisplay = mInfo.mDisplay;
+ RefPtr<VideoData> v = VideoData::Create(info,
+ mImageContainer,
+ aSample->mOffset,
+ aSample->mTime,
+ aSample->mDuration,
+ b,
+ aSample->mKeyframe,
+ aSample->mTimecode,
+ mInfo.ScaledImageRect(mTheoraInfo.frame_width,
+ mTheoraInfo.frame_height));
+ if (!v) {
+ LOG("Image allocation error source %ldx%ld display %ldx%ld picture %ldx%ld",
+ mTheoraInfo.frame_width, mTheoraInfo.frame_height, mInfo.mDisplay.width, mInfo.mDisplay.height,
+ mInfo.mImage.width, mInfo.mImage.height);
+ return -1;
+ }
+ mCallback->Output(v);
+ return 0;
+ } else {
+ LOG("Theora Decode error: %d", ret);
+ return -1;
+ }
+}
+
+void
+TheoraDecoder::ProcessDecode(MediaRawData* aSample)
+{
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ if (mIsFlushing) {
+ return;
+ }
+ if (DoDecode(aSample) == -1) {
+ mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+ } else if (mTaskQueue->IsEmpty()) {
+ mCallback->InputExhausted();
+ }
+}
+
+nsresult
+TheoraDecoder::Input(MediaRawData* aSample)
+{
+ MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+ mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
+ this, &TheoraDecoder::ProcessDecode, aSample));
+
+ return NS_OK;
+}
+
+void
+TheoraDecoder::ProcessDrain()
+{
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ mCallback->DrainComplete();
+}
+
+nsresult
+TheoraDecoder::Drain()
+{
+ MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+ mTaskQueue->Dispatch(NewRunnableMethod(this, &TheoraDecoder::ProcessDrain));
+
+ return NS_OK;
+}
+
+/* static */
+bool
+TheoraDecoder::IsTheora(const nsACString& aMimeType)
+{
+ return aMimeType.EqualsLiteral("video/ogg; codecs=theora");
+}
+
+} // namespace mozilla
+#undef LOG
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/agnostic/TheoraDecoder.h
@@ -0,0 +1,64 @@
+/* -*- 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/. */
+#if !defined(TheoraDecoder_h_)
+#define TheoraDecoder_h_
+
+#include "PlatformDecoderModule.h"
+
+#include <stdint.h>
+#include "ogg/ogg.h"
+#include "theora/theoradec.h"
+
+namespace mozilla {
+
+ using namespace layers;
+
+class TheoraDecoder : public MediaDataDecoder
+{
+public:
+ explicit TheoraDecoder(const CreateDecoderParams& aParams);
+
+ ~TheoraDecoder();
+
+ RefPtr<InitPromise> Init() override;
+ nsresult Input(MediaRawData* aSample) override;
+ nsresult Flush() override;
+ nsresult Drain() override;
+ nsresult Shutdown() override;
+
+ // Return true if mimetype is a Theora codec
+ static bool IsTheora(const nsACString& aMimeType);
+
+ const char* GetDescriptionName() const override
+ {
+ return "theora video decoder";
+ }
+
+private:
+ nsresult DoDecodeHeader(const unsigned char* aData, size_t aLength);
+
+ void ProcessDecode(MediaRawData* aSample);
+ int DoDecode(MediaRawData* aSample);
+ void ProcessDrain();
+
+ RefPtr<ImageContainer> mImageContainer;
+ RefPtr<TaskQueue> mTaskQueue;
+ MediaDataDecoderCallback* mCallback;
+ Atomic<bool> mIsFlushing;
+
+ // Theora header & decoder state
+ th_info mTheoraInfo;
+ th_comment mTheoraComment;
+ th_setup_info *mTheoraSetupInfo;
+ th_dec_ctx *mTheoraDecoderContext;
+ int mPacketCount;
+
+ const VideoInfo& mInfo;
+};
+
+} // namespace mozilla
+
+#endif
--- a/dom/media/platforms/moz.build
+++ b/dom/media/platforms/moz.build
@@ -2,29 +2,31 @@
# vim: set filetype=python:
# 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/.
EXPORTS += [
'agnostic/AgnosticDecoderModule.h',
'agnostic/OpusDecoder.h',
+ 'agnostic/TheoraDecoder.h',
'agnostic/VorbisDecoder.h',
'agnostic/VPXDecoder.h',
'MediaTelemetryConstants.h',
'PDMFactory.h',
'PlatformDecoderModule.h',
'wrappers/FuzzingWrapper.h',
'wrappers/H264Converter.h'
]
UNIFIED_SOURCES += [
'agnostic/AgnosticDecoderModule.cpp',
'agnostic/BlankDecoderModule.cpp',
'agnostic/OpusDecoder.cpp',
+ 'agnostic/TheoraDecoder.cpp',
'agnostic/VorbisDecoder.cpp',
'agnostic/VPXDecoder.cpp',
'agnostic/WAVDecoder.cpp',
'PDMFactory.cpp',
'wrappers/FuzzingWrapper.cpp',
'wrappers/H264Converter.cpp'
]