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>
29 const char *movie_path,
30 char use_alt_video_rect
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
44 struct SwsContext *
sws;
69 .audio_stream_index = -1,
70 .frame_duration = 1.0 / 15.0,
82 .audio_stream_index = -1,
83 .frame_duration = 1.0 / 15.0,
90 static char buf[AV_ERROR_MAX_STRING_SIZE];
92 av_strerror(err, buf,
sizeof(buf));
103 movie.buf_stride = 0;
104 movie.video_frame_ready =
false;
109 if (
movie.audio_device) {
110 SDL_DestroyAudioStream(
movie.audio_device);
114 swr_free(&
movie.swr);
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);
131 const AVCodec *codec = avcodec_find_decoder(stream->codecpar->codec_id);
134 "Missing movie decoder for codec id %d",
135 stream->codecpar->codec_id
140 AVCodecContext *
ctx = avcodec_alloc_context3(codec);
146 int err = avcodec_parameters_to_context(
ctx, stream->codecpar);
149 avcodec_free_context(&
ctx);
153 err = avcodec_open2(
ctx, codec,
NULL);
156 "Failed to open movie decoder %s: %s",
160 avcodec_free_context(&
ctx);
169 if (!
movie.audio_codec) {
173 AVChannelLayout out_layout;
176 const int err = swr_alloc_set_opts2(
180 movie.audio_codec->sample_rate,
181 &
movie.audio_codec->ch_layout,
182 movie.audio_codec->sample_fmt,
183 movie.audio_codec->sample_rate,
187 av_channel_layout_uninit(&out_layout);
189 if (err < 0 || !
movie.swr) {
191 "Failed to allocate movie audio resampler: %s",
197 const int init_err = swr_init(
movie.swr);
200 "Failed to initialize movie audio resampler: %s",
206 const SDL_AudioSpec spec = {
207 .format = SDL_AUDIO_S16LE,
209 .freq =
movie.audio_codec->sample_rate,
212 movie.audio_device = SDL_OpenAudioDeviceStream(
213 SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
218 if (!
movie.audio_device) {
219 DTTR_LOG_ERROR(
"Failed to open movie audio stream: %s", SDL_GetError());
223 SDL_ResumeAudioStreamDevice(
movie.audio_device);
229 int err = avformat_open_input(&
movie.format, path,
NULL,
NULL);
235 err = avformat_find_stream_info(
movie.format,
NULL);
241 movie.video_stream = av_find_best_stream(
249 if (
movie.video_stream < 0) {
251 "Movie has no playable video stream: %s",
257 AVStream *
const video_stream =
movie.format->streams[
movie.video_stream];
259 if (!
movie.video_codec) {
263 movie.audio_stream_index = av_find_best_stream(
271 if (
movie.audio_stream_index >= 0) {
272 AVStream *
const audio_stream =
movie.format->streams[
movie.audio_stream_index];
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});
289 if (pts == AV_NOPTS_VALUE) {
290 return movie.has_timing_origin ?
movie.last_video_pts +
movie.frame_duration
294 const AVStream *stream =
movie.format->streams[
movie.video_stream];
295 return (
double)pts * av_q2d(stream->time_base);
300 if (!
movie.has_timing_origin) {
301 movie.start_ticks = SDL_GetTicks();
302 movie.pts_origin = pts;
303 movie.has_timing_origin =
true;
306 movie.next_video_time = pts -
movie.pts_origin;
307 if (
movie.next_video_time < 0.0) {
308 movie.next_video_time = 0.0;
311 movie.last_video_pts = pts;
326 const int w = frame->width;
327 const int h = frame->height;
333 if ((
size_t)
h > SIZE_MAX / (
size_t)
stride) {
339 uint8_t *new_buffer = realloc(
movie.buffer,
size);
345 movie.buffer = new_buffer;
352 movie.sws = sws_getCachedContext(
356 (
enum AVPixelFormat)frame->format,
371 int dst_linesize[] = {
movie.buf_stride, 0, 0, 0};
374 (
const uint8_t *
const *)frame->data,
383 movie.video_frame_ready =
true;
393 const int out_samples = (int)swr_get_delay(
movie.swr,
movie.audio_codec->sample_rate)
395 const int out_size = av_samples_get_buffer_size(
406 uint8_t *out = av_malloc((
size_t)out_size);
412 uint8_t *out_planes[] = {out,
NULL};
413 const int converted = swr_convert(
417 (
const uint8_t **)frame->extended_data,
426 const int converted_size = av_samples_get_buffer_size(
433 if (converted_size > 0) {
434 SDL_PutAudioStreamData(
movie.audio_device, out, converted_size);
443 while (SDL_GetAudioStreamQueued(
movie.audio_device) > queue_limit) {
452 while (!
movie.video_frame_ready) {
454 if (err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
476 if (!
movie.audio_codec) {
482 if (err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
503 if (!
movie.video_flushed) {
504 avcodec_send_packet(
movie.video_codec,
NULL);
505 movie.video_flushed =
true;
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);
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) {
527 DTTR_LOG_WARN(
"Movie audio drain timed out with %d bytes queued", queued);
541 movie.video_frame_ready =
true;
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",
559 if (
packet->stream_index !=
movie.audio_stream_index || !
movie.audio_codec) {
563 const int err = avcodec_send_packet(
movie.audio_codec,
packet);
564 if (err < 0 && err != AVERROR(EAGAIN)) {
586 const int err = av_read_frame(
movie.format,
packet);
587 if (err == AVERROR_EOF) {
588 movie.hit_eof =
true;
602 return movie.video_frame_ready;
609 packet = av_packet_alloc();
677 if (!
movie.video_frame_ready) {
681 const double elapsed = (double)(SDL_GetTicks() -
movie.start_ticks) / 1000.0;
682 if (elapsed + 0.001 <
movie.next_video_time) {
694 "Failed to present movie frame (%dx%d stride=%d)",
701 movie.video_frame_ready =
false;
710 if (
event->type == SDL_EVENT_KEY_DOWN && !
event->key.repeat) {
711 if (
event->key.scancode == SDL_SCANCODE_ESCAPE) {
716 if (
event->key.scancode == SDL_SCANCODE_RETURN) {
721 if (
event->key.scancode == SDL_SCANCODE_F4 && (
event->key.mod & SDL_KMOD_ALT)) {
727 if (
event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
732 if (
event->type == SDL_EVENT_QUIT) {
755 const int32_t use_alt_rect
DTTR_Graphics_COM_Direct3DDevice7 DWORD block DTTR_Graphics_COM_Direct3DDevice7 DWORD block DTTR_Graphics_COM_Direct3DDevice7 void void void void DWORD f BOOL
DTTR_Graphics_COM_DirectDraw7 *self DWORD DWORD h
DTTR_Graphics_COM_DirectDraw7 *self DWORD w
void void DWORD HANDLE event
DTTR_Graphics_COM_DirectDrawSurface7 DWORD flags void NULL
DTTR_Result DTTR_Core_PatchGroupRelease(DTTR_Core_PatchGroup **group)
#define DTTR_ARRAY_COUNT(array_)
struct DTTR_Core_PatchGroup DTTR_Core_PatchGroup
#define DTTR_LOG_DEBUG(...)
#define DTTR_LOG_WARN(...)
#define DTTR_LOG_INFO(...)
#define DTTR_LOG_ERROR(...)
union SDL_Event SDL_Event
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()
sds dttr_game_data_resolve_media_path(const char *relative)
bool dttr_graphics_present_video_frame_bgra(const uint8_t *pixels, int width, int height, int stride)
static void send_packet()
void dttr_movies_start(const char *path)
static DTTR_Core_PatchGroup * movie_patch_group
static bool queue_video_frame(const AVFrame *frame)
void dttr_movies_hooks_cleanup(const DTTR_Mods_Context *)
static bool prepare_audio()
static void reset_movie_state(dttr_movie_result result)
static bool decode_until_video_frame()
static void reset_video_buffer()
#define AUDIO_DRAIN_LIMIT_MS
int32_t dttr_movies_hook_movie_play_file_callback(const char *path, const int32_t use_alt_rect)
static AVFrame * audio_frame
static BOOL movie_play_file_detour(const char *movie_path, char use_alt_video_rect)
dttr_movie_result dttr_movies_stop()
void dttr_movies_cleanup()
#define AUDIO_QUEUE_LIMIT_MS
static bool queue_audio_frame(const AVFrame *frame)
static void close_audio()
static void receive_audio_frames()
bool dttr_movies_is_playing()
static bool receive_video_frame()
static void close_movie()
static bool open_movie(const char *path)
static AVCodecContext * open_codec(const AVStream *stream)
static void set_next_video_time(const double pts)
static double video_pts_seconds(const int64_t pts)
static const char * movie_error_string(const int err)
static double frame_pts_seconds(const AVFrame *frame)
static double packet_pts_seconds(const AVPacket *packet)
bool dttr_movies_hooks_init(const DTTR_Mods_Context *ctx)
bool dttr_movies_handle_event(const SDL_Event *event)
static AVFrame * video_frame
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)
SDL_AudioStream * audio_device
uint64_t audio_drain_deadline_ticks
AVCodecContext * video_codec
AVCodecContext * audio_codec