271 lines
9.2 KiB
C++
271 lines
9.2 KiB
C++
/* -*- 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 <ogg/ogg.h>
|
|
|
|
#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;
|
|
Result<already_AddRefed<VideoData>, MediaResult> r =
|
|
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 (r.isErr()) {
|
|
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(r.unwrapErr(), __func__);
|
|
}
|
|
|
|
RefPtr<VideoData> v = r.unwrap();
|
|
MOZ_ASSERT(v);
|
|
|
|
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);
|
|
aStage.SetStartTimeAndEndTime(v->mTime.ToMicroseconds(),
|
|
v->GetEndTime().ToMicroseconds());
|
|
});
|
|
});
|
|
|
|
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
|