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 #include "XiphExtradata.h"
9 #include "gfx2DGlue.h"
10 #include "nsError.h"
11 #include "TimeUnits.h"
12 #include "mozilla/PodOperations.h"
13
14 #include <algorithm>
15
16 #undef LOG
17 #define LOG(arg, ...) MOZ_LOG(gMediaDecoderLog, mozilla::LogLevel::Debug, ("TheoraDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
18
19 namespace mozilla {
20
21 using namespace gfx;
22 using namespace layers;
23
24 extern LazyLogModule gMediaDecoderLog;
25
InitTheoraPacket(const unsigned char * aData,size_t aLength,bool aBOS,bool aEOS,int64_t aGranulepos,int64_t aPacketNo)26 ogg_packet InitTheoraPacket(const unsigned char* aData, size_t aLength,
27 bool aBOS, bool aEOS,
28 int64_t aGranulepos, int64_t aPacketNo)
29 {
30 ogg_packet packet;
31 packet.packet = const_cast<unsigned char*>(aData);
32 packet.bytes = aLength;
33 packet.b_o_s = aBOS;
34 packet.e_o_s = aEOS;
35 packet.granulepos = aGranulepos;
36 packet.packetno = aPacketNo;
37 return packet;
38 }
39
TheoraDecoder(const CreateDecoderParams & aParams)40 TheoraDecoder::TheoraDecoder(const CreateDecoderParams& aParams)
41 : mImageContainer(aParams.mImageContainer)
42 , mTaskQueue(aParams.mTaskQueue)
43 , mCallback(aParams.mCallback)
44 , mIsFlushing(false)
45 , mTheoraSetupInfo(nullptr)
46 , mTheoraDecoderContext(nullptr)
47 , mPacketCount(0)
48 , mInfo(aParams.VideoConfig())
49 {
50 MOZ_COUNT_CTOR(TheoraDecoder);
51 }
52
~TheoraDecoder()53 TheoraDecoder::~TheoraDecoder()
54 {
55 MOZ_COUNT_DTOR(TheoraDecoder);
56 th_setup_free(mTheoraSetupInfo);
57 th_comment_clear(&mTheoraComment);
58 th_info_clear(&mTheoraInfo);
59 }
60
61 void
Shutdown()62 TheoraDecoder::Shutdown()
63 {
64 if (mTheoraDecoderContext) {
65 th_decode_free(mTheoraDecoderContext);
66 mTheoraDecoderContext = nullptr;
67 }
68 }
69
70 RefPtr<MediaDataDecoder::InitPromise>
Init()71 TheoraDecoder::Init()
72 {
73 th_comment_init(&mTheoraComment);
74 th_info_init(&mTheoraInfo);
75
76 nsTArray<unsigned char*> headers;
77 nsTArray<size_t> headerLens;
78 if (!XiphExtradataToHeaders(headers, headerLens,
79 mInfo.mCodecSpecificConfig->Elements(),
80 mInfo.mCodecSpecificConfig->Length())) {
81 return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
82 }
83 for (size_t i = 0; i < headers.Length(); i++) {
84 if (NS_FAILED(DoDecodeHeader(headers[i], headerLens[i]))) {
85 return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
86 }
87 }
88 if (mPacketCount != 3) {
89 return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
90 }
91
92 mTheoraDecoderContext = th_decode_alloc(&mTheoraInfo, mTheoraSetupInfo);
93 if (mTheoraDecoderContext) {
94 return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
95 } else {
96 return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
97 }
98
99 }
100
101 void
Flush()102 TheoraDecoder::Flush()
103 {
104 MOZ_ASSERT(mCallback->OnReaderTaskQueue());
105 mIsFlushing = true;
106 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([this] () {
107 // nothing to do for now.
108 });
109 SyncRunnable::DispatchToThread(mTaskQueue, r);
110 mIsFlushing = false;
111 }
112
113 nsresult
DoDecodeHeader(const unsigned char * aData,size_t aLength)114 TheoraDecoder::DoDecodeHeader(const unsigned char* aData, size_t aLength)
115 {
116 bool bos = mPacketCount == 0;
117 ogg_packet pkt = InitTheoraPacket(aData, aLength, bos, false, 0, mPacketCount++);
118
119 int r = th_decode_headerin(&mTheoraInfo,
120 &mTheoraComment,
121 &mTheoraSetupInfo,
122 &pkt);
123 return r > 0 ? NS_OK : NS_ERROR_FAILURE;
124 }
125
126 MediaResult
DoDecode(MediaRawData * aSample)127 TheoraDecoder::DoDecode(MediaRawData* aSample)
128 {
129 MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
130
131 const unsigned char* aData = aSample->Data();
132 size_t aLength = aSample->Size();
133
134 bool bos = mPacketCount == 0;
135 ogg_packet pkt = InitTheoraPacket(aData, aLength, bos, false, aSample->mTimecode, mPacketCount++);
136
137 int ret = th_decode_packetin(mTheoraDecoderContext, &pkt, nullptr);
138 if (ret == 0 || ret == TH_DUPFRAME) {
139 th_ycbcr_buffer ycbcr;
140 th_decode_ycbcr_out(mTheoraDecoderContext, ycbcr);
141
142 int hdec = !(mTheoraInfo.pixel_fmt & 1);
143 int vdec = !(mTheoraInfo.pixel_fmt & 2);
144
145 VideoData::YCbCrBuffer b;
146 b.mPlanes[0].mData = ycbcr[0].data;
147 b.mPlanes[0].mStride = ycbcr[0].stride;
148 b.mPlanes[0].mHeight = mTheoraInfo.frame_height;
149 b.mPlanes[0].mWidth = mTheoraInfo.frame_width;
150 b.mPlanes[0].mOffset = b.mPlanes[0].mSkip = 0;
151
152 b.mPlanes[1].mData = ycbcr[1].data;
153 b.mPlanes[1].mStride = ycbcr[1].stride;
154 b.mPlanes[1].mHeight = mTheoraInfo.frame_height >> vdec;
155 b.mPlanes[1].mWidth = mTheoraInfo.frame_width >> hdec;
156 b.mPlanes[1].mOffset = b.mPlanes[1].mSkip = 0;
157
158 b.mPlanes[2].mData = ycbcr[2].data;
159 b.mPlanes[2].mStride = ycbcr[2].stride;
160 b.mPlanes[2].mHeight = mTheoraInfo.frame_height >> vdec;
161 b.mPlanes[2].mWidth = mTheoraInfo.frame_width >> hdec;
162 b.mPlanes[2].mOffset = b.mPlanes[2].mSkip = 0;
163
164 IntRect pictureArea(mTheoraInfo.pic_x, mTheoraInfo.pic_y,
165 mTheoraInfo.pic_width, mTheoraInfo.pic_height);
166
167 VideoInfo info;
168 info.mDisplay = mInfo.mDisplay;
169 RefPtr<VideoData> v =
170 VideoData::CreateAndCopyData(info,
171 mImageContainer,
172 aSample->mOffset,
173 aSample->mTime,
174 aSample->mDuration,
175 b,
176 aSample->mKeyframe,
177 aSample->mTimecode,
178 mInfo.ScaledImageRect(mTheoraInfo.frame_width,
179 mTheoraInfo.frame_height));
180 if (!v) {
181 LOG("Image allocation error source %ldx%ld display %ldx%ld picture %ldx%ld",
182 mTheoraInfo.frame_width, mTheoraInfo.frame_height, mInfo.mDisplay.width, mInfo.mDisplay.height,
183 mInfo.mImage.width, mInfo.mImage.height);
184 return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__);
185 }
186 mCallback->Output(v);
187 return NS_OK;
188 } else {
189 LOG("Theora Decode error: %d", ret);
190 return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
191 RESULT_DETAIL("Theora decode error:%d", ret));
192 }
193 }
194
195 void
ProcessDecode(MediaRawData * aSample)196 TheoraDecoder::ProcessDecode(MediaRawData* aSample)
197 {
198 MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
199 if (mIsFlushing) {
200 return;
201 }
202 MediaResult rv = DoDecode(aSample);
203 if (NS_FAILED(rv)) {
204 mCallback->Error(rv);
205 } else {
206 mCallback->InputExhausted();
207 }
208 }
209
210 void
Input(MediaRawData * aSample)211 TheoraDecoder::Input(MediaRawData* aSample)
212 {
213 MOZ_ASSERT(mCallback->OnReaderTaskQueue());
214 mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
215 this, &TheoraDecoder::ProcessDecode, aSample));
216 }
217
218 void
ProcessDrain()219 TheoraDecoder::ProcessDrain()
220 {
221 MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
222 mCallback->DrainComplete();
223 }
224
225 void
Drain()226 TheoraDecoder::Drain()
227 {
228 MOZ_ASSERT(mCallback->OnReaderTaskQueue());
229 mTaskQueue->Dispatch(NewRunnableMethod(this, &TheoraDecoder::ProcessDrain));
230 }
231
232 /* static */
233 bool
IsTheora(const nsACString & aMimeType)234 TheoraDecoder::IsTheora(const nsACString& aMimeType)
235 {
236 return aMimeType.EqualsLiteral("video/theora");
237 }
238
239 } // namespace mozilla
240 #undef LOG
241