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