102 Patches: Detours to the Rescue
C reference for DttR maintainers and modders.
Loading...
Searching...
No Matches
movies.c
Go to the documentation of this file.
1#include <dttr_pcdogs.h>
2
4#include "hooks_private.h"
5#include "movies_private.h"
6#include "sidecar_private.h"
7#include <dttr_log.h>
8#include <dttr_path.h>
9#include <sds.h>
10
11#include <SDL3/SDL.h>
12
13#include <libavcodec/avcodec.h>
14#include <libavformat/avformat.h>
15#include <libavutil/channel_layout.h>
16#include <libavutil/error.h>
17#include <libavutil/samplefmt.h>
18#include <libswresample/swresample.h>
19#include <libswscale/swscale.h>
20
21#include <stdbool.h>
22#include <stdint.h>
23#include <stdlib.h>
24
26
27// Replaces the game's blocking movie call with sidecar-managed FFmpeg playback.
29 const char *movie_path,
30 char use_alt_video_rect
31) {
32 return dttr_movies_hook_movie_play_file_callback(movie_path, use_alt_video_rect);
33}
34
35#define AUDIO_CHANNELS 2
36#define AUDIO_FORMAT AV_SAMPLE_FMT_S16
37#define AUDIO_QUEUE_LIMIT_MS 500
38#define AUDIO_DRAIN_LIMIT_MS 750
39
40typedef struct {
41 AVFormatContext *format;
42 AVCodecContext *video_codec;
43 AVCodecContext *audio_codec;
44 struct SwsContext *sws;
45 SwrContext *swr;
46 SDL_AudioStream *audio_device;
47 uint8_t *buffer;
50 int buf_w;
51 int buf_h;
54 bool hit_eof;
58 uint64_t start_ticks;
60 double pts_origin;
66
68 .video_stream = -1,
69 .audio_stream_index = -1,
70 .frame_duration = 1.0 / 15.0,
71 .result = DTTR_MOVIE_ENDED,
72};
73
74static AVFrame *video_frame = NULL;
75static AVFrame *audio_frame = NULL;
76static AVPacket *packet = NULL;
77
78// Reset decoder state but keep the final result.
81 .video_stream = -1,
82 .audio_stream_index = -1,
83 .frame_duration = 1.0 / 15.0,
84 .result = result,
85 };
86}
87
88// Formats an FFmpeg error code into a reusable buffer for sidecar log messages.
89static const char *movie_error_string(const int err) {
90 static char buf[AV_ERROR_MAX_STRING_SIZE];
91
92 av_strerror(err, buf, sizeof(buf));
93
94 return buf;
95}
96
97// Frees the converted BGRA frame buffer and marks the presentation frame as empty.
98static void reset_video_buffer() {
99 free(movie.buffer);
100 movie.buffer = NULL;
101 movie.buf_w = 0;
102 movie.buf_h = 0;
103 movie.buf_stride = 0;
104 movie.video_frame_ready = false;
105}
106
107// Releases the SDL audio stream and resampler used by decoded movie audio.
108static void close_audio() {
109 if (movie.audio_device) {
110 SDL_DestroyAudioStream(movie.audio_device);
111 movie.audio_device = NULL;
112 }
113
114 swr_free(&movie.swr);
115}
116
117// Releases all FFmpeg and SDL movie resources while keeping the playback result intact.
118static void close_movie() {
119 const dttr_movie_result result = movie.result;
120 close_audio();
121 sws_freeContext(movie.sws);
122 avcodec_free_context(&movie.video_codec);
123 avcodec_free_context(&movie.audio_codec);
124 avformat_close_input(&movie.format);
126 reset_movie_state(result);
127}
128
129// Allocates and opens an FFmpeg decoder for one selected movie stream.
130static AVCodecContext *open_codec(const AVStream *stream) {
131 const AVCodec *codec = avcodec_find_decoder(stream->codecpar->codec_id);
132 if (!codec) {
134 "Missing movie decoder for codec id %d",
135 stream->codecpar->codec_id
136 );
137 return NULL;
138 }
139
140 AVCodecContext *ctx = avcodec_alloc_context3(codec);
141 if (!ctx) {
142 DTTR_LOG_ERROR("Failed to allocate movie decoder context");
143 return NULL;
144 }
145
146 int err = avcodec_parameters_to_context(ctx, stream->codecpar);
147 if (err < 0) {
148 DTTR_LOG_ERROR("Failed to configure movie decoder: %s", movie_error_string(err));
149 avcodec_free_context(&ctx);
150 return NULL;
151 }
152
153 err = avcodec_open2(ctx, codec, NULL);
154 if (err < 0) {
156 "Failed to open movie decoder %s: %s",
157 codec->name,
159 );
160 avcodec_free_context(&ctx);
161 return NULL;
162 }
163
164 return ctx;
165}
166
167// Builds the resampler and SDL playback stream for movies that include audio.
168static bool prepare_audio() {
169 if (!movie.audio_codec) {
170 return true;
171 }
172
173 AVChannelLayout out_layout;
174 av_channel_layout_default(&out_layout, AUDIO_CHANNELS);
175
176 const int err = swr_alloc_set_opts2(
177 &movie.swr,
178 &out_layout,
180 movie.audio_codec->sample_rate,
181 &movie.audio_codec->ch_layout,
182 movie.audio_codec->sample_fmt,
183 movie.audio_codec->sample_rate,
184 0,
185 NULL
186 );
187 av_channel_layout_uninit(&out_layout);
188
189 if (err < 0 || !movie.swr) {
191 "Failed to allocate movie audio resampler: %s",
193 );
194 return false;
195 }
196
197 const int init_err = swr_init(movie.swr);
198 if (init_err < 0) {
200 "Failed to initialize movie audio resampler: %s",
201 movie_error_string(init_err)
202 );
203 return false;
204 }
205
206 const SDL_AudioSpec spec = {
207 .format = SDL_AUDIO_S16LE,
208 .channels = AUDIO_CHANNELS,
209 .freq = movie.audio_codec->sample_rate,
210 };
211
212 movie.audio_device = SDL_OpenAudioDeviceStream(
213 SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
214 &spec,
215 NULL,
216 NULL
217 );
218 if (!movie.audio_device) {
219 DTTR_LOG_ERROR("Failed to open movie audio stream: %s", SDL_GetError());
220 return false;
221 }
222
223 SDL_ResumeAudioStreamDevice(movie.audio_device);
224 return true;
225}
226
227// Opens the movie container, selects streams, and prepares decoders for sidecar playback.
228static bool open_movie(const char *path) {
229 int err = avformat_open_input(&movie.format, path, NULL, NULL);
230 if (err < 0) {
231 DTTR_LOG_ERROR("Failed to open movie %s: %s", path, movie_error_string(err));
232 return false;
233 }
234
235 err = avformat_find_stream_info(movie.format, NULL);
236 if (err < 0) {
237 DTTR_LOG_ERROR("Failed to read movie stream info: %s", movie_error_string(err));
238 return false;
239 }
240
241 movie.video_stream = av_find_best_stream(
242 movie.format,
243 AVMEDIA_TYPE_VIDEO,
244 -1,
245 -1,
246 NULL,
247 0
248 );
249 if (movie.video_stream < 0) {
251 "Movie has no playable video stream: %s",
252 movie_error_string(movie.video_stream)
253 );
254 return false;
255 }
256
257 AVStream *const video_stream = movie.format->streams[movie.video_stream];
258 movie.video_codec = open_codec(video_stream);
259 if (!movie.video_codec) {
260 return false;
261 }
262
263 movie.audio_stream_index = av_find_best_stream(
264 movie.format,
265 AVMEDIA_TYPE_AUDIO,
266 -1,
267 movie.video_stream,
268 NULL,
269 0
270 );
271 if (movie.audio_stream_index >= 0) {
272 AVStream *const audio_stream = movie.format->streams[movie.audio_stream_index];
273 movie.audio_codec = open_codec(audio_stream);
274 if (!movie.audio_codec || !prepare_audio()) {
275 return false;
276 }
277 }
278
279 AVRational rate = av_guess_frame_rate(movie.format, video_stream, NULL);
280 if (rate.num > 0 && rate.den > 0) {
281 movie.frame_duration = av_q2d((AVRational){rate.den, rate.num});
282 }
283
284 return true;
285}
286
287// Converts a decoded video timestamp into seconds using the stream time base.
288static double video_pts_seconds(const int64_t pts) {
289 if (pts == AV_NOPTS_VALUE) {
290 return movie.has_timing_origin ? movie.last_video_pts + movie.frame_duration
291 : 0.0;
292 }
293
294 const AVStream *stream = movie.format->streams[movie.video_stream];
295 return (double)pts * av_q2d(stream->time_base);
296}
297
298// Establishes the movie clock origin and next presentation timestamp for video pacing.
299static void set_next_video_time(const double pts) {
300 if (!movie.has_timing_origin) {
301 movie.start_ticks = SDL_GetTicks();
302 movie.pts_origin = pts;
303 movie.has_timing_origin = true;
304 }
305
306 movie.next_video_time = pts - movie.pts_origin;
307 if (movie.next_video_time < 0.0) {
308 movie.next_video_time = 0.0;
309 }
310
311 movie.last_video_pts = pts;
312}
313
314// Chooses the best available frame timestamp before scheduling presentation.
315static double frame_pts_seconds(const AVFrame *frame) {
316 return video_pts_seconds(frame->best_effort_timestamp);
317}
318
319// Converts a packet timestamp into seconds for fallback movie pacing.
320static double packet_pts_seconds(const AVPacket *packet) {
321 return video_pts_seconds(packet->pts != AV_NOPTS_VALUE ? packet->pts : packet->dts);
322}
323
324// Converts a decoded frame to BGRA and records when the renderer should present it.
325static bool queue_video_frame(const AVFrame *frame) {
326 const int w = frame->width;
327 const int h = frame->height;
328 if (w <= 0 || h <= 0 || w > INT_MAX / 4) {
329 return false;
330 }
331
332 const int stride = w * 4;
333 if ((size_t)h > SIZE_MAX / (size_t)stride) {
334 return false;
335 }
336
337 const size_t size = (size_t)stride * (size_t)h;
338 if (w != movie.buf_w || h != movie.buf_h || stride != movie.buf_stride) {
339 uint8_t *new_buffer = realloc(movie.buffer, size);
340 if (!new_buffer) {
341 DTTR_LOG_ERROR("Failed to allocate %zu bytes for movie frame", size);
342 return false;
343 }
344
345 movie.buffer = new_buffer;
346 movie.buf_w = w;
347 movie.buf_h = h;
348 movie.buf_stride = stride;
349 DTTR_LOG_DEBUG("Video Format: %dx%d", movie.buf_w, movie.buf_h);
350 }
351
352 movie.sws = sws_getCachedContext(
353 movie.sws,
354 w,
355 h,
356 (enum AVPixelFormat)frame->format,
357 w,
358 h,
359 AV_PIX_FMT_BGRA,
360 SWS_BILINEAR,
361 NULL,
362 NULL,
363 NULL
364 );
365 if (!movie.sws) {
366 DTTR_LOG_ERROR("Failed to create movie video converter");
367 return false;
368 }
369
370 uint8_t *dst_data[] = {movie.buffer, NULL, NULL, NULL};
371 int dst_linesize[] = {movie.buf_stride, 0, 0, 0};
372 sws_scale(
373 movie.sws,
374 (const uint8_t *const *)frame->data,
375 frame->linesize,
376 0,
377 h,
378 dst_data,
379 dst_linesize
380 );
381
383 movie.video_frame_ready = true;
384 return true;
385}
386
387// Resamples decoded audio into the SDL stream while capping queued latency.
388static bool queue_audio_frame(const AVFrame *frame) {
389 if (!movie.audio_device || !movie.swr) {
390 return true;
391 }
392
393 const int out_samples = (int)swr_get_delay(movie.swr, movie.audio_codec->sample_rate)
394 + frame->nb_samples;
395 const int out_size = av_samples_get_buffer_size(
396 NULL,
398 out_samples,
400 1
401 );
402 if (out_size <= 0) {
403 return false;
404 }
405
406 uint8_t *out = av_malloc((size_t)out_size);
407 if (!out) {
408 DTTR_LOG_ERROR("Failed to allocate movie audio buffer");
409 return false;
410 }
411
412 uint8_t *out_planes[] = {out, NULL};
413 const int converted = swr_convert(
414 movie.swr,
415 out_planes,
416 out_samples,
417 (const uint8_t **)frame->extended_data,
418 frame->nb_samples
419 );
420 if (converted < 0) {
421 DTTR_LOG_ERROR("Failed to convert movie audio: %s", movie_error_string(converted));
422 av_free(out);
423 return false;
424 }
425
426 const int converted_size = av_samples_get_buffer_size(
427 NULL,
429 converted,
431 1
432 );
433 if (converted_size > 0) {
434 SDL_PutAudioStreamData(movie.audio_device, out, converted_size);
435 }
436
437 av_free(out);
438
439 const int queue_limit = (movie.audio_codec->sample_rate * AUDIO_CHANNELS
440 * av_get_bytes_per_sample(AUDIO_FORMAT)
442 / 1000;
443 while (SDL_GetAudioStreamQueued(movie.audio_device) > queue_limit) {
444 SDL_Delay(1);
445 }
446
447 return true;
448}
449
450// Pulls decoded video frames until one is ready for presentation or the decoder drains.
451static bool receive_video_frame() {
452 while (!movie.video_frame_ready) {
453 const int err = avcodec_receive_frame(movie.video_codec, video_frame);
454 if (err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
455 return false;
456 }
457
458 if (err < 0) {
459 DTTR_LOG_ERROR("Failed to decode movie video: %s", movie_error_string(err));
460 movie.result = DTTR_MOVIE_ENDED;
461 return false;
462 }
463
465 movie.result = DTTR_MOVIE_ENDED;
466 }
467
468 av_frame_unref(video_frame);
469 }
470
471 return true;
472}
473
474// Drains available audio frames so playback stays ahead of the video clock.
475static void receive_audio_frames() {
476 if (!movie.audio_codec) {
477 return;
478 }
479
480 for (;;) {
481 const int err = avcodec_receive_frame(movie.audio_codec, audio_frame);
482 if (err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
483 return;
484 }
485
486 if (err < 0) {
487 DTTR_LOG_WARN("Failed to decode movie audio: %s", movie_error_string(err));
488 return;
489 }
490
492 DTTR_LOG_WARN("Failed to queue movie audio frame");
493 av_frame_unref(audio_frame);
494 return;
495 }
496
497 av_frame_unref(audio_frame);
498 }
499}
500
501// Waits for queued audio to finish after video reaches end-of-stream.
502static bool drain_eof() {
503 if (!movie.video_flushed) {
504 avcodec_send_packet(movie.video_codec, NULL);
505 movie.video_flushed = true;
506 return true;
507 }
508
509 if (movie.audio_codec && !movie.audio_flushed) {
510 avcodec_send_packet(movie.audio_codec, NULL);
511 movie.audio_flushed = true;
513 if (movie.audio_device) {
514 SDL_FlushAudioStream(movie.audio_device);
515 movie.audio_drain_deadline_ticks = SDL_GetTicks() + AUDIO_DRAIN_LIMIT_MS;
516 }
517 }
518
519 if (movie.audio_device) {
520 const int queued = SDL_GetAudioStreamQueued(movie.audio_device);
521 if (queued > 0 && SDL_GetTicks() < movie.audio_drain_deadline_ticks) {
522 SDL_Delay(1);
523 return false;
524 }
525
526 if (queued > 0) {
527 DTTR_LOG_WARN("Movie audio drain timed out with %d bytes queued", queued);
528 }
529 }
530
531 movie.result = DTTR_MOVIE_ENDED;
532 return false;
533}
534
535// Sends the current packet to the matching decoder.
536static void send_packet() {
537 if (packet->stream_index == movie.video_stream) {
538 if (packet->size <= 0) {
539 if (movie.buffer) {
541 movie.video_frame_ready = true;
542 }
543
544 return;
545 }
546
547 const int err = avcodec_send_packet(movie.video_codec, packet);
548 if (err < 0 && err != AVERROR(EAGAIN)) {
550 "Failed to submit movie video packet: %s",
552 );
553 movie.result = DTTR_MOVIE_ENDED;
554 }
555
556 return;
557 }
558
559 if (packet->stream_index != movie.audio_stream_index || !movie.audio_codec) {
560 return;
561 }
562
563 const int err = avcodec_send_packet(movie.audio_codec, packet);
564 if (err < 0 && err != AVERROR(EAGAIN)) {
565 DTTR_LOG_WARN("Failed to submit movie audio packet: %s", movie_error_string(err));
566 }
567}
568
569// Decode until there is a frame to present.
571 while (movie.result == DTTR_MOVIE_PLAYING && !movie.video_frame_ready) {
572 if (receive_video_frame()) {
573 return true;
574 }
575
577
578 if (movie.hit_eof) {
579 if (drain_eof()) {
580 continue;
581 }
582
583 return false;
584 }
585
586 const int err = av_read_frame(movie.format, packet);
587 if (err == AVERROR_EOF) {
588 movie.hit_eof = true;
589 continue;
590 }
591
592 if (err < 0) {
593 DTTR_LOG_ERROR("Failed to read movie packet: %s", movie_error_string(err));
594 movie.result = DTTR_MOVIE_ENDED;
595 return false;
596 }
597
598 send_packet();
599 av_packet_unref(packet);
600 }
601
602 return movie.video_frame_ready;
603}
604
605// Allocates reusable FFmpeg frames and packet storage for movie playback.
607 video_frame = av_frame_alloc();
608 audio_frame = av_frame_alloc();
609 packet = av_packet_alloc();
610 if (!video_frame || !audio_frame || !packet) {
611 DTTR_LOG_ERROR("Failed to allocate movie playback state");
612 }
613}
614
615// Installs the movie patch group so sidecar playback replaces the original routine.
617 const DTTR_PCDOGS_T_Patch_Spec movie_patches[] = {
619 };
620
622 ctx,
623 "sidecar/movie",
624 movie_patches,
625 DTTR_ARRAY_COUNT(movie_patches),
627 );
628}
629
633
634// Releases persistent FFmpeg frame and packet storage during sidecar shutdown.
636 close_movie();
637 av_packet_free(&packet);
638 av_frame_free(&audio_frame);
639 av_frame_free(&video_frame);
640 DTTR_LOG_INFO("Released movie playback state");
641}
642
643// Resolves and opens a movie file, then marks playback active when decoders are ready.
644void dttr_movies_start(const char *path) {
645 if (!video_frame || !audio_frame || !packet) {
646 DTTR_LOG_ERROR("Missing movie playback state");
647 movie.result = DTTR_MOVIE_ENDED;
648 return;
649 }
650
651 close_movie();
652
653 sds abs_path = dttr_game_data_resolve_media_path(path);
654 DTTR_LOG_INFO("Playing movie %s", abs_path);
655
656 if (!open_movie(abs_path)) {
657 sdsfree(abs_path);
658 close_movie();
659 movie.result = DTTR_MOVIE_ENDED;
660 return;
661 }
662
663 sdsfree(abs_path);
664 movie.result = DTTR_MOVIE_PLAYING;
665}
666
667// Decodes and presents movie frames on the sidecar loop according to the video clock.
669 if (movie.result != DTTR_MOVIE_PLAYING) {
670 return;
671 }
672
673 if (!movie.video_frame_ready && !decode_until_video_frame()) {
674 return;
675 }
676
677 if (!movie.video_frame_ready) {
678 return;
679 }
680
681 const double elapsed = (double)(SDL_GetTicks() - movie.start_ticks) / 1000.0;
682 if (elapsed + 0.001 < movie.next_video_time) {
683 SDL_Delay(1);
684 return;
685 }
686
688 movie.buffer,
689 movie.buf_w,
690 movie.buf_h,
691 movie.buf_stride
692 )) {
694 "Failed to present movie frame (%dx%d stride=%d)",
695 movie.buf_w,
696 movie.buf_h,
697 movie.buf_stride
698 );
699 }
700
701 movie.video_frame_ready = false;
702}
703
704// Handles skip, quit, and gamepad input while a sidecar movie is playing.
706 if (movie.result != DTTR_MOVIE_PLAYING) {
707 return false;
708 }
709
710 if (event->type == SDL_EVENT_KEY_DOWN && !event->key.repeat) {
711 if (event->key.scancode == SDL_SCANCODE_ESCAPE) {
712 movie.result = DTTR_MOVIE_ESCAPE;
713 return true;
714 }
715
716 if (event->key.scancode == SDL_SCANCODE_RETURN) {
717 movie.result = DTTR_MOVIE_ENDED;
718 return true;
719 }
720
721 if (event->key.scancode == SDL_SCANCODE_F4 && (event->key.mod & SDL_KMOD_ALT)) {
722 movie.result = DTTR_MOVIE_QUIT;
723 return true;
724 }
725 }
726
727 if (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
728 movie.result = DTTR_MOVIE_ENDED;
729 return true;
730 }
731
732 if (event->type == SDL_EVENT_QUIT) {
733 movie.result = DTTR_MOVIE_QUIT;
734 return true;
735 }
736
737 return false;
738}
739
740// Stops playback, releases per-movie resources, and returns the final movie result.
742 const dttr_movie_result result = movie.result;
743 close_movie();
744 DTTR_LOG_INFO("Stopped movie with result %d", result);
745 return result;
746}
747
749 return movie.result == DTTR_MOVIE_PLAYING;
750}
751
752// Runs replacement movie playback to completion while pumping sidecar events.
754 const char *path,
755 const int32_t use_alt_rect
756) {
757 dttr_movies_start(path);
758
759 while (dttr_movies_is_playing()) {
762 }
763
764 return dttr_movies_stop();
765}
DTTR_Graphics_COM_Direct3DDevice7 DWORD block DTTR_Graphics_COM_Direct3DDevice7 DWORD block DTTR_Graphics_COM_Direct3DDevice7 void void void void DWORD f BOOL
size_t stride
void void * ctx
DTTR_Graphics_COM_DirectDraw7 *self DWORD DWORD h
void DWORD DWORD * free
DTTR_Graphics_COM_DirectDraw7 *self DWORD w
const DWORD size
void void DWORD HANDLE event
DTTR_Graphics_COM_DirectDrawSurface7 DWORD flags void NULL
DTTR_Result DTTR_Core_PatchGroupRelease(DTTR_Core_PatchGroup **group)
Definition core.c:657
#define DTTR_ARRAY_COUNT(array_)
Definition dttr_core.h:18
struct DTTR_Core_PatchGroup DTTR_Core_PatchGroup
Definition dttr_core.h:25
#define DTTR_LOG_DEBUG(...)
Definition dttr_log.h:28
#define DTTR_LOG_WARN(...)
Definition dttr_log.h:30
#define DTTR_LOG_INFO(...)
Definition dttr_log.h:29
#define DTTR_LOG_ERROR(...)
Definition dttr_log.h:31
union SDL_Event SDL_Event
Definition dttr_mods.h:20
DTTR_PCDOGS_API const struct dttr_pcdogs_function_accessor_Video_PlayMovieFile *const DTTR_PCDOGS_F_Video_PlayMovieFile
Accessor object for Video_PlayMovieFile.
void dttr_sidecar_poll_sdl_events()
Definition entrypoint.c:383
sds dttr_game_data_resolve_media_path(const char *relative)
Definition game_data.c:210
bool dttr_graphics_present_video_frame_bgra(const uint8_t *pixels, int width, int height, int stride)
Definition graphics.c:621
static void send_packet()
Definition movies.c:536
static AVPacket * packet
Definition movies.c:76
void dttr_movies_start(const char *path)
Definition movies.c:644
static DTTR_Core_PatchGroup * movie_patch_group
Definition movies.c:25
static bool queue_video_frame(const AVFrame *frame)
Definition movies.c:325
#define AUDIO_FORMAT
Definition movies.c:36
void dttr_movies_hooks_cleanup(const DTTR_Mods_Context *)
Definition movies.c:630
static bool prepare_audio()
Definition movies.c:168
static void reset_movie_state(dttr_movie_result result)
Definition movies.c:79
static bool decode_until_video_frame()
Definition movies.c:570
static void reset_video_buffer()
Definition movies.c:98
#define AUDIO_DRAIN_LIMIT_MS
Definition movies.c:38
int32_t dttr_movies_hook_movie_play_file_callback(const char *path, const int32_t use_alt_rect)
Definition movies.c:753
static AVFrame * audio_frame
Definition movies.c:75
static BOOL movie_play_file_detour(const char *movie_path, char use_alt_video_rect)
Definition movies.c:28
static movie_state movie
Definition movies.c:67
dttr_movie_result dttr_movies_stop()
Definition movies.c:741
void dttr_movies_cleanup()
Definition movies.c:635
#define AUDIO_QUEUE_LIMIT_MS
Definition movies.c:37
static bool drain_eof()
Definition movies.c:502
#define AUDIO_CHANNELS
Definition movies.c:35
static bool queue_audio_frame(const AVFrame *frame)
Definition movies.c:388
static void close_audio()
Definition movies.c:108
static void receive_audio_frames()
Definition movies.c:475
bool dttr_movies_is_playing()
Definition movies.c:748
void dttr_movies_init()
Definition movies.c:606
static bool receive_video_frame()
Definition movies.c:451
static void close_movie()
Definition movies.c:118
static bool open_movie(const char *path)
Definition movies.c:228
static AVCodecContext * open_codec(const AVStream *stream)
Definition movies.c:130
static void set_next_video_time(const double pts)
Definition movies.c:299
static double video_pts_seconds(const int64_t pts)
Definition movies.c:288
static const char * movie_error_string(const int err)
Definition movies.c:89
static double frame_pts_seconds(const AVFrame *frame)
Definition movies.c:315
static double packet_pts_seconds(const AVPacket *packet)
Definition movies.c:320
void dttr_movies_tick()
Definition movies.c:668
bool dttr_movies_hooks_init(const DTTR_Mods_Context *ctx)
Definition movies.c:616
bool dttr_movies_handle_event(const SDL_Event *event)
Definition movies.c:705
static AVFrame * video_frame
Definition movies.c:74
dttr_movie_result
@ DTTR_MOVIE_QUIT
@ DTTR_MOVIE_ENDED
@ DTTR_MOVIE_PLAYING
@ DTTR_MOVIE_ESCAPE
static bool dttr_sidecar_install_pcdogs_patch_group(const DTTR_Mods_Context *ctx, const char *label, const DTTR_PCDOGS_T_Patch_Spec *patches, size_t patch_count, DTTR_Core_PatchGroup **group)
int buf_stride
Definition movies.c:52
SDL_AudioStream * audio_device
Definition movies.c:46
bool video_flushed
Definition movies.c:55
double pts_origin
Definition movies.c:60
bool audio_flushed
Definition movies.c:56
SwrContext * swr
Definition movies.c:45
int audio_stream_index
Definition movies.c:49
uint64_t audio_drain_deadline_ticks
Definition movies.c:59
double frame_duration
Definition movies.c:63
AVCodecContext * video_codec
Definition movies.c:42
AVCodecContext * audio_codec
Definition movies.c:43
int video_stream
Definition movies.c:48
struct SwsContext * sws
Definition movies.c:44
int buf_w
Definition movies.c:50
bool video_frame_ready
Definition movies.c:53
dttr_movie_result result
Definition movies.c:64
double last_video_pts
Definition movies.c:62
uint8_t * buffer
Definition movies.c:47
bool has_timing_origin
Definition movies.c:57
int buf_h
Definition movies.c:51
uint64_t start_ticks
Definition movies.c:58
AVFormatContext * format
Definition movies.c:41
double next_video_time
Definition movies.c:61
bool hit_eof
Definition movies.c:54