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