1 /**
2  * Copyright (c) 2006-2019 LOVE Development Team
3  *
4  * This software is provided 'as-is', without any express or implied
5  * warranty.  In no event will the authors be held liable for any damages
6  * arising from the use of this software.
7  *
8  * Permission is granted to anyone to use this software for any purpose,
9  * including commercial applications, and to alter it and redistribute it
10  * freely, subject to the following restrictions:
11  *
12  * 1. The origin of this software must not be misrepresented; you must not
13  *    claim that you wrote the original software. If you use this software
14  *    in a product, an acknowledgment in the product documentation would be
15  *    appreciated but is not required.
16  * 2. Altered source versions must be plainly marked as such, and must not be
17  *    misrepresented as being the original software.
18  * 3. This notice may not be removed or altered from any source distribution.
19  **/
20 
21 // STL
22 #include <iostream>
23 
24 // LOVE
25 #include "TheoraVideoStream.h"
26 
27 using love::filesystem::File;
28 
29 namespace love
30 {
31 namespace video
32 {
33 namespace theora
34 {
35 
TheoraVideoStream(love::filesystem::File * file)36 TheoraVideoStream::TheoraVideoStream(love::filesystem::File *file)
37 	: demuxer(file)
38 	, headerParsed(false)
39 	, decoder(nullptr)
40 	, frameReady(false)
41 	, lastFrame(0)
42 	, nextFrame(0)
43 {
44 	if (demuxer.findStream() != OggDemuxer::TYPE_THEORA)
45 		throw love::Exception("Invalid video file, video is not theora");
46 
47 	th_info_init(&videoInfo);
48 
49 	frontBuffer = new Frame();
50 	backBuffer = new Frame();
51 
52 	try
53 	{
54 		parseHeader();
55 	}
56 	catch (love::Exception &ex)
57 	{
58 		delete backBuffer;
59 		delete frontBuffer;
60 		th_info_clear(&videoInfo);
61 		throw ex;
62 	}
63 
64 	frameSync.set(new DeltaSync(), Acquire::NORETAIN);
65 }
66 
~TheoraVideoStream()67 TheoraVideoStream::~TheoraVideoStream()
68 {
69 	if (decoder)
70 		th_decode_free(decoder);
71 
72 	th_info_clear(&videoInfo);
73 
74 	delete frontBuffer;
75 	delete backBuffer;
76 }
77 
getWidth() const78 int TheoraVideoStream::getWidth() const
79 {
80 	if (headerParsed)
81 		return videoInfo.pic_width;
82 	else
83 		return 0;
84 }
85 
getHeight() const86 int TheoraVideoStream::getHeight() const
87 {
88 	if (headerParsed)
89 		return videoInfo.pic_height;
90 	else
91 		return 0;
92 }
93 
getFilename() const94 const std::string &TheoraVideoStream::getFilename() const
95 {
96 	return demuxer.getFilename();
97 }
98 
setSync(FrameSync * frameSync)99 void TheoraVideoStream::setSync(FrameSync *frameSync)
100 {
101 	love::thread::Lock l(bufferMutex);
102 	this->frameSync = frameSync;
103 }
104 
getFrontBuffer() const105 const void *TheoraVideoStream::getFrontBuffer() const
106 {
107 	return frontBuffer;
108 }
109 
getSize() const110 size_t TheoraVideoStream::getSize() const
111 {
112 	return sizeof(Frame);
113 }
114 
isPlaying() const115 bool TheoraVideoStream::isPlaying() const
116 {
117 	return frameSync->isPlaying() && !demuxer.isEos();
118 }
119 
120 template<typename T>
scaleFormat(th_pixel_fmt fmt,T & x,T & y)121 inline void scaleFormat(th_pixel_fmt fmt, T &x, T &y)
122 {
123 	switch(fmt)
124 	{
125 	case TH_PF_420:
126 		y /= 2;
127 	case TH_PF_422:
128 		x /= 2;
129 		break;
130 	default:
131 		break;
132 	}
133 }
134 
parseHeader()135 void TheoraVideoStream::parseHeader()
136 {
137 	if (headerParsed)
138 		return;
139 
140 	th_comment comment;
141 	th_setup_info *setupInfo = nullptr;
142 	th_comment_init(&comment);
143 	int ret;
144 
145 	demuxer.readPacket(packet);
146 	ret = th_decode_headerin(&videoInfo, &comment, &setupInfo, &packet);
147 
148 	if (ret < 0)
149 	{
150 		th_comment_clear(&comment);
151 		throw love::Exception("Could not find header");
152 	}
153 
154 	while (ret > 0)
155 	{
156 		demuxer.readPacket(packet);
157 		ret = th_decode_headerin(&videoInfo, &comment, &setupInfo, &packet);
158 	}
159 
160 	th_comment_clear(&comment);
161 
162 	decoder = th_decode_alloc(&videoInfo, setupInfo);
163 	th_setup_free(setupInfo);
164 
165 	Frame *buffers[2] = {backBuffer, frontBuffer};
166 
167 	yPlaneXOffset = cPlaneXOffset = videoInfo.pic_x;
168 	yPlaneYOffset = cPlaneYOffset = videoInfo.pic_y;
169 
170 	scaleFormat(videoInfo.pixel_fmt, cPlaneXOffset, cPlaneYOffset);
171 
172 	for (int i = 0; i < 2; i++)
173 	{
174 		buffers[i]->cw = buffers[i]->yw = videoInfo.pic_width;
175 		buffers[i]->ch = buffers[i]->yh = videoInfo.pic_height;
176 
177 		scaleFormat(videoInfo.pixel_fmt, buffers[i]->cw, buffers[i]->ch);
178 
179 		buffers[i]->yplane = new unsigned char[buffers[i]->yw * buffers[i]->yh];
180 		buffers[i]->cbplane = new unsigned char[buffers[i]->cw * buffers[i]->ch];
181 		buffers[i]->crplane = new unsigned char[buffers[i]->cw * buffers[i]->ch];
182 
183 		memset(buffers[i]->yplane, 16, buffers[i]->yw * buffers[i]->yh);
184 		memset(buffers[i]->cbplane, 128, buffers[i]->cw * buffers[i]->ch);
185 		memset(buffers[i]->crplane, 128, buffers[i]->cw * buffers[i]->ch);
186 	}
187 
188 	headerParsed = true;
189 	th_decode_packetin(decoder, &packet, nullptr);
190 }
191 
seekDecoder(double target)192 void TheoraVideoStream::seekDecoder(double target)
193 {
194 	bool success = demuxer.seek(packet, target, [this](int64 granulepos) {
195 		return th_granule_time(decoder, granulepos);
196 	});
197 
198 	if (!success)
199 		return;
200 
201 	// Now update theora and our decoder on this new position of ours
202 	lastFrame = nextFrame = -1;
203 	th_decode_ctl(decoder, TH_DECCTL_SET_GRANPOS, &packet.granulepos, sizeof(packet.granulepos));
204 }
205 
threadedFillBackBuffer(double dt)206 void TheoraVideoStream::threadedFillBackBuffer(double dt)
207 {
208 	// Synchronize
209 	frameSync->update(dt);
210 	double position = frameSync->getPosition();
211 
212 	// Seeking backwards
213 	if (position < lastFrame)
214 		seekDecoder(position);
215 
216 	th_ycbcr_buffer bufferinfo;
217 	bool hasFrame = false;
218 
219 	// Until we are at the end of the stream, or we are displaying the right frame
220 	unsigned int framesBehind = 0;
221 	bool failedSeek = false;
222 	while (!demuxer.isEos() && position >= nextFrame)
223 	{
224 		// If we can't catch up, seek
225 		if (framesBehind++ > 5 && !failedSeek)
226 		{
227 			seekDecoder(position);
228 			framesBehind = 0;
229 			failedSeek = true;
230 		}
231 
232 		th_decode_ycbcr_out(decoder, bufferinfo);
233 		hasFrame = true;
234 
235 		ogg_int64_t granulePosition;
236 		do
237 		{
238 			if (demuxer.readPacket(packet))
239 				return;
240 		} while (th_decode_packetin(decoder, &packet, &granulePosition) != 0);
241 		lastFrame = nextFrame;
242 		nextFrame = th_granule_time(decoder, granulePosition);
243 	}
244 
245 	// Only swap once, even if we read many frames to get here
246 	if (hasFrame)
247 	{
248 		// Don't swap whilst we're writing to the backbuffer
249 		{
250 			love::thread::Lock l(bufferMutex);
251 			frameReady = false;
252 		}
253 
254 		for (int y = 0; y < backBuffer->yh; ++y)
255 		{
256 			memcpy(backBuffer->yplane+backBuffer->yw*y,
257 					bufferinfo[0].data+
258 						bufferinfo[0].stride*(y+yPlaneYOffset)+yPlaneXOffset,
259 					backBuffer->yw);
260 		}
261 
262 		for (int y = 0; y < backBuffer->ch; ++y)
263 		{
264 			memcpy(backBuffer->cbplane+backBuffer->cw*y,
265 					bufferinfo[1].data+
266 						bufferinfo[1].stride*(y+cPlaneYOffset)+cPlaneXOffset,
267 					backBuffer->cw);
268 		}
269 
270 		for (int y = 0; y < backBuffer->ch; ++y)
271 		{
272 			memcpy(backBuffer->crplane+backBuffer->cw*y,
273 					bufferinfo[2].data+
274 						bufferinfo[2].stride*(y+cPlaneYOffset)+cPlaneXOffset,
275 					backBuffer->cw);
276 		}
277 
278 		// Re-enable swapping
279 		{
280 			love::thread::Lock l(bufferMutex);
281 			frameReady = true;
282 		}
283 	}
284 }
285 
fillBackBuffer()286 void TheoraVideoStream::fillBackBuffer()
287 {
288 	// Done in worker thread
289 }
290 
swapBuffers()291 bool TheoraVideoStream::swapBuffers()
292 {
293 	if (demuxer.isEos())
294 		return false;
295 
296 	if (!frameSync->isPlaying())
297 		return false;
298 
299 	love::thread::Lock l(bufferMutex);
300 	if (!frameReady)
301 		return false;
302 	frameReady = false;
303 
304 	Frame *temp = frontBuffer;
305 	frontBuffer = backBuffer;
306 	backBuffer = temp;
307 
308 	return true;
309 }
310 
311 } // theora
312 } // video
313 } // love
314