summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJim <obs.jim@gmail.com>2023-02-04 01:33:40 -0800
committerJim <obs.jim@gmail.com>2023-02-14 16:09:07 -0800
commite7fd24c85559e5f4f8646dbd0a421da2440e7f16 (patch)
treee0eaba576899706f4b2d726f53d356065998f597
parentbe629b93cea68b2465c0ba053a37a9046afcb92d (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.txt10
-rw-r--r--deps/media-playback/media-playback/cache.c698
-rw-r--r--deps/media-playback/media-playback/cache.h81
-rw-r--r--deps/media-playback/media-playback/decode.c1
-rw-r--r--deps/media-playback/media-playback/media-playback.c144
-rw-r--r--deps/media-playback/media-playback/media-playback.h49
-rw-r--r--deps/media-playback/media-playback/media.c104
-rw-r--r--deps/media-playback/media-playback/media.h35
-rw-r--r--plugins/obs-ffmpeg/obs-ffmpeg-source.c116
-rw-r--r--plugins/obs-transitions/data/locale/en-US.ini1
-rw-r--r--plugins/obs-transitions/transition-stinger.c10
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(