1 //=============================================================================
2 //
3 // Adventure Game Studio (AGS)
4 //
5 // Copyright (C) 1999-2011 Chris Jones and 2011-20xx others
6 // The full list of copyright holders can be found in the Copyright.txt
7 // file, which is part of this source code distribution.
8 //
9 // The AGS source code is provided under the Artistic License 2.0.
10 // A copy of this license can be found in the file License.txt and at
11 // http://www.opensource.org/licenses/artistic-license-2.0.php
12 //
13 //=============================================================================
14
15 #include <stdio.h>
16 #include "video.h"
17 #include "apeg.h"
18 #include "debug/debug_log.h"
19 #include "debug/out.h"
20 #include "ac/asset_helper.h"
21 #include "ac/common.h"
22 #include "ac/draw.h"
23 #include "ac/game_version.h"
24 #include "ac/gamesetupstruct.h"
25 #include "ac/gamestate.h"
26 #include "ac/global_display.h"
27 #include "ac/mouse.h"
28 #include "ac/record.h"
29 #include "ac/runtime_defines.h"
30 #include "ac/system.h"
31 #include "core/assetmanager.h"
32 #include "gfx/bitmap.h"
33 #include "gfx/ddb.h"
34 #include "gfx/graphicsdriver.h"
35 #include "main/game_run.h"
36 #include "media/audio/audio.h"
37 #include "util/stream.h"
38
39 #if (ALLEGRO_DATE >= 20190303) || defined (WINDOWS_VERSION) || defined (ANDROID_VERSION)
40 #define AGS_FLI_FROM_PACK_FILE
41 #endif
42
43 using namespace AGS::Common;
44 using namespace AGS::Engine;
45
46
47 extern GameSetupStruct game;
48 extern IGraphicsDriver *gfxDriver;
49 extern Bitmap *virtual_screen;
50 extern int psp_video_framedrop;
51
52 enum VideoPlaybackType
53 {
54 kVideoNone,
55 kVideoFlic,
56 kVideoTheora
57 };
58
59 VideoPlaybackType video_type = kVideoNone;
60
61 // FLIC player start
62 Bitmap *fli_buffer = NULL;
63 short fliwidth,fliheight;
64 int canabort=0, stretch_flc = 1;
65 Bitmap *hicol_buf=NULL;
66 IDriverDependantBitmap *fli_ddb = NULL;
67 Bitmap *fli_target = NULL;
68 int fliTargetWidth, fliTargetHeight;
check_if_user_input_should_cancel_video()69 int check_if_user_input_should_cancel_video()
70 {
71 NEXT_ITERATION();
72 int key;
73 if (run_service_key_controls(key)) {
74 if ((key==27) && (canabort==1))
75 return 1;
76 if (canabort >= 2)
77 return 1; // skip on any key
78 }
79 if (canabort == 3) { // skip on mouse click
80 if (mgetbutton()!=NONE) return 1;
81 }
82 return 0;
83 }
84
85 #if defined(WINDOWS_VERSION)
fli_callback()86 int __cdecl fli_callback() {
87 #else
88 extern "C" int fli_callback() {
89 #endif
90 Bitmap *usebuf = fli_buffer;
91
92 update_polled_audio_and_crossfade ();
93
94 if (game.color_depth > 1) {
95 hicol_buf->Blit(fli_buffer,0,0,0,0,fliwidth,fliheight);
96 usebuf=hicol_buf;
97 }
98 if (stretch_flc == 0)
99 fli_target->Blit(usebuf, 0,0,play.viewport.GetWidth()/2-fliwidth/2,play.viewport.GetHeight()/2-fliheight/2,play.viewport.GetWidth(),play.viewport.GetHeight());
100 else
101 fli_target->StretchBlt(usebuf, RectWH(0,0,fliwidth,fliheight), RectWH(0,0,play.viewport.GetWidth(),play.viewport.GetHeight()));
102
103 gfxDriver->UpdateDDBFromBitmap(fli_ddb, fli_target, false);
104 gfxDriver->DrawSprite(0, 0, fli_ddb);
105 render_to_screen(fli_target, 0, 0);
106
107 return check_if_user_input_should_cancel_video();
108 }
109
110 void play_flc_file(int numb,int playflags) {
111 color oldpal[256];
112
113 // AGS 2.x: If the screen is faded out, fade in again when playing a movie.
114 if (loaded_game_file_version <= kGameVersion_272)
115 play.screen_is_faded_out = 0;
116
117 if (play.fast_forward)
118 return;
119
120 get_palette_range(oldpal, 0, 255);
121
122 int clearScreenAtStart = 1;
123 canabort = playflags % 10;
124 playflags -= canabort;
125
126 if (canabort == 2) // convert to PlayVideo-compatible setting
127 canabort = 3;
128
129 if (playflags % 100 == 0)
130 stretch_flc = 1;
131 else
132 stretch_flc = 0;
133
134 if (playflags / 100)
135 clearScreenAtStart = 0;
136
137 String flicname = String::FromFormat("flic%d.flc", numb);
138 Stream *in = AssetManager::OpenAsset(flicname);
139 if (!in)
140 {
141 flicname.Format("flic%d.fli", numb);
142 in = AssetManager::OpenAsset(flicname);
143 }
144 if (!in)
145 {
146 debug_script_warn("FLIC animation flic%d.flc nor flic%d.fli not found", numb, numb);
147 return;
148 }
149
150 in->Seek(8);
151 fliwidth = in->ReadInt16();
152 fliheight = in->ReadInt16();
153 delete in;
154
155 if (game.color_depth > 1) {
156 hicol_buf=BitmapHelper::CreateBitmap(fliwidth,fliheight,game.GetColorDepth());
157 hicol_buf->Clear();
158 }
159 // override the stretch option if necessary
160 if ((fliwidth==play.viewport.GetWidth()) && (fliheight==play.viewport.GetHeight()))
161 stretch_flc = 0;
162 else if ((fliwidth > play.viewport.GetWidth()) || (fliheight > play.viewport.GetHeight()))
163 stretch_flc = 1;
164 fli_buffer=BitmapHelper::CreateBitmap(fliwidth,fliheight,8);
165 if (fli_buffer==NULL) quit("Not enough memory to play animation");
166 fli_buffer->Clear();
167
168 Bitmap *screen_bmp = BitmapHelper::GetScreenBitmap();
169
170 if (clearScreenAtStart) {
171 screen_bmp->Clear();
172 render_to_screen(screen_bmp, 0, 0);
173 }
174
175 video_type = kVideoFlic;
176 fli_target = BitmapHelper::CreateBitmap(screen_bmp->GetWidth(), screen_bmp->GetHeight(), game.GetColorDepth());
177 fli_ddb = gfxDriver->CreateDDBFromBitmap(fli_target, false, true);
178
179 // TODO: find a better solution.
180 // Make only certain versions of the engineuse play_fli_pf from the patched version of Allegro for now.
181 // Add more versions as their Allegro lib becomes patched too, or they use newer version of Allegro 4.
182 // Ports can still play FLI if separate file is put into game's directory.
183 #if defined (AGS_FLI_FROM_PACK_FILE)
184 PACKFILE *pf = PackfileFromAsset(AssetPath("", flicname));
185 if (play_fli_pf(pf, (BITMAP*)fli_buffer->GetAllegroBitmap(), fli_callback)==FLI_ERROR)
186 #else
187 if (play_fli(flicname, (BITMAP*)fli_buffer->GetAllegroBitmap(), 0, fli_callback)==FLI_ERROR)
188 #endif
189 {
190 // This is not a fatal error that should prevent the game from continuing
191 Debug::Printf("FLI/FLC animation play error");
192 }
193 #if defined WINDOWS_VERSION
194 pack_fclose(pf);
195 #endif
196
197 video_type = kVideoNone;
198 delete fli_buffer;
199 fli_buffer = NULL;
200 // NOTE: the screen bitmap could change in the meanwhile, if the display mode has changed
201 screen_bmp = BitmapHelper::GetScreenBitmap();
202 screen_bmp->Clear();
203 set_palette_range(oldpal, 0, 255, 0);
204 render_to_screen(screen_bmp, 0, 0);
205
206 delete fli_target;
207 gfxDriver->DestroyDDB(fli_ddb);
208 fli_target = NULL;
209 fli_ddb = NULL;
210
211
212 delete hicol_buf;
213 hicol_buf=NULL;
214 // SetVirtualScreen(screen); wputblock(0,0,backbuffer,0);
215 while (mgetbutton()!=NONE) ;
216 invalidate_screen();
217 }
218
219 // FLIC player end
220
221 // Theora player begin
222 // TODO: find a way to take Bitmap here?
223 Bitmap gl_TheoraBuffer;
224 int theora_playing_callback(BITMAP *theoraBuffer)
225 {
226 if (theoraBuffer == NULL)
227 {
228 // No video, only sound
229 return check_if_user_input_should_cancel_video();
230 }
231
232 gl_TheoraBuffer.WrapAllegroBitmap(theoraBuffer, true);
233
234 int drawAtX = 0, drawAtY = 0;
235 if (fli_ddb == NULL)
236 {
237 fli_ddb = gfxDriver->CreateDDBFromBitmap(&gl_TheoraBuffer, false, true);
238 }
239 if (stretch_flc)
240 {
241 drawAtX = play.viewport.GetWidth() / 2 - fliTargetWidth / 2;
242 drawAtY = play.viewport.GetHeight() / 2 - fliTargetHeight / 2;
243 if (!gfxDriver->HasAcceleratedStretchAndFlip())
244 {
245 fli_target->StretchBlt(&gl_TheoraBuffer, RectWH(0, 0, gl_TheoraBuffer.GetWidth(), gl_TheoraBuffer.GetHeight()),
246 RectWH(drawAtX, drawAtY, fliTargetWidth, fliTargetHeight));
247 gfxDriver->UpdateDDBFromBitmap(fli_ddb, fli_target, false);
248 drawAtX = 0;
249 drawAtY = 0;
250 }
251 else
252 {
253 gfxDriver->UpdateDDBFromBitmap(fli_ddb, &gl_TheoraBuffer, false);
254 fli_ddb->SetStretch(fliTargetWidth, fliTargetHeight, false);
255 }
256 }
257 else
258 {
259 gfxDriver->UpdateDDBFromBitmap(fli_ddb, &gl_TheoraBuffer, false);
260 drawAtX = play.viewport.GetWidth() / 2 - gl_TheoraBuffer.GetWidth() / 2;
261 drawAtY = play.viewport.GetHeight() / 2 - gl_TheoraBuffer.GetHeight() / 2;
262 }
263
264 gfxDriver->DrawSprite(drawAtX, drawAtY, fli_ddb);
265 render_to_screen(virtual_screen, 0, 0);
266 update_polled_audio_and_crossfade ();
267
268 return check_if_user_input_should_cancel_video();
269 }
270
271 //
272 // Theora stream reader callbacks. We need these since we are creating PACKFILE
273 // stream by our own rules, and APEG library does not provide means to supply
274 // user's PACKFILE.
275 //
276 // Ironically, internally those callbacks will become a part of another
277 // PACKFILE's vtable, of APEG's library, so that will be a PACKFILE reading
278 // data using proxy PACKFILE through callback system...
279 //
280 class ApegStreamReader
281 {
282 public:
283 ApegStreamReader(const AssetPath path) : _path(path), _pf(NULL) {}
284 ~ApegStreamReader() { Close(); }
285
286 bool Open()
287 {
288 Close(); // PACKFILE cannot be rewinded, sadly
289 _pf = PackfileFromAsset(_path);
290 return _pf != NULL;
291 }
292
293 void Close()
294 {
295 if (_pf)
296 pack_fclose(_pf);
297 _pf = NULL;
298 }
299
300 int Read(void *buffer, int bytes)
301 {
302 return _pf ? pack_fread(buffer, bytes, _pf) : 0;
303 }
304
305 void Skip(int bytes)
306 {
307 if (_pf)
308 pack_fseek(_pf, bytes);
309 }
310
311 private:
312 AssetPath _path; // path to the asset
313 PACKFILE *_pf; // our stream
314 };
315
316 // Open stream for reading (return suggested cache buffer size).
317 int apeg_stream_init(void *ptr)
318 {
319 return ((ApegStreamReader*)ptr)->Open() ? F_BUF_SIZE : 0;
320 }
321
322 // Read requested number of bytes into provided buffer,
323 // return actual number of bytes managed to read.
324 int apeg_stream_read(void *buffer, int bytes, void *ptr)
325 {
326 return ((ApegStreamReader*)ptr)->Read(buffer, bytes);
327 }
328
329 // Skip requested number of bytes
330 void apeg_stream_skip(int bytes, void *ptr)
331 {
332 ((ApegStreamReader*)ptr)->Skip(bytes);
333 }
334
335 APEG_STREAM* get_theora_size(ApegStreamReader &reader, int *width, int *height)
336 {
337 APEG_STREAM* oggVid = apeg_open_stream_ex(&reader);
338 if (oggVid != NULL)
339 {
340 apeg_get_video_size(oggVid, width, height);
341 }
342 else
343 {
344 *width = 0;
345 *height = 0;
346 }
347 return oggVid;
348 }
349
350 void calculate_destination_size_maintain_aspect_ratio(int vidWidth, int vidHeight, int *targetWidth, int *targetHeight)
351 {
352 float aspectRatioVideo = (float)vidWidth / (float)vidHeight;
353 float aspectRatioScreen = (float)play.viewport.GetWidth() / (float)play.viewport.GetHeight();
354
355 if (aspectRatioVideo == aspectRatioScreen)
356 {
357 *targetWidth = play.viewport.GetWidth();
358 *targetHeight = play.viewport.GetHeight();
359 }
360 else if (aspectRatioVideo > aspectRatioScreen)
361 {
362 *targetWidth = play.viewport.GetWidth();
363 *targetHeight = (int)(((float)play.viewport.GetWidth() / aspectRatioVideo) + 0.5f);
364 }
365 else
366 {
367 *targetHeight = play.viewport.GetHeight();
368 *targetWidth = (float)play.viewport.GetHeight() * aspectRatioVideo;
369 }
370
371 }
372
373 void play_theora_video(const char *name, int skip, int flags)
374 {
375 ApegStreamReader reader(AssetPath("", name));
376 apeg_set_stream_reader(apeg_stream_init, apeg_stream_read, apeg_stream_skip);
377 apeg_set_display_depth(BitmapHelper::GetScreenBitmap()->GetColorDepth());
378 // we must disable length detection, otherwise it takes ages to start
379 // playing if the file is large because it seeks through the whole thing
380 apeg_disable_length_detection(TRUE);
381 // Disable framedrop because it can lead to the PSP not playing the video at all.
382 apeg_enable_framedrop(psp_video_framedrop);
383 update_polled_stuff_if_runtime();
384
385 stretch_flc = (flags % 10);
386 canabort = skip;
387 apeg_ignore_audio((flags >= 10) ? 1 : 0);
388
389 int videoWidth, videoHeight;
390 APEG_STREAM *oggVid = get_theora_size(reader, &videoWidth, &videoHeight);
391
392 if (videoWidth == 0)
393 {
394 Display("Unable to load theora video '%s'", name);
395 return;
396 }
397
398 if (flags < 10)
399 {
400 stop_all_sound_and_music();
401 }
402
403 //fli_buffer = BitmapHelper::CreateBitmap_(scsystem.coldepth, videoWidth, videoHeight);
404 calculate_destination_size_maintain_aspect_ratio(videoWidth, videoHeight, &fliTargetWidth, &fliTargetHeight);
405
406 if ((fliTargetWidth == videoWidth) && (fliTargetHeight == videoHeight) && (stretch_flc))
407 {
408 // don't need to stretch after all
409 stretch_flc = 0;
410 }
411
412 if ((stretch_flc) && (!gfxDriver->HasAcceleratedStretchAndFlip()))
413 {
414 fli_target = BitmapHelper::CreateBitmap(play.viewport.GetWidth(), play.viewport.GetHeight(), game.GetColorDepth());
415 fli_target->Clear();
416 fli_ddb = gfxDriver->CreateDDBFromBitmap(fli_target, false, true);
417 }
418 else
419 {
420 fli_ddb = NULL;
421 }
422
423 update_polled_stuff_if_runtime();
424
425 virtual_screen->Clear();
426
427 video_type = kVideoTheora;
428 if (apeg_play_apeg_stream(oggVid, NULL, 0, theora_playing_callback) == APEG_ERROR)
429 {
430 Display("Error playing theora video '%s'", name);
431 }
432 apeg_close_stream(oggVid);
433 video_type = kVideoNone;
434
435 //destroy_bitmap(fli_buffer);
436 delete fli_target;
437 gfxDriver->DestroyDDB(fli_ddb);
438 fli_target = NULL;
439 fli_ddb = NULL;
440 invalidate_screen();
441 }
442 // Theora player end
443
444 void video_on_gfxmode_changed()
445 {
446 if (video_type == kVideoFlic)
447 {
448 // If the FLIC video is playing, restore its palette
449 set_palette_range(fli_palette, 0, 255, 0);
450 }
451 }
452