summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2014-10-06 21:20:38 +0200
committerwm4 <wm4@nowhere>2014-10-06 21:49:26 +0200
commit5fb05940f1b4d0dad208d94ddd73d78520d8ce03 (patch)
tree9be531822bb1cd7e2fc1daab2aa96eaf00043945
parentd26104351b65a212c03ec08679cb45a3aa2f0a32 (diff)
downloadmpv-5fb05940f1b4d0dad208d94ddd73d78520d8ce03.tar.bz2
mpv-5fb05940f1b4d0dad208d94ddd73d78520d8ce03.tar.xz
player: open stream and demuxer asynchronously
Run opening the stream and opening the demuxer in a separate thread. This should remove the last code paths in which the player can normally get blocked on network. When the stream is opened, the player will still react to input and so on. Commands to abort opening can also be handled properly, instead of using some of the old hacks in input.c. The only thing the user can really do is aborting loading by navigating the playlist or quitting. Whether playback abort works depends on the stream implementation; with normal network, this will depend on what libavformat (via "interrupt" callback) does. Some pain is caused by DVD/BD/DVB. These want to reload the demuxer sometimes. DVB wants it in order to discard old, inactive streams. DVD/BD for the same reason, and also for reloading stream languages and similar metadata. This means the stream and the demuxer have to be loaded separately. One minor detail is that we now need to copy all global options. This wasn't really needed before, because the options were accessed on opening only, but since opening is now on a separate thread, this obviously becomes a necessity.
-rw-r--r--player/core.h6
-rw-r--r--player/discnav.c5
-rw-r--r--player/loadfile.c67
-rw-r--r--player/misc.c64
-rw-r--r--player/playloop.c17
-rw-r--r--player/scripting.c6
6 files changed, 146 insertions, 19 deletions
diff --git a/player/core.h b/player/core.h
index cf35df64b2..a0c9db2cc5 100644
--- a/player/core.h
+++ b/player/core.h
@@ -178,7 +178,7 @@ typedef struct MPContext {
struct playlist *playlist;
struct playlist_entry *playing; // currently playing file
- char *filename; // always the same as playing->filename (or NULL)
+ char *filename; // immutable copy of playing->filename (or NULL)
struct mp_resolve_result *resolve_result;
enum stop_play_reason stop_play;
bool playback_initialized; // playloop can be run/is running
@@ -417,6 +417,9 @@ float mp_get_cache_percent(struct MPContext *mpctx);
bool mp_get_cache_idle(struct MPContext *mpctx);
void update_window_title(struct MPContext *mpctx, bool force);
void stream_dump(struct MPContext *mpctx);
+int mpctx_run_non_blocking(struct MPContext *mpctx, void (*thread_fn)(void *arg),
+ void *thread_arg);
+struct mpv_global *create_sub_global(struct MPContext *mpctx);
// osd.c
void set_osd_bar(struct MPContext *mpctx, int type,
@@ -449,6 +452,7 @@ double chapter_start_time(struct MPContext *mpctx, int chapter);
int get_chapter_count(struct MPContext *mpctx);
void execute_queued_seek(struct MPContext *mpctx);
void run_playloop(struct MPContext *mpctx);
+void mp_idle(struct MPContext *mpctx);
void idle_loop(struct MPContext *mpctx);
void handle_force_window(struct MPContext *mpctx, bool reconfig);
void add_frame_pts(struct MPContext *mpctx, double pts);
diff --git a/player/discnav.c b/player/discnav.c
index 1511dec468..ceb38bc3f4 100644
--- a/player/discnav.c
+++ b/player/discnav.c
@@ -167,6 +167,11 @@ void mp_nav_user_input(struct MPContext *mpctx, char *command)
struct mp_nav_state *nav = mpctx->nav_state;
if (!nav)
return;
+ // In the short time while the demuxer is opened (running in a different
+ // thread) we can't access the stream directly. Once the demuxer is opened,
+ // we can access the stream via demux_stream_control() though.
+ if (!mpctx->demuxer)
+ return;
if (strcmp(command, "mouse_move") == 0) {
struct mp_image_params vid = {0};
if (mpctx->d_video)
diff --git a/player/loadfile.c b/player/loadfile.c
index e015b867e7..23ffa80684 100644
--- a/player/loadfile.c
+++ b/player/loadfile.c
@@ -32,6 +32,7 @@
#include "osdep/timer.h"
#include "common/msg.h"
+#include "common/global.h"
#include "options/path.h"
#include "options/m_config.h"
#include "options/parse_configfile.h"
@@ -844,6 +845,60 @@ static void load_per_file_options(m_config_t *conf,
}
}
+struct stream_open_args {
+ struct mp_cancel *cancel;
+ struct mpv_global *global; // contains copy of global options
+ char *filename;
+ int stream_flags;
+ struct stream *stream; // result
+};
+
+static void open_stream_thread(void *pctx)
+{
+ struct stream_open_args *args = pctx;
+ args->stream = stream_create(args->filename, args->stream_flags,
+ args->cancel, args->global);
+}
+
+static struct stream *open_stream_async(struct MPContext *mpctx,
+ char *filename, int stream_flags)
+{
+ struct stream_open_args args = {
+ .cancel = mpctx->playback_abort,
+ .global = create_sub_global(mpctx),
+ .filename = filename,
+ .stream_flags = stream_flags,
+ };
+ mpctx_run_non_blocking(mpctx, open_stream_thread, &args);
+ if (args.stream) {
+ talloc_steal(args.stream, args.global);
+ } else {
+ talloc_free(args.global);
+ }
+ return args.stream;
+}
+
+struct demux_open_args {
+ struct stream *stream;
+ struct demuxer *demux; // result
+};
+
+static void open_demux_thread(void *pctx)
+{
+ struct demux_open_args *args = pctx;
+ struct stream *s = args->stream;
+ struct mpv_global *global = s->global; // they run in the same thread anyway
+ args->demux = demux_open(s, global->opts->demuxer_name, NULL, global);
+}
+
+static struct demuxer *open_demux_async(struct MPContext *mpctx,
+ struct stream *stream)
+{
+ struct demux_open_args args = {stream};
+ mpctx_run_non_blocking(mpctx, open_demux_thread, &args);
+ return args.demux;
+}
+
// Start playing the current playlist entry.
// Handle initialization and deinitialization.
static void play_current_file(struct MPContext *mpctx)
@@ -880,7 +935,7 @@ static void play_current_file(struct MPContext *mpctx)
goto terminate_playback;
mpctx->playing->reserved += 1;
- mpctx->filename = mpctx->playing->filename;
+ mpctx->filename = talloc_strdup(tmp, mpctx->playing->filename);
mpctx->add_osd_seek_info &= OSD_SEEK_INFO_EDITION;
@@ -931,8 +986,7 @@ static void play_current_file(struct MPContext *mpctx)
int stream_flags = STREAM_READ;
if (!opts->load_unsafe_playlists)
stream_flags |= mpctx->playing->stream_flags;
- mpctx->stream = stream_create(stream_filename, stream_flags,
- mpctx->playback_abort, mpctx->global);
+ mpctx->stream = open_stream_async(mpctx, stream_filename, stream_flags);
if (!mpctx->stream) { // error...
mp_process_input(mpctx);
goto terminate_playback;
@@ -958,15 +1012,12 @@ goto_reopen_demuxer: ;
mp_nav_reset(mpctx);
- //============ Open DEMUXERS --- DETECT file type =======================
-
- mpctx->demuxer = demux_open(mpctx->stream, opts->demuxer_name, NULL,
- mpctx->global);
- mpctx->master_demuxer = mpctx->demuxer;
+ mpctx->demuxer = open_demux_async(mpctx, mpctx->stream);
if (!mpctx->demuxer) {
MP_ERR(mpctx, "Failed to recognize file format.\n");
goto terminate_playback;
}
+ mpctx->master_demuxer = mpctx->demuxer;
MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources, mpctx->demuxer);
diff --git a/player/misc.c b/player/misc.c
index d5e27f9584..21e9bcbba3 100644
--- a/player/misc.c
+++ b/player/misc.c
@@ -18,6 +18,7 @@
#include <stddef.h>
#include <stdbool.h>
+#include <pthread.h>
#include <assert.h>
#include "config.h"
@@ -29,7 +30,9 @@
#include "common/msg.h"
#include "options/options.h"
#include "options/m_property.h"
+#include "options/m_config.h"
#include "common/common.h"
+#include "common/global.h"
#include "common/encode.h"
#include "common/playlist.h"
#include "input/input.h"
@@ -219,3 +222,64 @@ void merge_playlist_files(struct playlist *pl)
playlist_add_file(pl, edl);
talloc_free(edl);
}
+
+// Create a talloc'ed copy of mpctx->global. It contains a copy of the global
+// option struct. It still just references some things though, like mp_log.
+// The main purpose is letting threads access the option struct without the
+// need for additional synchronization.
+struct mpv_global *create_sub_global(struct MPContext *mpctx)
+{
+ struct mpv_global *new = talloc_ptrtype(NULL, new);
+ struct m_config *new_config = m_config_dup(new, mpctx->mconfig);
+ *new = (struct mpv_global){
+ .log = mpctx->global->log,
+ .opts = new_config->optstruct,
+ };
+ return new;
+}
+
+struct wrapper_args {
+ struct MPContext *mpctx;
+ void (*thread_fn)(void *);
+ void *thread_arg;
+ pthread_mutex_t mutex;
+ bool done;
+};
+
+static void *thread_wrapper(void *pctx)
+{
+ struct wrapper_args *args = pctx;
+ args->thread_fn(args->thread_arg);
+ pthread_mutex_lock(&args->mutex);
+ args->done = true;
+ pthread_mutex_unlock(&args->mutex);
+ mp_input_wakeup(args->mpctx->input); // this interrupts mp_idle()
+ return NULL;
+}
+
+// Run the thread_fn in a new thread. Wait until the thread returns, but while
+// waiting, process input and input commands.
+int mpctx_run_non_blocking(struct MPContext *mpctx, void (*thread_fn)(void *arg),
+ void *thread_arg)
+{
+ struct wrapper_args args = {mpctx, thread_fn, thread_arg};
+ pthread_mutex_init(&args.mutex, NULL);
+ bool success = false;
+ pthread_t thread;
+ if (pthread_create(&thread, NULL, thread_wrapper, &args))
+ goto done;
+ while (!success) {
+ mp_idle(mpctx);
+
+ if (mpctx->stop_play)
+ mp_cancel_trigger(mpctx->playback_abort);
+
+ pthread_mutex_lock(&args.mutex);
+ success |= args.done;
+ pthread_mutex_unlock(&args.mutex);
+ }
+ pthread_join(thread, NULL);
+done:
+ pthread_mutex_destroy(&args.mutex);
+ return success ? 0 : -1;
+}
diff --git a/player/playloop.c b/player/playloop.c
index 79d3d14655..5d9abf923d 100644
--- a/player/playloop.c
+++ b/player/playloop.c
@@ -968,6 +968,16 @@ void run_playloop(struct MPContext *mpctx)
execute_queued_seek(mpctx);
}
+void mp_idle(struct MPContext *mpctx)
+{
+ handle_dummy_ticks(mpctx);
+ mp_wait_events(mpctx, mpctx->sleeptime);
+ mpctx->sleeptime = 100.0;
+ mp_process_input(mpctx);
+ update_osd_msg(mpctx);
+ handle_osd_redraw(mpctx);
+}
+
// Waiting for the slave master to send us a new file to play.
void idle_loop(struct MPContext *mpctx)
{
@@ -983,11 +993,6 @@ void idle_loop(struct MPContext *mpctx)
mpctx->sleeptime = 0;
need_reinit = false;
}
- handle_dummy_ticks(mpctx);
- mp_wait_events(mpctx, mpctx->sleeptime);
- mpctx->sleeptime = 100.0;
- mp_process_input(mpctx);
- update_osd_msg(mpctx);
- handle_osd_redraw(mpctx);
+ mp_idle(mpctx);
}
}
diff --git a/player/scripting.c b/player/scripting.c
index 0b5993c8e5..6e2a5a4e2d 100644
--- a/player/scripting.c
+++ b/player/scripting.c
@@ -90,10 +90,8 @@ static void *script_thread(void *p)
static void wait_loaded(struct MPContext *mpctx)
{
- while (!mp_clients_all_initialized(mpctx)) {
- mp_wait_events(mpctx, 1e9);
- mp_process_input(mpctx);
- }
+ while (!mp_clients_all_initialized(mpctx))
+ mp_idle(mpctx);
}
static void mp_load_script(struct MPContext *mpctx, const char *fname)