1 //       _________ __                 __
2 //      /   _____//  |_____________ _/  |______     ____  __ __  ______
3 //      \_____  \\   __\_  __ \__  \\   __\__  \   / ___\|  |  \/  ___/
4 //      /        \|  |  |  | \// __ \|  |  / __ \_/ /_/  >  |  /\___ |
5 //     /_______  /|__|  |__|  (____  /__| (____  /\___  /|____//____  >
6 //             \/                  \/          \//_____/            \/
7 //  ______________________                           ______________________
8 //                        T H E   W A R   B E G I N S
9 //         Stratagus - A free fantasy real time strategy game engine
10 //
11 /**@name movie.cpp - Movie playback functions. */
12 //
13 //      (c) Copyright 2005-2011 by Nehal Mistry, Jimmy Salmon and Pali Rohár
14 //
15 //      This program is free software; you can redistribute it and/or modify
16 //      it under the terms of the GNU General Public License as published by
17 //      the Free Software Foundation; only version 2 of the License.
18 //
19 //      This program is distributed in the hope that it will be useful,
20 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
21 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 //      GNU General Public License for more details.
23 //
24 //      You should have received a copy of the GNU General Public License
25 //      along with this program; if not, write to the Free Software
26 //      Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
27 //      02111-1307, USA.
28 //
29 
30 //@{
31 
32 #ifdef USE_THEORA
33 
34 /*----------------------------------------------------------------------------
35 -- Includes
36 ----------------------------------------------------------------------------*/
37 
38 #include "stratagus.h"
39 
40 #include "movie.h"
41 
42 #include "iocompat.h"
43 #include "iolib.h"
44 #include "network.h"
45 #include "sound.h"
46 #include "sound_server.h"
47 #include "video.h"
48 
49 #include "SDL.h"
50 
51 /*----------------------------------------------------------------------------
52 --  Defines
53 ----------------------------------------------------------------------------*/
54 
55 /*----------------------------------------------------------------------------
56 --  Variables
57 ----------------------------------------------------------------------------*/
58 
59 extern SDL_Surface *TheScreen;
60 static bool MovieStop;
61 
62 /*----------------------------------------------------------------------------
63 --  Functions
64 ----------------------------------------------------------------------------*/
65 
66 /**
67 **  Callbacks for movie input.
68 */
69 
MovieCallbackButtonPressed(unsigned)70 static void MovieCallbackButtonPressed(unsigned)
71 {
72 	MovieStop = true;
73 }
74 
MovieCallbackButtonReleased(unsigned)75 static void MovieCallbackButtonReleased(unsigned)
76 {
77 }
78 
MovieCallbackKeyPressed(unsigned,unsigned)79 static void MovieCallbackKeyPressed(unsigned, unsigned)
80 {
81 	MovieStop = true;
82 }
83 
84 
MovieCallbackKeyReleased(unsigned,unsigned)85 static void MovieCallbackKeyReleased(unsigned, unsigned)
86 {
87 }
88 
MovieCallbackKeyRepeated(unsigned,unsigned)89 static void MovieCallbackKeyRepeated(unsigned, unsigned)
90 {
91 }
92 
MovieCallbackMouseMove(const PixelPos &)93 static void MovieCallbackMouseMove(const PixelPos &)
94 {
95 }
96 
MovieCallbackMouseExit()97 static void MovieCallbackMouseExit()
98 {
99 }
100 
101 /**
102 **  Draw Ogg data to the overlay
103 */
OutputTheora(OggData * data,SDL_Overlay * yuv_overlay,SDL_Rect * rect)104 static int OutputTheora(OggData *data, SDL_Overlay *yuv_overlay, SDL_Rect *rect)
105 {
106 	yuv_buffer yuv;
107 
108 	theora_decode_YUVout(&data->tstate, &yuv);
109 
110 	if (SDL_MUSTLOCK(TheScreen)) {
111 		if (SDL_LockSurface(TheScreen) < 0) {
112 			return - 1;
113 		}
114 	}
115 
116 	if (SDL_LockYUVOverlay(yuv_overlay) < 0) {
117 		return -1;
118 	}
119 
120 	int crop_offset = data->tinfo.offset_x + yuv.y_stride * data->tinfo.offset_y;
121 	for (int i = 0; i < yuv_overlay->h; ++i) {
122 		memcpy(yuv_overlay->pixels[0] + yuv_overlay->pitches[0] * i,
123 			   yuv.y + crop_offset + yuv.y_stride * i, yuv_overlay->w);
124 	}
125 
126 	crop_offset = (data->tinfo.offset_x / 2) + (yuv.uv_stride) *
127 				  (data->tinfo.offset_y / 2);
128 	for (int i = 0; i < yuv_overlay->h / 2; ++i) {
129 		memcpy(yuv_overlay->pixels[1] + yuv_overlay->pitches[1] * i,
130 			   yuv.v + yuv.uv_stride * i, yuv_overlay->w / 2);
131 		memcpy(yuv_overlay->pixels[2] + yuv_overlay->pitches[2] * i,
132 			   yuv.u + crop_offset + yuv.uv_stride * i, yuv_overlay->w / 2);
133 	}
134 
135 	if (SDL_MUSTLOCK(TheScreen)) {
136 		SDL_UnlockSurface(TheScreen);
137 	}
138 	SDL_UnlockYUVOverlay(yuv_overlay);
139 
140 	SDL_DisplayYUVOverlay(yuv_overlay, rect);
141 
142 	return 0;
143 }
144 
145 /**
146 **  Process Ogg data
147 */
TheoraProcessData(OggData * data)148 static int TheoraProcessData(OggData *data)
149 {
150 	ogg_packet packet;
151 
152 	while (1) {
153 		if (ogg_stream_packetout(&data->vstream, &packet) != 1) {
154 			if (OggGetNextPage(&data->page, &data->sync, data->File)) {
155 				// EOF
156 				return -1;
157 			}
158 			ogg_stream_pagein(&data->vstream, &data->page);
159 		} else {
160 			theora_decode_packetin(&data->tstate, &packet);
161 			return 0;
162 		}
163 	}
164 }
165 
166 
167 /**
168 **  Play a video file.
169 **
170 **  @param name   Filename of movie file.
171 **
172 **  @return       Non-zero if file isn't a supported movie.
173 */
PlayMovie(const std::string & name)174 int PlayMovie(const std::string &name)
175 {
176 	int videoWidth, videoHeight;
177 #if defined(USE_OPENGL) || defined(USE_GLES)
178 	videoWidth  = Video.ViewportWidth;
179 	videoHeight = Video.ViewportHeight;
180 #else
181 	videoWidth  = Video.Width;
182 	videoHeight = Video.Height;
183 #endif
184 
185 	const std::string filename = LibraryFileName(name.c_str());
186 
187 	CFile f;
188 	if (f.open(filename.c_str(), CL_OPEN_READ) == -1) {
189 		fprintf(stderr, "Can't open file '%s'\n", name.c_str());
190 		return 0;
191 	}
192 
193 	OggData data;
194 	memset(&data, 0, sizeof(data));
195 	if (OggInit(&f, &data) || !data.video) {
196 		OggFree(&data);
197 		f.close();
198 		return -1;
199 	}
200 
201 	data.File = &f;
202 	SDL_Rect rect;
203 
204 	if (data.tinfo.frame_width * 300 / 4 > data.tinfo.frame_height * 100) {
205 		rect.w = videoWidth;
206 		rect.h = videoWidth * data.tinfo.frame_height / data.tinfo.frame_width;
207 		rect.x = 0;
208 		rect.y = (videoHeight - rect.h) / 2;
209 	} else {
210 		rect.w = videoHeight * data.tinfo.frame_width / data.tinfo.frame_height;
211 		rect.h = videoHeight;
212 		rect.x = (videoWidth - rect.w) / 2;
213 		rect.y = 0;
214 	}
215 
216 #ifdef USE_OPENGL
217 	// When SDL_OPENGL is used, it is not possible to call SDL_CreateYUVOverlay, so turn temporary OpenGL off
218 	// With GLES is all ok
219 	if (UseOpenGL) {
220 		SDL_SetVideoMode(Video.ViewportWidth, Video.ViewportHeight, Video.Depth, SDL_GetVideoSurface()->flags & ~SDL_OPENGL);
221 	}
222 #endif
223 
224 	SDL_FillRect(SDL_GetVideoSurface(), nullptr, 0);
225 	Video.ClearScreen();
226 	SDL_Overlay *yuv_overlay = SDL_CreateYUVOverlay(data.tinfo.frame_width, data.tinfo.frame_height, SDL_YV12_OVERLAY, TheScreen);
227 
228 	if (yuv_overlay == nullptr) {
229 		fprintf(stderr, "SDL_CreateYUVOverlay: %s\n", SDL_GetError());
230 		OggFree(&data);
231 		f.close();
232 		return 0;
233 	}
234 
235 	StopMusic();
236 	CSample *sample = LoadVorbis(filename.c_str(), PlayAudioStream);
237 	if (sample) {
238 		if ((sample->Channels != 1 && sample->Channels != 2) || sample->SampleSize != 16) {
239 			fprintf(stderr, "Unsupported sound format in movie\n");
240 			delete sample;
241 			SDL_FreeYUVOverlay(yuv_overlay);
242 			OggFree(&data);
243 			f.close();
244 			return 0;
245 		}
246 		PlayMusic(sample);
247 	}
248 
249 	EventCallback callbacks;
250 
251 	callbacks.ButtonPressed = MovieCallbackButtonPressed;
252 	callbacks.ButtonReleased = MovieCallbackButtonReleased;
253 	callbacks.MouseMoved = MovieCallbackMouseMove;
254 	callbacks.MouseExit = MovieCallbackMouseExit;
255 	callbacks.KeyPressed = MovieCallbackKeyPressed;
256 	callbacks.KeyReleased = MovieCallbackKeyReleased;
257 	callbacks.KeyRepeated = MovieCallbackKeyRepeated;
258 	callbacks.NetworkEvent = NetworkEvent;
259 
260 	const EventCallback *old_callbacks = GetCallbacks();
261 	SetCallbacks(&callbacks);
262 
263 	Invalidate();
264 	RealizeVideoMemory();
265 
266 	MovieStop = false;
267 	const unsigned int start_ticks = SDL_GetTicks();
268 	bool need_data = true;
269 	while (!MovieStop) {
270 		if (need_data) {
271 			if (TheoraProcessData(&data)) {
272 				break;
273 			}
274 			need_data = false;
275 		}
276 
277 		const int diff = SDL_GetTicks() - start_ticks
278 						 - static_cast<int>(theora_granule_time(&data.tstate, data.tstate.granulepos) * 1000);
279 
280 		if (diff > 100) {
281 			// too far behind, skip some frames
282 			need_data = true;
283 			continue;
284 		}
285 		if (diff > 0) {
286 			OutputTheora(&data, yuv_overlay, &rect);
287 			need_data = true;
288 		}
289 
290 		WaitEventsOneFrame();
291 	}
292 
293 	StopMusic();
294 	SDL_FreeYUVOverlay(yuv_overlay);
295 
296 	OggFree(&data);
297 	f.close();
298 
299 #ifdef USE_OPENGL
300 	if (UseOpenGL) {
301 		SDL_SetVideoMode(Video.ViewportWidth, Video.ViewportHeight, Video.Depth, SDL_GetVideoSurface()->flags | SDL_OPENGL);
302 		ReloadOpenGL();
303 	}
304 #endif
305 
306 	SetCallbacks(old_callbacks);
307 
308 	return 0;
309 }
310 
311 #else
312 
313 #include <string>
314 #include <string.h>
315 #include <stdio.h>
316 
317 /**
318 **  Play a video file.
319 **
320 **  @param name   Filename of movie file.
321 **
322 **  @return       Non-zero if file isn't a supported movie.
323 */
PlayMovie(const std::string & name)324 int PlayMovie(const std::string &name)
325 {
326 	if (strstr(name.c_str(), ".ogg") || strstr(name.c_str(), ".ogv")) {
327 		fprintf(stderr, "PlayMovie() '%s' is not supported, please recompile stratagus with theora support\n", name.c_str());
328 		return 0;
329 	}
330 	return -1;
331 }
332 
333 #endif
334 
335 //@}
336