diff options
author | Jim <obs.jim@gmail.com> | 2023-02-04 01:33:40 -0800 |
---|---|---|
committer | Jim <obs.jim@gmail.com> | 2023-02-14 16:09:07 -0800 |
commit | e7fd24c85559e5f4f8646dbd0a421da2440e7f16 (patch) | |
tree | e0eaba576899706f4b2d726f53d356065998f597 | |
parent | be629b93cea68b2465c0ba053a37a9046afcb92d (diff) |
obs-transitions: Add option to preload stinger video to RAMannoying-abstraction-v2
Stingers -- and especially track matte stingers -- are currently subject
to real time decoding, which can be detrimental in a production
environment where a stinger video may not be able to decode in a timely
fashion.
To remedy this, this change adds an option to fully decode stingers
immediately and cache the decoded video/audio in RAM for playback to
greatly improve stinger performance.
-rw-r--r-- | deps/media-playback/CMakeLists.txt | 10 | ||||
-rw-r--r-- | deps/media-playback/media-playback/cache.c | 698 | ||||
-rw-r--r-- | deps/media-playback/media-playback/cache.h | 81 | ||||
-rw-r--r-- | deps/media-playback/media-playback/decode.c | 1 | ||||
-rw-r--r-- | deps/media-playback/media-playback/media-playback.c | 144 | ||||
-rw-r--r-- | deps/media-playback/media-playback/media-playback.h | 49 | ||||
-rw-r--r-- | deps/media-playback/media-playback/media.c | 104 | ||||
-rw-r--r-- | deps/media-playback/media-playback/media.h | 35 | ||||
-rw-r--r-- | plugins/obs-ffmpeg/obs-ffmpeg-source.c | 116 | ||||
-rw-r--r-- | plugins/obs-transitions/data/locale/en-US.ini | 1 | ||||
-rw-r--r-- | plugins/obs-transitions/transition-stinger.c | 10 |
11 files changed, 1140 insertions, 109 deletions
diff --git a/deps/media-playback/CMakeLists.txt b/deps/media-playback/CMakeLists.txt index e97d3004c..729675306 100644 --- a/deps/media-playback/CMakeLists.txt +++ b/deps/media-playback/CMakeLists.txt @@ -9,8 +9,14 @@ add_library(OBS::media-playback ALIAS media-playback) target_sources( media-playback - INTERFACE media-playback/media.c media-playback/media.h - media-playback/decode.c media-playback/decode.h + INTERFACE media-playback/media.c + media-playback/media.h + media-playback/cache.c + media-playback/cache.h + media-playback/decode.c + media-playback/decode.h + media-playback/media-playback.c + media-playback/media-playback.h media-playback/closest-format.h) target_link_libraries(media-playback INTERFACE FFmpeg::avcodec FFmpeg::avdevice diff --git a/deps/media-playback/media-playback/cache.c b/deps/media-playback/media-playback/cache.c new file mode 100644 index 000000000..3314ac075 --- /dev/null +++ b/deps/media-playback/media-playback/cache.c @@ -0,0 +1,698 @@ +#include <media-io/audio-io.h> +#include <util/platform.h> + +#include "media-playback.h" +#include "cache.h" +#include "media.h" + +extern bool mp_media_init2(mp_media_t *m); +extern bool mp_media_prepare_frames(mp_media_t *m); +extern bool mp_media_eof(mp_media_t *m); +extern void mp_media_next_video(mp_media_t *m, bool preload); +extern void mp_media_next_audio(mp_media_t *m); +extern bool mp_media_reset(mp_media_t *m); + +static bool mp_cache_reset(mp_cache_t *c); + +static int64_t base_sys_ts = 0; + +#define v_eof(c) (c->cur_v_idx == c->video_frames.num) +#define a_eof(c) (c->cur_a_idx == c->audio_segments.num) + +static inline int64_t mp_cache_get_next_min_pts(mp_cache_t *c) +{ + int64_t min_next_ns = 0x7FFFFFFFFFFFFFFFLL; + + if (c->has_video && !v_eof(c)) { + min_next_ns = c->next_v_ts; + } + if (c->has_audio && !a_eof(c) && c->next_a_ts < min_next_ns) { + min_next_ns = c->next_a_ts; + } + + return min_next_ns; +} + +static inline int64_t mp_cache_get_base_pts(mp_cache_t *c) +{ + int64_t base_ts = 0; + + if (c->has_video && c->next_v_ts > base_ts) + base_ts = c->next_v_ts; + if (c->has_audio && c->next_a_ts > base_ts) + base_ts = c->next_a_ts; + + return base_ts; +} + +static void reset_ts(mp_cache_t *c) +{ + c->base_ts += mp_cache_get_base_pts(c); + c->play_sys_ts = (int64_t)os_gettime_ns(); + c->start_ts = c->next_pts_ns = mp_cache_get_next_min_pts(c); + c->next_ns = 0; +} + +static inline bool mp_cache_sleep(mp_cache_t *c) +{ + bool timeout = false; + + if (!c->next_ns) { + c->next_ns = os_gettime_ns(); + } else { + const uint64_t t = os_gettime_ns(); + if (c->next_ns > t) { + const uint32_t delta_ms = + (uint32_t)((c->next_ns - t + 500000) / 1000000); + if (delta_ms > 0) { + static const uint32_t timeout_ms = 200; + timeout = delta_ms > timeout_ms; + os_sleep_ms(timeout ? timeout_ms : delta_ms); + } + } + } + + return timeout; +} + +static bool mp_cache_eof(mp_cache_t *c) +{ + bool v_ended = !c->has_video || v_eof(c); + bool a_ended = !c->has_audio || a_eof(c); + bool eof = v_ended && a_ended; + + if (!eof) + return false; + + pthread_mutex_lock(&c->mutex); + if (!c->looping) { + c->active = false; + c->stopping = true; + } + pthread_mutex_unlock(&c->mutex); + + mp_cache_reset(c); + return true; +} + +bool mp_cache_decode(mp_cache_t *c) +{ + mp_media_t *m = &c->m; + bool success = false; + + m->full_decode = true; + + mp_media_reset(m); + + while (!mp_media_eof(m)) { + if (m->has_video) + mp_media_next_video(m, false); + if (m->has_audio) + mp_media_next_audio(m); + + if (!mp_media_prepare_frames(m)) + goto fail; + } + + success = true; + + c->start_time = c->m.fmt->start_time; + if (c->start_time == AV_NOPTS_VALUE) + c->start_time = 0; + +fail: + mp_media_free(m); + return success; +} + +static void seek_to(mp_cache_t *c, int64_t pos) +{ + size_t new_v_idx = 0; + size_t new_a_idx = 0; + + if (pos > c->media_duration) { + blog(LOG_WARNING, "MP: Invalid seek position"); + return; + } + + if (c->has_video) { + struct obs_source_frame *v; + + for (size_t i = 0; i < c->video_frames.num; i++) { + v = &c->video_frames.array[i]; + new_v_idx = i; + if ((int64_t)v->timestamp >= pos) { + break; + } + } + + size_t next_idx = new_v_idx + 1; + if (next_idx == c->video_frames.num) { + c->next_v_ts = + (int64_t)v->timestamp + c->final_v_duration; + } else { + struct obs_source_frame *next = + &c->video_frames.array[next_idx]; + c->next_v_ts = (int64_t)next->timestamp; + } + } + if (c->has_audio) { + struct obs_source_audio *a; + for (size_t i = 0; i < c->audio_segments.num; i++) { + a = &c->audio_segments.array[i]; + new_a_idx = i; + if ((int64_t)a->timestamp >= pos) { + break; + } + } + + size_t next_idx = new_a_idx + 1; + if (next_idx == c->audio_segments.num) { + c->next_a_ts = + (int64_t)a->timestamp + c->final_a_duration; + } else { + struct obs_source_audio *next = + &c->audio_segments.array[next_idx]; + c->next_a_ts = (int64_t)next->timestamp; + } + } + + c->cur_v_idx = c->next_v_idx = new_v_idx; + c->cur_a_idx = c->next_a_idx = new_a_idx; +} + +/* maximum timestamp variance in nanoseconds */ +#define MAX_TS_VAR 2000000000LL + +static inline bool mp_media_can_play_video(mp_cache_t *c) +{ + return !v_eof(c) && (c->next_v_ts <= c->next_pts_ns || + (c->next_v_ts - c->next_pts_ns > MAX_TS_VAR)); +} + +static inline bool mp_media_can_play_audio(mp_cache_t *c) +{ + return !a_eof(c) && (c->next_a_ts <= c->next_pts_ns || + (c->next_a_ts - c->next_pts_ns > MAX_TS_VAR)); +} + +static inline void calc_next_v_ts(mp_cache_t *c, struct obs_source_frame *frame) +{ + int64_t offset; + if (c->next_v_idx < c->video_frames.num) { + struct obs_source_frame *next = + &c->video_frames.array[c->next_v_idx]; + offset = (int64_t)(next->timestamp - frame->timestamp); + } else { + offset = c->final_v_duration; + } + + c->next_v_ts += offset; +} + +static inline void calc_next_a_ts(mp_cache_t *c, struct obs_source_audio *audio) +{ + int64_t offset; + if (c->next_a_idx < c->audio_segments.num) { + struct obs_source_audio *next = + &c->audio_segments.array[c->next_a_idx]; + offset = (int64_t)(next->timestamp - audio->timestamp); + } else { + offset = c->final_a_duration; + } + + c->next_a_ts += offset; +} + +static void mp_cache_next_video(mp_cache_t *c, bool preload) +{ + /* eof check */ + if (c->next_v_idx == c->video_frames.num) { + if (mp_media_can_play_video(c)) + c->cur_v_idx = c->next_v_idx; + return; + } + + struct obs_source_frame *frame = &c->video_frames.array[c->next_v_idx]; + struct obs_source_frame dup = *frame; + + dup.timestamp = c->base_ts + dup.timestamp - c->start_ts + + c->play_sys_ts - base_sys_ts; + + if (!preload) { + if (!mp_media_can_play_video(c)) + return; + + if (c->v_cb) + c->v_cb(c->opaque, &dup); + + if (c->cur_v_idx < c->next_v_idx) + ++c->cur_v_idx; + ++c->next_v_idx; + calc_next_v_ts(c, frame); + } else { + if (c->seek_next_ts && c->v_seek_cb) { + c->v_seek_cb(c->opaque, &dup); + } else if (!c->request_preload) { + c->v_preload_cb(c->opaque, &dup); + } + } +} + +static void mp_cache_next_audio(mp_cache_t *c) +{ + /* eof check */ + if (c->next_a_idx == c->video_frames.num) { + if (mp_media_can_play_audio(c)) + c->cur_a_idx = c->next_a_idx; + return; + } + + if (!mp_media_can_play_audio(c)) + return; + + struct obs_source_audio *audio = + &c->audio_segments.array[c->next_a_idx]; + struct obs_source_audio dup = *audio; + + dup.timestamp = c->base_ts + dup.timestamp - c->start_ts + + c->play_sys_ts - base_sys_ts; + if (c->a_cb) + c->a_cb(c->opaque, &dup); + + if (c->cur_a_idx < c->next_a_idx) + ++c->cur_a_idx; + ++c->next_a_idx; + calc_next_a_ts(c, audio); +} + +static bool mp_cache_reset(mp_cache_t *c) +{ + bool stopping; + bool active; + + int64_t next_ts = mp_cache_get_base_pts(c); + int64_t offset = next_ts - c->next_pts_ns; + int64_t start_time = c->start_time; + + c->eof = false; + c->base_ts += next_ts; + c->seek_next_ts = false; + + seek_to(c, start_time); + + pthread_mutex_lock(&c->mutex); + stopping = c->stopping; + active = c->active; + c->stopping = false; + pthread_mutex_unlock(&c->mutex); + + if (c->has_video) { + size_t next_idx = c->video_frames.num > 1 ? 1 : 0; + c->cur_v_idx = c->next_v_idx = 0; + c->next_v_ts = c->video_frames.array[next_idx].timestamp; + } + if (c->has_audio) { + size_t next_idx = c->audio_segments.num > 1 ? 1 : 0; + c->cur_a_idx = c->next_a_idx = 0; + c->next_a_ts = c->audio_segments.array[next_idx].timestamp; + } + + if (active) { + if (!c->play_sys_ts) + c->play_sys_ts = (int64_t)os_gettime_ns(); + c->start_ts = c->next_pts_ns = mp_cache_get_next_min_pts(c); + if (c->next_ns) + c->next_ns += offset; + } else { + c->start_ts = c->next_pts_ns = mp_cache_get_next_min_pts(c); + c->play_sys_ts = (int64_t)os_gettime_ns(); + c->next_ns = 0; + } + + c->pause = false; + + if (!active && c->v_preload_cb) + mp_cache_next_video(c, true); + if (stopping && c->stop_cb) + c->stop_cb(c->opaque); + return true; +} + +static void mp_cache_calc_next_ns(mp_cache_t *c) +{ + int64_t min_next_ns = mp_cache_get_next_min_pts(c); + int64_t delta = min_next_ns - c->next_pts_ns; + + if (c->seek_next_ts) { + delta = 0; + c->seek_next_ts = false; + } else { +#ifdef _DEBUG + assert(delta >= 0); +#endif + if (delta < 0) + delta = 0; + if (delta > 3000000000) + delta = 0; + } + + c->next_ns += delta; + c->next_pts_ns = min_next_ns; +} + +static inline bool mp_cache_thread(mp_cache_t *c) +{ + os_set_thread_name("mp_cache_thread"); + + if (!mp_cache_decode(c)) { + return false; + } + + for (;;) { + bool reset, kill, is_active, seek, pause, reset_time, + preload_frame; + int64_t seek_pos; + bool timeout = false; + + pthread_mutex_lock(&c->mutex); + is_active = c->active; + pause = c->pause; + pthread_mutex_unlock(&c->mutex); + + if (!is_active || pause) { + if (os_sem_wait(c->sem) < 0) + return false; + if (pause) + reset_ts(c); + } else { + timeout = mp_cache_sleep(c); + } + + pthread_mutex_lock(&c->mutex); + + reset = c->reset; + kill = c->kill; + c->reset = false; + c->kill = false; + + preload_frame = c->preload_frame; + pause = c->pause; + seek_pos = c->seek_pos; + seek = c->seek; + reset_time = c->reset_ts; + c->preload_frame = false; + c->seek = false; + c->reset_ts = false; + + pthread_mutex_unlock(&c->mutex); + + if (kill) { + break; + } + if (reset) { + mp_cache_reset(c); + continue; + } + + if (seek) { + c->seek_next_ts = true; + seek_to(c, seek_pos); + continue; + } + + if (reset_time) { + reset_ts(c); + continue; + } + + if (pause) + continue; + + if (preload_frame) + c->v_preload_cb(c->opaque, &c->video_frames.array[0]); + + /* frames are ready */ + if (is_active && !timeout) { + if (c->has_video) + mp_cache_next_video(c, false); + if (c->has_audio) + mp_cache_next_audio(c); + + if (mp_cache_eof(c)) + continue; + + mp_cache_calc_next_ns(c); + } + } + + return true; +} + +static void *mp_cache_thread_start(void *opaque) +{ + mp_cache_t *c = opaque; + + if (!mp_cache_thread(c)) { + if (c->stop_cb) { + c->stop_cb(c->opaque); + } + } + + return NULL; +} + +static void fill_video(void *data, struct obs_source_frame *frame) +{ + mp_cache_t *c = data; + struct obs_source_frame dup; + + obs_source_frame_init(&dup, frame->format, frame->width, frame->height); + obs_source_frame_copy(&dup, frame); + + dup.timestamp = frame->timestamp; + + c->final_v_duration = c->m.v.last_duration; + + da_push_back(c->video_frames, &dup); +} + +static void fill_audio(void *data, struct obs_source_audio *audio) +{ + mp_cache_t *c = data; + struct obs_source_audio dup = *audio; + + size_t size = + get_total_audio_size(dup.format, dup.speakers, dup.frames); + dup.data[0] = bmalloc(size); + + size_t planes = get_audio_planes(dup.format, dup.speakers); + if (planes > 1) { + size = get_audio_bytes_per_channel(dup.format) * dup.frames; + uint8_t *out = (uint8_t *)dup.data[0]; + + for (size_t i = 0; i < planes; i++) { + if (i > 0) + dup.data[i] = out; + + memcpy(out, audio->data[i], size); + out += size; + } + } else { + memcpy((uint8_t *)dup.data[0], audio->data[0], size); + } + + c->final_a_duration = c->m.a.last_duration; + + da_push_back(c->audio_segments, &dup); +} + +static inline bool mp_cache_init_internal(mp_cache_t *c, + const struct mp_media_info *info) +{ + if (pthread_mutex_init(&c->mutex, NULL) != 0) { + blog(LOG_WARNING, "MP: Failed to init mutex"); + return false; + } + if (os_sem_init(&c->sem, 0) != 0) { + blog(LOG_WARNING, "MP: Failed to init semaphore"); + return false; + } + + c->path = info->path ? bstrdup(info->path) : NULL; + c->format_name = info->format ? bstrdup(info->format) : NULL; + + if (pthread_create(&c->thread, NULL, mp_cache_thread_start, c) != 0) { + blog(LOG_WARNING, "MP: Could not create media thread"); + return false; + } + + c->thread_valid = true; + return true; +} + +bool mp_cache_init(mp_cache_t *c, const struct mp_media_info *info) +{ + struct mp_media_info info2 = *info; + + info2.opaque = c; + info2.v_cb = fill_video; + info2.a_cb = fill_audio; + info2.v_preload_cb = NULL; + info2.v_seek_cb = NULL; + info2.stop_cb = NULL; + info2.full_decode = true; + + mp_media_t *m = &c->m; + if (!mp_media_init(m, &info2)) { + mp_cache_free(c); + return false; + } + if (!mp_media_init2(m)) { + mp_cache_free(c); + return false; + } + + pthread_mutex_init_value(&c->mutex); + c->opaque = info->opaque; + c->v_cb = info->v_cb; + c->a_cb = info->a_cb; + c->stop_cb = info->stop_cb; + c->ffmpeg_options = info->ffmpeg_options; + c->v_seek_cb = info->v_seek_cb; + c->v_preload_cb = info->v_preload_cb; + c->request_preload = info->request_preload; + c->speed = info->speed; + c->media_duration = m->fmt->duration; + + c->has_video = m->has_video; + c->has_audio = m->has_audio; + + if (!base_sys_ts) + base_sys_ts = (int64_t)os_gettime_ns(); + + if (!mp_cache_init_internal(c, info)) { + mp_cache_free(c); + return false; + } + + return true; +} + +static void mp_kill_thread(mp_cache_t *c) +{ + if (c->thread_valid) { + pthread_mutex_lock(&c->mutex); + c->kill = true; + pthread_mutex_unlock(&c->mutex); + os_sem_post(c->sem); + + pthread_join(c->thread, NULL); + } +} + +void mp_cache_free(mp_cache_t *c) +{ + if (!c) + return; + + mp_cache_stop(c); + mp_kill_thread(c); + + if (c->m.fmt) + mp_media_free(&c->m); + + for (size_t i = 0; i < c->video_frames.num; i++) { + struct obs_source_frame *f = &c->video_frames.array[i]; + obs_source_frame_free(f); + } + for (size_t i = 0; i < c->audio_segments.num; i++) { + struct obs_source_audio *a = &c->audio_segments.array[i]; + bfree((void *)a->data[0]); + } + da_free(c->video_frames); + da_free(c->audio_segments); + + bfree(c->path); + bfree(c->format_name); + pthread_mutex_destroy(&c->mutex); + os_sem_destroy(c->sem); + memset(c, 0, sizeof(*c)); +} + +void mp_cache_play(mp_cache_t *c, bool loop) +{ + pthread_mutex_lock(&c->mutex); + + if (c->active) + c->reset = true; + + c->looping = loop; + c->active = true; + + pthread_mutex_unlock(&c->mutex); + + os_sem_post(c->sem); +} + +void mp_cache_play_pause(mp_cache_t *c, bool pause) +{ + pthread_mutex_lock(&c->mutex); + if (c->active) { + c->pause = pause; + c->reset_ts = !pause; + } + pthread_mutex_unlock(&c->mutex); + + os_sem_post(c->sem); +} + +void mp_cache_stop(mp_cache_t *c) +{ + pthread_mutex_lock(&c->mutex); + if (c->active) { + c->reset = true; + c->active = false; + c->stopping = true; + } + pthread_mutex_unlock(&c->mutex); + + os_sem_post(c->sem); +} + +void mp_cache_preload_frame(mp_cache_t *c) +{ + if (c->request_preload && c->thread_valid && c->v_preload_cb) { + pthread_mutex_lock(&c->mutex); + c->preload_frame = true; + pthread_mutex_unlock(&c->mutex); + os_sem_post(c->sem); + } +} + +int64_t mp_cache_get_current_time(mp_cache_t *c) +{ + return mp_cache_get_base_pts(c) * (int64_t)c->speed / 100000000LL; +} + +void mp_cache_seek(mp_cache_t *c, int64_t pos) +{ + pthread_mutex_lock(&c->mutex); + if (c->active) { + c->seek = true; + c->seek_pos = pos * 1000; + } + pthread_mutex_unlock(&c->mutex); + + os_sem_post(c->sem); +} + +int64_t mp_cache_get_frames(mp_cache_t *c) +{ + return c->video_frames.num; +} + +int64_t mp_cache_get_duration(mp_cache_t *c) +{ + return c->media_duration; +} diff --git a/deps/media-playback/media-playback/cache.h b/deps/media-playback/media-playback/cache.h new file mode 100644 index 000000000..d5ef506ea --- /dev/null +++ b/deps/media-playback/media-playback/cache.h @@ -0,0 +1,81 @@ +#pragma once + +#include <util/threading.h> +#include <util/darray.h> +#include <obs.h> + +#include "media.h" + +struct mp_cache { + mp_video_cb v_preload_cb; + mp_video_cb v_seek_cb; + mp_stop_cb stop_cb; + mp_video_cb v_cb; + mp_audio_cb a_cb; + void *opaque; + bool request_preload; + bool has_video; + bool has_audio; + + char *path; + char *format_name; + char *ffmpeg_options; + int buffering; + int speed; + + pthread_mutex_t mutex; + os_sem_t *sem; + bool preload_frame; + bool stopping; + bool looping; + bool active; + bool reset; + bool kill; + + bool thread_valid; + pthread_t thread; + + DARRAY(struct obs_source_frame) video_frames; + DARRAY(struct obs_source_audio) audio_segments; + + size_t cur_v_idx; + size_t cur_a_idx; + size_t next_v_idx; + size_t next_a_idx; + int64_t next_v_ts; + int64_t next_a_ts; + + int64_t final_v_duration; + int64_t final_a_duration; + + int64_t play_sys_ts; + int64_t next_pts_ns; + uint64_t next_ns; + int64_t start_ts; + int64_t base_ts; + + bool pause; + bool reset_ts; + bool seek; + bool seek_next_ts; + bool eof; + int64_t seek_pos; + int64_t start_time; + int64_t media_duration; + + mp_media_t m; +}; + +typedef struct mp_cache mp_cache_t; + +extern bool mp_cache_init(mp_cache_t *c, const struct mp_media_info *info); +extern void mp_cache_free(mp_cache_t *c); + +extern void mp_cache_play(mp_cache_t *c, bool loop); +extern void mp_cache_play_pause(mp_cache_t *c, bool pause); +extern void mp_cache_stop(mp_cache_t *c); +extern void mp_cache_preload_frame(mp_cache_t *c); +extern int64_t mp_cache_get_current_time(mp_cache_t *c); +extern void mp_cache_seek(mp_cache_t *c, int64_t pos); +extern int64_t mp_cache_get_frames(mp_cache_t *c); +extern int64_t mp_cache_get_duration(mp_cache_t *c); diff --git a/deps/media-playback/media-playback/decode.c b/deps/media-playback/media-playback/decode.c index 059687b6d..f63cf538f 100644 --- a/deps/media-playback/media-playback/decode.c +++ b/deps/media-playback/media-playback/decode.c @@ -16,6 +16,7 @@ #include "decode.h" +#include "media-playback.h" #include "media.h" #include <libavutil/mastering_display_metadata.h> diff --git a/deps/media-playback/media-playback/media-playback.c b/deps/media-playback/media-playback/media-playback.c new file mode 100644 index 000000000..2f6c57b61 --- /dev/null +++ b/deps/media-playback/media-playback/media-playback.c @@ -0,0 +1,144 @@ +#include "media-playback.h" +#include "media.h" +#include "cache.h" + +struct media_playback { + bool is_cached; + union { + mp_media_t media; + mp_cache_t cache; + }; +}; + +media_playback_t *media_playback_create(const struct mp_media_info *info) +{ + media_playback_t *mp = bzalloc(sizeof(*mp)); + mp->is_cached = info->is_local_file && info->full_decode; + + if ((mp->is_cached && !mp_cache_init(&mp->cache, info)) || + (!mp->is_cached && !mp_media_init(&mp->media, info))) { + bfree(mp); + return NULL; + } + + return mp; +} + +void media_playback_destroy(media_playback_t *mp) +{ + if (!mp) + return; + + if (mp->is_cached) + mp_cache_free(&mp->cache); + else + mp_media_free(&mp->media); + bfree(mp); +} + +void media_playback_play(media_playback_t *mp, bool looping, bool reconnecting) +{ + if (!mp) + return; + + if (mp->is_cached) + mp_cache_play(&mp->cache, looping); + else + mp_media_play(&mp->media, looping, reconnecting); +} + +void media_playback_play_pause(media_playback_t *mp, bool pause) +{ + if (!mp) + return; + + if (mp->is_cached) + mp_cache_play_pause(&mp->cache, pause); + else + mp_media_play_pause(&mp->media, pause); +} + +void media_playback_stop(media_playback_t *mp) +{ + if (!mp) + return; + + if (mp->is_cached) + mp_cache_stop(&mp->cache); + else + mp_media_stop(&mp->media); +} + +void media_playback_preload_frame(media_playback_t *mp) +{ + if (!mp) + return; + + if (mp->is_cached) + mp_cache_preload_frame(&mp->cache); + else + mp_media_preload_frame(&mp->media); +} + +int64_t media_playback_get_current_time(media_playback_t *mp) +{ + if (!mp) + return 0; + + if (mp->is_cached) + return mp_cache_get_current_time(&mp->cache); + else + return mp_media_get_current_time(&mp->media); +} + +void media_playback_seek(media_playback_t *mp, int64_t pos) +{ + if (mp->is_cached) + mp_cache_seek(&mp->cache, pos); + else + mp_media_seek(&mp->media, pos); +} + +int64_t media_playback_get_frames(media_playback_t *mp) +{ + if (!mp) + return 0; + + if (mp->is_cached) + return mp_cache_get_frames(&mp->cache); + else + return mp_media_get_frames(&mp->media); +} + +int64_t media_playback_get_duration(media_playback_t *mp) +{ + if (!mp) + return 0; + + if (mp->is_cached) + return mp_cache_get_duration(&mp->cache); + else + return mp_media_get_duration(&mp->media); +} + +bool media_playback_has_video(media_playback_t *mp) +{ + if (!mp) + return false; + + if (mp->is_cached) + return mp->cache.has_video; + else + return mp->media.has_video; +} + +bool media_playback_has_audio(media_playback_t *mp) +{ + if (!mp) + return false; + + if (mp->is_cached) + return mp->cache.has_audio; + else + return mp->media.has_audio; +} diff --git a/deps/media-playback/media-playback/media-playback.h b/deps/media-playback/media-playback/media-playback.h new file mode 100644 index 000000000..bbb07e495 --- /dev/null +++ b/deps/media-playback/media-playback/media-playback.h @@ -0,0 +1,49 @@ +#pragma once + +#include <obs.h> + +struct media_playback; +typedef struct media_playback media_playback_t; + +typedef void (*mp_video_cb)(void *opaque, struct obs_source_frame *frame); +typedef void (*mp_audio_cb)(void *opaque, struct obs_source_audio *audio); +typedef void (*mp_stop_cb)(void *opaque); + +struct mp_media_info { + void *opaque; + + mp_video_cb v_cb; + mp_video_cb v_preload_cb; + mp_video_cb v_seek_cb; + mp_audio_cb a_cb; + mp_stop_cb stop_cb; + + const char *path; + const char *format; + char *ffmpeg_options; + int buffering; + int speed; + enum video_range_type force_range; + bool is_linear_alpha; + bool hardware_decoding; + bool is_local_file; + bool reconnecting; + bool request_preload; + bool full_decode; +}; + +extern media_playback_t * +media_playback_create(const struct mp_media_info *info); +extern void media_playback_destroy(media_playback_t *mp); + +extern void media_playback_play(media_playback_t *mp, bool looping, + bool reconnecting); +extern void media_playback_play_pause(media_playback_t *mp, bool pause); +extern void media_playback_stop(media_playback_t *mp); +extern void media_playback_preload_frame(media_playback_t *mp); +extern int64_t media_playback_get_current_time(media_playback_t *mp); +extern void media_playback_seek(media_playback_t *mp, int64_t pos); +extern int64_t media_playback_get_frames(media_playback_t *mp); +extern int64_t media_playback_get_duration(media_playback_t *mp); +extern bool media_playback_has_video(media_playback_t *mp); +extern bool media_playback_has_audio(media_playback_t *mp); diff --git a/deps/media-playback/media-playback/media.c b/deps/media-playback/media-playback/media.c index ea7b3b29a..14b80b9ed 100644 --- a/deps/media-playback/media-playback/media.c +++ b/deps/media-playback/media-playback/media.c @@ -14,11 +14,11 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include <obs.h> #include <util/platform.h> #include <assert.h> +#include "media-playback.h" #include "media.h" #include "closest-format.h" @@ -275,7 +275,7 @@ static bool mp_media_init_scaling(mp_media_t *m) return true; } -static bool mp_media_prepare_frames(mp_media_t *m) +bool mp_media_prepare_frames(mp_media_t *m) { bool actively_seeking = m->seek_next_ts && m->pause; @@ -344,11 +344,13 @@ static inline int64_t mp_media_get_base_pts(mp_media_t *m) static inline bool mp_media_can_play_frame(mp_media_t *m, struct mp_decode *d) { + if (m->full_decode) + return d->frame_ready; return d->frame_ready && (d->frame_pts <= m->next_pts_ns || (d->frame_pts - m->next_pts_ns > MAX_TS_VAR)); } -static void mp_media_next_audio(mp_media_t *m) +void mp_media_next_audio(mp_media_t *m) { struct mp_decode *d = &m->a; struct obs_source_audio audio = {0}; @@ -374,9 +376,10 @@ static void mp_media_next_audio(mp_media_t *m) audio.speakers = convert_speaker_layout(channels); audio.format = convert_sample_format(f->format); audio.frames = f->nb_samples; - - audio.timestamp = m->base_ts + d->frame_pts - m->start_ts + - m->play_sys_ts - base_sys_ts; + audio.timestamp = m->full_decode + ? d->frame_pts + : m->base_ts + d->frame_pts - m->start_ts + + m->play_sys_ts - base_sys_ts; if (audio.format == AUDIO_FORMAT_UNKNOWN) return; @@ -384,7 +387,7 @@ static void mp_media_next_audio(mp_media_t *m) m->a_cb(m->opaque, &audio); } -static void mp_media_next_video(mp_media_t *m, bool preload) +void mp_media_next_video(mp_media_t *m, bool preload) { struct mp_decode *d = &m->v; struct obs_source_frame *frame = &m->obsframe; @@ -462,8 +465,10 @@ static void mp_media_next_video(mp_media_t *m, bool preload) if (frame->format == VIDEO_FORMAT_NONE) return; - frame->timestamp = m->base_ts + d->frame_pts - m->start_ts + - m->play_sys_ts - base_sys_ts; + frame->timestamp = m->full_decode + ? d->frame_pts + : (m->base_ts + d->frame_pts - m->start_ts + + m->play_sys_ts - base_sys_ts); frame->width = f->width; frame->height = f->height; @@ -499,7 +504,7 @@ static void mp_media_next_video(mp_media_t *m, bool preload) if (preload) { if (m->seek_next_ts && m->v_seek_cb) { m->v_seek_cb(m->opaque, frame); - } else { + } else if (!m->request_preload) { m->v_preload_cb(m->opaque, frame); } } else { @@ -563,7 +568,7 @@ static void seek_to(mp_media_t *m, int64_t pos) mp_decode_flush(&m->a); } -static bool mp_media_reset(mp_media_t *m) +bool mp_media_reset(mp_media_t *m) { bool stopping; bool active; @@ -632,7 +637,7 @@ static inline bool mp_media_sleep(mp_media_t *m) return timeout; } -static inline bool mp_media_eof(mp_media_t *m) +bool mp_media_eof(mp_media_t *m) { bool v_ended = !m->has_video || !m->v.frame_ready; bool a_ended = !m->has_audio || !m->a.frame_ready; @@ -759,11 +764,19 @@ static void reset_ts(mp_media_t *m) m->next_ns = 0; } +bool mp_media_init2(mp_media_t *m) +{ + if (!init_avformat(m)) { + return false; + } + return true; +} + static inline bool mp_media_thread(mp_media_t *m) { os_set_thread_name("mp_media_thread"); - if (!init_avformat(m)) { + if (!mp_media_init2(m)) { return false; } if (!mp_media_reset(m)) { @@ -771,7 +784,8 @@ static inline bool mp_media_thread(mp_media_t *m) } for (;;) { - bool reset, kill, is_active, seek, pause, reset_time; + bool reset, kill, is_active, seek, pause, reset_time, + preload_frame; int64_t seek_pos; bool timeout = false; @@ -796,10 +810,12 @@ static inline bool mp_media_thread(mp_media_t *m) m->reset = false; m->kill = false; + preload_frame = m->preload_frame; pause = m->pause; seek_pos = m->seek_pos; seek = m->seek; reset_time = m->reset_ts; + m->preload_frame = false; m->seek = false; m->reset_ts = false; @@ -827,6 +843,9 @@ static inline bool mp_media_thread(mp_media_t *m) if (pause) continue; + if (preload_frame) + m->v_preload_cb(m->opaque, &m->obsframe); + /* frames are ready */ if (is_active && !timeout) { if (m->has_video) @@ -875,6 +894,9 @@ static inline bool mp_media_init_internal(mp_media_t *m, m->format_name = info->format ? bstrdup(info->format) : NULL; m->hw = info->hardware_decoding; + if (info->full_decode) + return true; + if (pthread_create(&m->thread, NULL, mp_media_thread_start, m) != 0) { blog(LOG_WARNING, "MP: Could not create media thread"); return false; @@ -899,6 +921,7 @@ bool mp_media_init(mp_media_t *media, const struct mp_media_info *info) media->is_linear_alpha = info->is_linear_alpha; media->buffering = info->buffering; media->speed = info->speed; + media->request_preload = info->request_preload; media->is_local_file = info->is_local_file; da_init(media->packet_pool); @@ -990,6 +1013,16 @@ void mp_media_play_pause(mp_media_t *m, bool pause) os_sem_post(m->sem); } +void mp_media_preload_frame(mp_media_t *m) +{ + if (m->request_preload && m->thread_valid && m->v_preload_cb) { + pthread_mutex_lock(&m->mutex); + m->preload_frame = true; + pthread_mutex_unlock(&m->mutex); + os_sem_post(m->sem); + } +} + void mp_media_stop(mp_media_t *m) { pthread_mutex_lock(&m->mutex); @@ -1003,12 +1036,51 @@ void mp_media_stop(mp_media_t *m) os_sem_post(m->sem); } -int64_t mp_get_current_time(mp_media_t *m) +int64_t mp_media_get_current_time(mp_media_t *m) { return mp_media_get_base_pts(m) * (int64_t)m->speed / 100000000LL; } -void mp_media_seek_to(mp_media_t *m, int64_t pos) +int64_t mp_media_get_frames(mp_media_t *m) +{ + int64_t frames = 0; + + if (!m->fmt) { + return 0; + } + + int video_stream_index = av_find_best_stream(m->fmt, AVMEDIA_TYPE_VIDEO, + -1, -1, NULL, 0); + + if (video_stream_index < 0) { + blog(LOG_WARNING, "MP: Getting number of frames failed: No " + "video stream in media file!"); + return 0; + } + + AVStream *stream = m->fmt->streams[video_stream_index]; + + if (stream->nb_frames > 0) { + frames = stream->nb_frames; + } else { + blog(LOG_DEBUG, "MP: nb_frames not set, estimating using frame " + "rate and duration"); + AVRational avg_frame_rate = stream->avg_frame_rate; + frames = (int64_t)ceil((double)m->fmt->duration / + (double)AV_TIME_BASE * + (double)avg_frame_rate.num / + (double)avg_frame_rate.den); + } + + return frames; +} + +int64_t mp_media_get_duration(mp_media_t *m) +{ + return m->fmt ? m->fmt->duration : 0; +} + +void mp_media_seek(mp_media_t *m, int64_t pos) { pthread_mutex_lock(&m->mutex); if (m->active) { diff --git a/deps/media-playback/media-playback/media.h b/deps/media-playback/media-playback/media.h index 4e6a9af54..391f1831f 100644 --- a/deps/media-playback/media-playback/media.h +++ b/deps/media-playback/media-playback/media.h @@ -38,10 +38,6 @@ extern "C" { #pragma warning(pop) #endif -typedef void (*mp_video_cb)(void *opaque, struct obs_source_frame *frame); -typedef void (*mp_audio_cb)(void *opaque, struct obs_source_audio *audio); -typedef void (*mp_stop_cb)(void *opaque); - struct mp_media { AVFormatContext *fmt; @@ -66,6 +62,7 @@ struct mp_media { DARRAY(AVPacket *) packet_pool; struct mp_decode v; struct mp_decode a; + bool request_preload; bool is_local_file; bool reconnecting; bool has_video; @@ -85,11 +82,13 @@ struct mp_media { uint64_t next_ns; int64_t start_ts; int64_t base_ts; + bool full_decode; uint64_t interrupt_poll_ts; pthread_mutex_t mutex; os_sem_t *sem; + bool preload_frame; bool stopping; bool looping; bool active; @@ -108,35 +107,17 @@ struct mp_media { typedef struct mp_media mp_media_t; -struct mp_media_info { - void *opaque; - - mp_video_cb v_cb; - mp_video_cb v_preload_cb; - mp_video_cb v_seek_cb; - mp_audio_cb a_cb; - mp_stop_cb stop_cb; - - const char *path; - const char *format; - char *ffmpeg_options; - int buffering; - int speed; - enum video_range_type force_range; - bool is_linear_alpha; - bool hardware_decoding; - bool is_local_file; - bool reconnecting; -}; - extern bool mp_media_init(mp_media_t *media, const struct mp_media_info *info); extern void mp_media_free(mp_media_t *media); extern void mp_media_play(mp_media_t *media, bool loop, bool reconnecting); extern void mp_media_stop(mp_media_t *media); extern void mp_media_play_pause(mp_media_t *media, bool pause); -extern int64_t mp_get_current_time(mp_media_t *m); -extern void mp_media_seek_to(mp_media_t *m, int64_t pos); +extern void mp_media_preload_frame(mp_media_t *media); +extern int64_t mp_media_get_current_time(mp_media_t *m); +extern int64_t mp_media_get_frames(mp_media_t *m); +extern int64_t mp_media_get_duration(mp_media_t *m); +extern void mp_media_seek(mp_media_t *m, int64_t pos); /* #define DETAILED_DEBUG_INFO */ diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-source.c b/plugins/obs-ffmpeg/obs-ffmpeg-source.c index 5233da0d6..3a0aab639 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-source.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-source.c @@ -15,13 +15,14 @@ */ #include <obs-module.h> +#include <util/threading.h> #include <util/platform.h> #include <util/dstr.h> #include "obs-ffmpeg-compat.h" #include "obs-ffmpeg-formats.h" -#include <media-playback/media.h> +#include <media-playback/media-playback.h> #define FF_LOG_S(source, level, format, ...) \ blog(level, "[Media Source '%s']: " format, \ @@ -30,8 +31,7 @@ FF_LOG_S(s->source, level, format, ##__VA_ARGS__) struct ffmpeg_source { - mp_media_t media; - bool media_valid; + media_playback_t *media; bool destroy_media; enum video_range_type range; @@ -47,10 +47,12 @@ struct ffmpeg_source { bool is_looping; bool is_local_file; bool is_hw_decoding; + bool full_decode; bool is_clear_on_media_end; bool restart_on_activate; bool close_when_inactive; bool seekable; + bool is_stinger; pthread_t reconnect_thread; bool stop_reconnect; @@ -283,11 +285,11 @@ static void get_audio(void *opaque, struct obs_source_audio *a) static void media_stopped(void *opaque) { struct ffmpeg_source *s = opaque; - if (s->is_clear_on_media_end) { + if (s->is_clear_on_media_end && !s->is_stinger) { obs_source_output_video(s->source, NULL); } - if ((s->close_when_inactive || !s->is_local_file) && s->media_valid) + if ((s->close_when_inactive || !s->is_local_file) && s->media) s->destroy_media = true; set_media_state(s, OBS_MEDIA_STATE_ENDED); @@ -314,22 +316,24 @@ static void ffmpeg_source_open(struct ffmpeg_source *s) .ffmpeg_options = s->ffmpeg_options, .is_local_file = s->is_local_file || s->seekable, .reconnecting = s->reconnecting, + .request_preload = s->is_stinger, + .full_decode = s->full_decode, }; - s->media_valid = mp_media_init(&s->media, &info); + s->media = media_playback_create(&info); } } static void ffmpeg_source_start(struct ffmpeg_source *s) { - if (!s->media_valid) + if (!s->media) ffmpeg_source_open(s); - if (!s->media_valid) + if (!s->media) return; - mp_media_play(&s->media, s->is_looping, s->reconnecting); - if (s->is_local_file && s->media.has_video && + media_playback_play(s->media, s->is_looping, s->reconnecting); + if (s->is_local_file && media_playback_has_video(s->media) && (s->is_clear_on_media_end || s->is_looping)) obs_source_show_preloaded_video(s->source); else @@ -343,7 +347,7 @@ static void *ffmpeg_source_reconnect(void *data) struct ffmpeg_source *s = data; os_sleep_ms(s->reconnect_delay_sec * 1000); - if (s->stop_reconnect || s->media_valid) + if (s->stop_reconnect || s->media) goto finish; bool active = obs_source_active(s->source); @@ -364,9 +368,9 @@ static void ffmpeg_source_tick(void *data, float seconds) struct ffmpeg_source *s = data; if (s->destroy_media) { - if (s->media_valid) { - mp_media_free(&s->media); - s->media_valid = false; + if (s->media) { + media_playback_destroy(s->media); + s->media = NULL; } s->destroy_media = false; @@ -401,6 +405,7 @@ static void ffmpeg_source_update(void *data, obs_data_t *settings) struct ffmpeg_source *s = data; bool is_local_file = obs_data_get_bool(settings, "is_local_file"); + bool is_stinger = obs_data_get_bool(settings, "is_stinger"); const char *input; const char *input_format; @@ -443,6 +448,7 @@ static void ffmpeg_source_update(void *data, obs_data_t *settings) s->input = input ? bstrdup(input) : NULL; s->input_format = input_format ? bstrdup(input_format) : NULL; s->is_hw_decoding = obs_data_get_bool(settings, "hw_decode"); + s->full_decode = obs_data_get_bool(settings, "full_decode"); s->is_clear_on_media_end = obs_data_get_bool(settings, "clear_on_media_end"); s->restart_on_activate = @@ -457,13 +463,14 @@ static void ffmpeg_source_update(void *data, obs_data_t *settings) s->is_local_file = is_local_file; s->seekable = obs_data_get_bool(settings, "seekable"); s->ffmpeg_options = ffmpeg_options ? bstrdup(ffmpeg_options) : NULL; + s->is_stinger = is_stinger; if (s->speed_percent < 1 || s->speed_percent > 200) s->speed_percent = 100; - if (s->media_valid) { - mp_media_free(&s->media); - s->media_valid = false; + if (s->media) { + media_playback_destroy(s->media); + s->media = NULL; } bool active = obs_source_active(s->source); @@ -501,12 +508,21 @@ static void restart_proc(void *data, calldata_t *cd) UNUSED_PARAMETER(cd); } +static void preload_first_frame_proc(void *data, calldata_t *cd) +{ + struct ffmpeg_source *s = data; + if (s->is_stinger) + obs_source_output_video(s->source, NULL); + media_playback_preload_frame(s->media); + UNUSED_PARAMETER(cd); +} + static void get_duration(void *data, calldata_t *cd) { struct ffmpeg_source *s = data; int64_t dur = 0; - if (s->media.fmt) - dur = s->media.fmt->duration; + if (s->media) + dur = media_playback_get_duration(s->media); calldata_set_int(cd, "duration", dur * 1000); } @@ -514,37 +530,7 @@ static void get_duration(void *data, calldata_t *cd) static void get_nb_frames(void *data, calldata_t *cd) { struct ffmpeg_source *s = data; - int64_t frames = 0; - - if (!s->media.fmt) { - calldata_set_int(cd, "num_frames", frames); - return; - } - - int video_stream_index = av_find_best_stream( - s->media.fmt, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); - - if (video_stream_index < 0) { - FF_BLOG(LOG_WARNING, "Getting number of frames failed: No " - "video stream in media file!"); - calldata_set_int(cd, "num_frames", frames); - return; - } - - AVStream *stream = s->media.fmt->streams[video_stream_index]; - - if (stream->nb_frames > 0) { - frames = stream->nb_frames; - } else { - FF_BLOG(LOG_DEBUG, "nb_frames not set, estimating using frame " - "rate and duration"); - AVRational avg_frame_rate = stream->avg_frame_rate; - frames = (int64_t)ceil((double)s->media.fmt->duration / - (double)AV_TIME_BASE * - (double)avg_frame_rate.num / - (double)avg_frame_rate.den); - } - + int64_t frames = media_playback_get_frames(s->media); calldata_set_int(cd, "num_frames", frames); } @@ -622,6 +608,8 @@ static void *ffmpeg_source_create(obs_data_t *settings, obs_source_t *source) proc_handler_t *ph = obs_source_get_proc_handler(source); proc_handler_add(ph, "void restart()", restart_proc, s); + proc_handler_add(ph, "void preload_first_frame()", + preload_first_frame_proc, s); proc_handler_add(ph, "void get_duration(out int duration)", get_duration, s); proc_handler_add(ph, "void get_nb_frames(out int num_frames)", @@ -642,8 +630,8 @@ static void ffmpeg_source_destroy(void *data) if (s->reconnect_thread_valid) pthread_join(s->reconnect_thread, NULL); } - if (s->media_valid) - mp_media_free(&s->media); + if (s->media) + media_playback_destroy(s->media); bfree(s->input); bfree(s->input_format); @@ -664,8 +652,8 @@ static void ffmpeg_source_deactivate(void *data) struct ffmpeg_source *s = data; if (s->restart_on_activate) { - if (s->media_valid) { - mp_media_stop(&s->media); + if (s->media) { + media_playback_stop(s->media); if (s->is_clear_on_media_end) obs_source_output_video(s->source, NULL); @@ -677,13 +665,13 @@ static void ffmpeg_source_play_pause(void *data, bool pause) { struct ffmpeg_source *s = data; - if (!s->media_valid) + if (!s->media) ffmpeg_source_open(s); - if (!s->media_valid) + if (!s->media) return; - mp_media_play_pause(&s->media, pause); + media_playback_play_pause(s->media, pause); if (pause) { @@ -699,8 +687,8 @@ static void ffmpeg_source_stop(void *data) { struct ffmpeg_source *s = data; - if (s->media_valid) { - mp_media_stop(&s->media); + if (s->media) { + media_playback_stop(s->media); obs_source_output_video(s->source, NULL); set_media_state(s, OBS_MEDIA_STATE_STOPPED); } @@ -721,8 +709,8 @@ static int64_t ffmpeg_source_get_duration(void *data) struct ffmpeg_source *s = data; int64_t dur = 0; - if (s->media.fmt) - dur = s->media.fmt->duration / INT64_C(1000); + if (s->media) + dur = media_playback_get_duration(s->media) / INT64_C(1000); return dur; } @@ -731,17 +719,17 @@ static int64_t ffmpeg_source_get_time(void *data) { struct ffmpeg_source *s = data; - return mp_get_current_time(&s->media); + return media_playback_get_current_time(s->media); } static void ffmpeg_source_set_time(void *data, int64_t ms) { struct ffmpeg_source *s = data; - if (!s->media_valid) + if (!s->media) return; - mp_media_seek_to(&s->media, ms); + media_playback_seek(s->media, ms); } static enum obs_media_state ffmpeg_source_get_state(void *data) diff --git a/plugins/obs-transitions/data/locale/en-US.ini b/plugins/obs-transitions/data/locale/en-US.ini index 6f54882d2..44a772337 100644 --- a/plugins/obs-transitions/data/locale/en-US.ini +++ b/plugins/obs-transitions/data/locale/en-US.ini @@ -28,6 +28,7 @@ TrackMatteLayoutHorizontal="Same file, side-by-side (stinger on the left, track TrackMatteLayoutVertical="Same file, stacked (stinger on top, track matte at the bottom)" TrackMatteLayoutSeparateFile="Separate file (warning: matte can get out of sync)" TrackMatteLayoutMask="Mask only" +PreloadVideoToRam="Preload Video to RAM" AudioFadeStyle="Audio Fade Style" AudioFadeStyle.FadeOutFadeIn="Fade out to transition point then fade in" AudioFadeStyle.CrossFade="Crossfade" diff --git a/plugins/obs-transitions/transition-stinger.c b/plugins/obs-transitions/transition-stinger.c index c27656d86..f12c803fc 100644 --- a/plugins/obs-transitions/transition-stinger.c +++ b/plugins/obs-transitions/transition-stinger.c @@ -69,11 +69,14 @@ static void stinger_update(void *data, obs_data_t *settings) struct stinger_info *s = data; const char *path = obs_data_get_string(settings, "path"); bool hw_decode = obs_data_get_bool(settings, "hw_decode"); + bool preload = obs_data_get_bool(settings, "preload"); obs_data_t *media_settings = obs_data_create(); obs_data_set_string(media_settings, "local_file", path); obs_data_set_bool(media_settings, "hw_decode", hw_decode); obs_data_set_bool(media_settings, "looping", false); + obs_data_set_bool(media_settings, "full_decode", preload); + obs_data_set_bool(media_settings, "is_stinger", true); obs_source_release(s->media_source); struct dstr name; @@ -630,6 +633,11 @@ static void stinger_transition_stop(void *data) if (s->matte_source) obs_source_remove_active_child(s->source, s->matte_source); + proc_handler_t *ph = obs_source_get_proc_handler(s->media_source); + + calldata_t cd = {0}; + proc_handler_call(ph, "preload_first_frame", &cd); + s->transitioning = false; } @@ -737,6 +745,8 @@ static obs_properties_t *stinger_properties(void *data) OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); obs_properties_add_bool(ppts, "hw_decode", obs_module_text("HardwareDecode")); + obs_properties_add_bool(ppts, "preload", + obs_module_text("PreloadVideoToRam")); obs_property_list_add_int(p, obs_module_text("TransitionPointTypeTime"), TIMING_TIME); obs_property_list_add_int( |