1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "TheoraDecoder.h"
8
9 #include <algorithm>
10
11 #include "ImageContainer.h"
12 #include "TimeUnits.h"
13 #include "XiphExtradata.h"
14 #include "gfx2DGlue.h"
15 #include "mozilla/PodOperations.h"
16 #include "mozilla/TaskQueue.h"
17 #include "nsError.h"
18 #include "VideoUtils.h"
19
20 #undef LOG
21 #define LOG(arg, ...) \
22 DDMOZ_LOG(gMediaDecoderLog, mozilla::LogLevel::Debug, "::%s: " arg, \
23 __func__, ##__VA_ARGS__)
24
25 namespace mozilla {
26
27 using namespace gfx;
28 using namespace layers;
29
30 extern LazyLogModule gMediaDecoderLog;
31
InitTheoraPacket(const unsigned char * aData,size_t aLength,bool aBOS,bool aEOS,int64_t aGranulepos,int64_t aPacketNo)32 ogg_packet InitTheoraPacket(const unsigned char* aData, size_t aLength,
33 bool aBOS, bool aEOS, int64_t aGranulepos,
34 int64_t aPacketNo) {
35 ogg_packet packet;
36 packet.packet = const_cast<unsigned char*>(aData);
37 packet.bytes = aLength;
38 packet.b_o_s = aBOS;
39 packet.e_o_s = aEOS;
40 packet.granulepos = aGranulepos;
41 packet.packetno = aPacketNo;
42 return packet;
43 }
44
TheoraDecoder(const CreateDecoderParams & aParams)45 TheoraDecoder::TheoraDecoder(const CreateDecoderParams& aParams)
46 : mImageAllocator(aParams.mKnowsCompositor),
47 mImageContainer(aParams.mImageContainer),
48 mTaskQueue(
49 new TaskQueue(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
50 "TheoraDecoder")),
51 mTheoraInfo{},
52 mTheoraComment{},
53 mTheoraSetupInfo(nullptr),
54 mTheoraDecoderContext(nullptr),
55 mPacketCount(0),
56 mInfo(aParams.VideoConfig()) {
57 MOZ_COUNT_CTOR(TheoraDecoder);
58 }
59
~TheoraDecoder()60 TheoraDecoder::~TheoraDecoder() {
61 MOZ_COUNT_DTOR(TheoraDecoder);
62 th_setup_free(mTheoraSetupInfo);
63 th_comment_clear(&mTheoraComment);
64 th_info_clear(&mTheoraInfo);
65 }
66
Shutdown()67 RefPtr<ShutdownPromise> TheoraDecoder::Shutdown() {
68 RefPtr<TheoraDecoder> self = this;
69 return InvokeAsync(mTaskQueue, __func__, [self, this]() {
70 if (mTheoraDecoderContext) {
71 th_decode_free(mTheoraDecoderContext);
72 mTheoraDecoderContext = nullptr;
73 }
74 return mTaskQueue->BeginShutdown();
75 });
76 }
77
Init()78 RefPtr<MediaDataDecoder::InitPromise> TheoraDecoder::Init() {
79 th_comment_init(&mTheoraComment);
80 th_info_init(&mTheoraInfo);
81
82 nsTArray<unsigned char*> headers;
83 nsTArray<size_t> headerLens;
84 if (!XiphExtradataToHeaders(headers, headerLens,
85 mInfo.mCodecSpecificConfig->Elements(),
86 mInfo.mCodecSpecificConfig->Length())) {
87 return InitPromise::CreateAndReject(
88 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
89 RESULT_DETAIL("Could not get theora header.")),
90 __func__);
91 }
92 for (size_t i = 0; i < headers.Length(); i++) {
93 if (NS_FAILED(DoDecodeHeader(headers[i], headerLens[i]))) {
94 return InitPromise::CreateAndReject(
95 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
96 RESULT_DETAIL("Could not decode theora header.")),
97 __func__);
98 }
99 }
100 if (mPacketCount != 3) {
101 return InitPromise::CreateAndReject(
102 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
103 RESULT_DETAIL("Packet count is wrong.")),
104 __func__);
105 }
106
107 mTheoraDecoderContext = th_decode_alloc(&mTheoraInfo, mTheoraSetupInfo);
108 if (mTheoraDecoderContext) {
109 return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
110 } else {
111 return InitPromise::CreateAndReject(
112 MediaResult(NS_ERROR_OUT_OF_MEMORY,
113 RESULT_DETAIL("Could not allocate theora decoder.")),
114 __func__);
115 }
116 }
117
Flush()118 RefPtr<MediaDataDecoder::FlushPromise> TheoraDecoder::Flush() {
119 return InvokeAsync(mTaskQueue, __func__, []() {
120 return FlushPromise::CreateAndResolve(true, __func__);
121 });
122 }
123
DoDecodeHeader(const unsigned char * aData,size_t aLength)124 nsresult TheoraDecoder::DoDecodeHeader(const unsigned char* aData,
125 size_t aLength) {
126 bool bos = mPacketCount == 0;
127 ogg_packet pkt =
128 InitTheoraPacket(aData, aLength, bos, false, 0, mPacketCount++);
129
130 int r = th_decode_headerin(&mTheoraInfo, &mTheoraComment, &mTheoraSetupInfo,
131 &pkt);
132 return r > 0 ? NS_OK : NS_ERROR_FAILURE;
133 }
134
ProcessDecode(MediaRawData * aSample)135 RefPtr<MediaDataDecoder::DecodePromise> TheoraDecoder::ProcessDecode(
136 MediaRawData* aSample) {
137 MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
138
139 const unsigned char* aData = aSample->Data();
140 size_t aLength = aSample->Size();
141
142 bool bos = mPacketCount == 0;
143 ogg_packet pkt =
144 InitTheoraPacket(aData, aLength, bos, false,
145 aSample->mTimecode.ToMicroseconds(), mPacketCount++);
146
147 int ret = th_decode_packetin(mTheoraDecoderContext, &pkt, nullptr);
148 if (ret == 0 || ret == TH_DUPFRAME) {
149 th_ycbcr_buffer ycbcr;
150 th_decode_ycbcr_out(mTheoraDecoderContext, ycbcr);
151
152 int hdec = !(mTheoraInfo.pixel_fmt & 1);
153 int vdec = !(mTheoraInfo.pixel_fmt & 2);
154
155 VideoData::YCbCrBuffer b;
156 b.mPlanes[0].mData = ycbcr[0].data;
157 b.mPlanes[0].mStride = ycbcr[0].stride;
158 b.mPlanes[0].mHeight = mTheoraInfo.frame_height;
159 b.mPlanes[0].mWidth = mTheoraInfo.frame_width;
160 b.mPlanes[0].mSkip = 0;
161
162 b.mPlanes[1].mData = ycbcr[1].data;
163 b.mPlanes[1].mStride = ycbcr[1].stride;
164 b.mPlanes[1].mHeight = mTheoraInfo.frame_height >> vdec;
165 b.mPlanes[1].mWidth = mTheoraInfo.frame_width >> hdec;
166 b.mPlanes[1].mSkip = 0;
167
168 b.mPlanes[2].mData = ycbcr[2].data;
169 b.mPlanes[2].mStride = ycbcr[2].stride;
170 b.mPlanes[2].mHeight = mTheoraInfo.frame_height >> vdec;
171 b.mPlanes[2].mWidth = mTheoraInfo.frame_width >> hdec;
172 b.mPlanes[2].mSkip = 0;
173
174 b.mYUVColorSpace =
175 DefaultColorSpace({mTheoraInfo.frame_width, mTheoraInfo.frame_height});
176
177 IntRect pictureArea(mTheoraInfo.pic_x, mTheoraInfo.pic_y,
178 mTheoraInfo.pic_width, mTheoraInfo.pic_height);
179
180 VideoInfo info;
181 info.mDisplay = mInfo.mDisplay;
182 RefPtr<VideoData> v = VideoData::CreateAndCopyData(
183 info, mImageContainer, aSample->mOffset, aSample->mTime,
184 aSample->mDuration, b, aSample->mKeyframe, aSample->mTimecode,
185 mInfo.ScaledImageRect(mTheoraInfo.frame_width,
186 mTheoraInfo.frame_height),
187 mImageAllocator);
188 if (!v) {
189 LOG("Image allocation error source %ux%u display %ux%u picture %ux%u",
190 mTheoraInfo.frame_width, mTheoraInfo.frame_height,
191 mInfo.mDisplay.width, mInfo.mDisplay.height, mInfo.mImage.width,
192 mInfo.mImage.height);
193 return DecodePromise::CreateAndReject(
194 MediaResult(NS_ERROR_OUT_OF_MEMORY,
195 RESULT_DETAIL("Insufficient memory")),
196 __func__);
197 }
198 return DecodePromise::CreateAndResolve(DecodedData{v}, __func__);
199 }
200 LOG("Theora Decode error: %d", ret);
201 return DecodePromise::CreateAndReject(
202 MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
203 RESULT_DETAIL("Theora decode error:%d", ret)),
204 __func__);
205 }
206
Decode(MediaRawData * aSample)207 RefPtr<MediaDataDecoder::DecodePromise> TheoraDecoder::Decode(
208 MediaRawData* aSample) {
209 return InvokeAsync<MediaRawData*>(mTaskQueue, this, __func__,
210 &TheoraDecoder::ProcessDecode, aSample);
211 }
212
Drain()213 RefPtr<MediaDataDecoder::DecodePromise> TheoraDecoder::Drain() {
214 return InvokeAsync(mTaskQueue, __func__, [] {
215 return DecodePromise::CreateAndResolve(DecodedData(), __func__);
216 });
217 }
218
219 /* static */
IsTheora(const nsACString & aMimeType)220 bool TheoraDecoder::IsTheora(const nsACString& aMimeType) {
221 return aMimeType.EqualsLiteral("video/theora");
222 }
223
224 } // namespace mozilla
225 #undef LOG
226