diff --git a/demux/demux.c b/demux/demux.c
index 095c23fd0c..592ad1608a 100644
--- a/demux/demux.c
+++ b/demux/demux.c
@@ -241,7 +241,6 @@ struct sh_stream *new_sh_stream(demuxer_t *demuxer, enum stream_type type)
         .demuxer = demuxer,
         .index = demuxer->num_streams,
         .demuxer_id = demuxer_id, // may be overwritten by demuxer
-        .opts = demuxer->opts,
         .ds = talloc_zero(sh, struct demux_stream),
     };
     MP_TARRAY_APPEND(demuxer, demuxer->streams, demuxer->num_streams, sh);
@@ -249,21 +248,18 @@ struct sh_stream *new_sh_stream(demuxer_t *demuxer, enum stream_type type)
         case STREAM_VIDEO: {
             struct sh_video *sht = talloc_zero(demuxer, struct sh_video);
             sht->gsh = sh;
-            sht->opts = sh->opts;
             sh->video = sht;
             break;
         }
         case STREAM_AUDIO: {
             struct sh_audio *sht = talloc_zero(demuxer, struct sh_audio);
             sht->gsh = sh;
-            sht->opts = sh->opts;
             sh->audio = sht;
             break;
         }
         case STREAM_SUB: {
             struct sh_sub *sht = talloc_zero(demuxer, struct sh_sub);
             sht->gsh = sh;
-            sht->opts = sh->opts;
             sh->sub = sht;
             break;
         }
diff --git a/demux/stheader.h b/demux/stheader.h
index eb7c3d132b..1c151cda3d 100644
--- a/demux/stheader.h
+++ b/demux/stheader.h
@@ -63,30 +63,14 @@ struct sh_stream {
     // stream is a picture (such as album art)
     struct demux_packet *attached_picture;
 
-    // Human readable description of the running decoder, or NULL
-    char *decoder_desc;
-
-    // shouldn't exist type of stuff
-    struct MPOpts *opts;
-
     // Internal to demux.c
     struct demux_stream *ds;
 };
 
-#define SH_COMMON                                                       \
-    struct sh_stream *gsh;                                              \
-    struct MPOpts *opts;                                                \
-    /* usually a FourCC, exact meaning depends on gsh->format */        \
-    unsigned int format;                                                \
-    int initialized;                                                    \
-    /* audio: last known pts value in output from decoder               \
-     * video: predicted/interpolated PTS of the current frame */        \
-    double pts;                                                         \
-    /* decoder context */                                               \
-    void *context;                                                      \
-
 typedef struct sh_audio {
-    SH_COMMON
+    struct sh_stream *gsh;
+    /* usually a FourCC, exact meaning depends on gsh->codec */
+    unsigned int format;
     int samplerate;
     struct mp_chmap channels;
     int i_bps; // == bitrate  (compressed bytes/sec)
@@ -98,37 +82,18 @@ typedef struct sh_audio {
 } sh_audio_t;
 
 typedef struct sh_video {
-    SH_COMMON
-    float next_frame_time;
-    double last_pts;
-    double buffered_pts[32];
-    int num_buffered_pts;
-    double codec_reordered_pts;
-    double prev_codec_reordered_pts;
-    int num_reordered_pts_problems;
-    double sorted_pts;
-    double prev_sorted_pts;
-    int num_sorted_pts_problems;
-    int pts_assoc_mode;
-    // output format: (set by demuxer)
+    struct sh_stream *gsh;
+    /* usually a FourCC, exact meaning depends on gsh->codec */
+    unsigned int format;
     float fps;            // frames per second (set only if constant fps)
     float aspect;         // aspect ratio stored in the file (for prescaling)
-    float stream_aspect;  // aspect ratio in media headers (DVD IFO files)
     int i_bps;            // == bitrate  (compressed bytes/sec)
-    int disp_w, disp_h;   // display size (filled by demuxer or decoder)
-    // output driver/filters: (set by libmpcodecs core)
-    struct vf_instance *vfilter;  // video filter chain
-    const struct vd_functions *vd_driver;
-    int vf_initialized;   // -1 failed, 0 not done, 1 done
-    long vf_reconfig_count; // incremented each mpcodecs_reconfig_vo() call
-    struct mp_image_params *vf_input; // video filter input params
-    struct mp_hwdec_info *hwdec_info; // video output hwdec handles
-    // win32-compatible codec parameters:
+    int disp_w, disp_h;   // display size
     MP_BITMAPINFOHEADER *bih;
 } sh_video_t;
 
 typedef struct sh_sub {
-    SH_COMMON
+    struct sh_stream *gsh;
     unsigned char *extradata;   // extra header data passed from demuxer
     int extradata_len;
     int frame_based;            // timestamps are frame-based
diff --git a/mpvcore/player/audio.c b/mpvcore/player/audio.c
index ce161a58d5..377b393dfd 100644
--- a/mpvcore/player/audio.c
+++ b/mpvcore/player/audio.c
@@ -36,6 +36,7 @@
 #include "audio/filter/af.h"
 #include "audio/out/ao.h"
 #include "demux/demux.h"
+#include "video/decode/dec_video.h"
 
 #include "mp_core.h"
 
@@ -302,7 +303,7 @@ static int audio_start_sync(struct MPContext *mpctx, int playsize)
         if (hrseek)
             ptsdiff = written_pts - mpctx->hrseek_pts;
         else
-            ptsdiff = written_pts - mpctx->sh_video->pts - mpctx->delay
+            ptsdiff = written_pts - mpctx->d_video->pts - mpctx->delay
                       - mpctx->audio_delay;
         samples = ptsdiff * real_samplerate;
 
@@ -376,7 +377,7 @@ int fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
         playsize = ao_get_space(ao);
 
     // Coming here with hrseek_active still set means audio-only
-    if (!mpctx->sh_video || !mpctx->sync_audio_to_video)
+    if (!mpctx->d_video || !mpctx->sync_audio_to_video)
         mpctx->syncing_audio = false;
     if (!opts->initial_audio_sync || !modifiable_audio_format) {
         mpctx->syncing_audio = false;
diff --git a/mpvcore/player/command.c b/mpvcore/player/command.c
index cdeb11c713..c09a046cd2 100644
--- a/mpvcore/player/command.c
+++ b/mpvcore/player/command.c
@@ -316,7 +316,7 @@ static int mp_property_length(m_option_t *prop, int action, void *arg,
 static int mp_property_avsync(m_option_t *prop, int action, void *arg,
                               MPContext *mpctx)
 {
-    if (!mpctx->d_audio || !mpctx->sh_video)
+    if (!mpctx->d_audio || !mpctx->d_video)
         return M_PROPERTY_UNAVAILABLE;
     if (mpctx->last_av_difference == MP_NOPTS_VALUE)
         return M_PROPERTY_UNAVAILABLE;
@@ -623,8 +623,8 @@ static int mp_property_angle(m_option_t *prop, int action, void *arg,
     case M_PROPERTY_SET:
         angle = demuxer_set_angle(demuxer, *(int *)arg);
         if (angle >= 0) {
-            if (mpctx->sh_video)
-                resync_video_stream(mpctx->sh_video);
+            if (mpctx->d_video)
+                video_resync_stream(mpctx->d_video);
 
             if (mpctx->d_audio)
                 audio_resync_stream(mpctx->d_audio);
@@ -817,7 +817,7 @@ static int mp_property_volrestore(m_option_t *prop, int action,
 static int mp_property_audio_delay(m_option_t *prop, int action,
                                    void *arg, MPContext *mpctx)
 {
-    if (!(mpctx->d_audio && mpctx->sh_video))
+    if (!(mpctx->d_audio && mpctx->d_video))
         return M_PROPERTY_UNAVAILABLE;
     float delay = mpctx->opts->audio_delay;
     switch (action) {
@@ -1168,7 +1168,7 @@ static int probe_deint_filters(struct MPContext *mpctx, const char *cmd)
 
 static int get_deinterlacing(struct MPContext *mpctx)
 {
-    vf_instance_t *vf = mpctx->sh_video->vfilter;
+    vf_instance_t *vf = mpctx->d_video->vfilter;
     int enabled = 0;
     if (vf->control(vf, VFCTRL_GET_DEINTERLACE, &enabled) != CONTROL_OK)
         enabled = -1;
@@ -1182,7 +1182,7 @@ static int get_deinterlacing(struct MPContext *mpctx)
 
 static void set_deinterlacing(struct MPContext *mpctx, bool enable)
 {
-    vf_instance_t *vf = mpctx->sh_video->vfilter;
+    vf_instance_t *vf = mpctx->d_video->vfilter;
     if (vf_find_by_label(vf, VF_DEINTERLACE_LABEL)) {
         if (!enable)
             edit_filters(mpctx, STREAM_VIDEO, "del", "@" VF_DEINTERLACE_LABEL);
@@ -1199,7 +1199,7 @@ static void set_deinterlacing(struct MPContext *mpctx, bool enable)
 static int mp_property_deinterlace(m_option_t *prop, int action,
                                    void *arg, MPContext *mpctx)
 {
-    if (!mpctx->sh_video || !mpctx->sh_video->vfilter)
+    if (!mpctx->d_video || !mpctx->d_video->vfilter)
         return M_PROPERTY_UNAVAILABLE;
     switch (action) {
     case M_PROPERTY_GET:
@@ -1218,7 +1218,7 @@ static int video_refresh_property_helper(m_option_t *prop, int action,
 {
     int r = mp_property_generic_option(prop, action, arg, mpctx);
     if (action == M_PROPERTY_SET) {
-        if (mpctx->sh_video) {
+        if (mpctx->d_video) {
             reinit_video_filters(mpctx);
             mp_force_video_refresh(mpctx);
         }
@@ -1239,8 +1239,8 @@ static int mp_property_colormatrix(m_option_t *prop, int action, void *arg,
         vo_control(mpctx->video_out, VOCTRL_GET_YUV_COLORSPACE, &vo_csp);
 
     struct mp_image_params vd_csp = {0};
-    if (mpctx->sh_video)
-        vd_control(mpctx->sh_video, VDCTRL_GET_PARAMS, &vd_csp);
+    if (mpctx->d_video)
+        video_vd_control(mpctx->d_video, VDCTRL_GET_PARAMS, &vd_csp);
 
     char *res = talloc_asprintf(NULL, "%s",
                                 mp_csp_names[opts->requested_colorspace]);
@@ -1273,8 +1273,8 @@ static int mp_property_colormatrix_input_range(m_option_t *prop, int action,
         vo_control(mpctx->video_out, VOCTRL_GET_YUV_COLORSPACE, &vo_csp );
 
     struct mp_image_params vd_csp = {0};
-    if (mpctx->sh_video)
-        vd_control(mpctx->sh_video, VDCTRL_GET_PARAMS, &vd_csp);
+    if (mpctx->d_video)
+        video_vd_control(mpctx->d_video, VDCTRL_GET_PARAMS, &vd_csp);
 
     char *res = talloc_asprintf(NULL, "%s",
                                 mp_csp_levels_names[opts->requested_input_range]);
@@ -1372,7 +1372,7 @@ static int mp_property_border(m_option_t *prop, int action, void *arg,
 static int mp_property_framedrop(m_option_t *prop, int action,
                                  void *arg, MPContext *mpctx)
 {
-    if (!mpctx->sh_video)
+    if (!mpctx->d_video)
         return M_PROPERTY_UNAVAILABLE;
 
     return mp_property_generic_option(prop, action, arg, mpctx);
@@ -1381,17 +1381,17 @@ static int mp_property_framedrop(m_option_t *prop, int action,
 static int mp_property_video_color(m_option_t *prop, int action, void *arg,
                                    MPContext *mpctx)
 {
-    if (!mpctx->sh_video)
+    if (!mpctx->d_video)
         return M_PROPERTY_UNAVAILABLE;
 
     switch (action) {
     case M_PROPERTY_SET: {
-        if (set_video_colors(mpctx->sh_video, prop->name, *(int *) arg) <= 0)
+        if (video_set_colors(mpctx->d_video, prop->name, *(int *) arg) <= 0)
             return M_PROPERTY_UNAVAILABLE;
         break;
     }
     case M_PROPERTY_GET:
-        if (get_video_colors(mpctx->sh_video, prop->name, (int *)arg) <= 0)
+        if (video_get_colors(mpctx->d_video, prop->name, (int *)arg) <= 0)
             return M_PROPERTY_UNAVAILABLE;
         // Write new value to option variable
         mp_property_generic_option(prop, M_PROPERTY_SET, arg, mpctx);
@@ -1404,7 +1404,7 @@ static int mp_property_video_color(m_option_t *prop, int action, void *arg,
 static int mp_property_video_format(m_option_t *prop, int action,
                                     void *arg, MPContext *mpctx)
 {
-    const char *c = mpctx->sh_video ? mpctx->sh_video->gsh->codec : NULL;
+    const char *c = mpctx->d_video ? mpctx->d_video->header->codec : NULL;
     return m_property_strdup_ro(prop, action, arg, c);
 }
 
@@ -1412,7 +1412,7 @@ static int mp_property_video_format(m_option_t *prop, int action,
 static int mp_property_video_codec(m_option_t *prop, int action,
                                    void *arg, MPContext *mpctx)
 {
-    const char *c = mpctx->sh_video ? mpctx->sh_video->gsh->decoder_desc : NULL;
+    const char *c = mpctx->d_video ? mpctx->d_video->decoder_desc : NULL;
     return m_property_strdup_ro(prop, action, arg, c);
 }
 
@@ -1421,42 +1421,44 @@ static int mp_property_video_codec(m_option_t *prop, int action,
 static int mp_property_video_bitrate(m_option_t *prop, int action,
                                      void *arg, MPContext *mpctx)
 {
-    if (!mpctx->sh_video)
+    if (!mpctx->d_video)
         return M_PROPERTY_UNAVAILABLE;
     if (action == M_PROPERTY_PRINT) {
-        *(char **)arg = format_bitrate(mpctx->sh_video->i_bps);
+        *(char **)arg = format_bitrate(mpctx->d_video->i_bps);
         return M_PROPERTY_OK;
     }
-    return m_property_int_ro(prop, action, arg, mpctx->sh_video->i_bps);
+    return m_property_int_ro(prop, action, arg, mpctx->d_video->i_bps);
 }
 
 /// Video display width (RO)
 static int mp_property_width(m_option_t *prop, int action, void *arg,
                              MPContext *mpctx)
 {
-    struct sh_video *sh = mpctx->sh_video;
-    if (!sh)
+    struct dec_video *vd = mpctx->d_video;
+    if (!vd)
         return M_PROPERTY_UNAVAILABLE;
+    struct sh_video *sh = vd->header->video;
     return m_property_int_ro(prop, action, arg,
-                             sh->vf_input ? sh->vf_input->w : sh->disp_w);
+                             vd->vf_input ? vd->vf_input->w : sh->disp_w);
 }
 
 /// Video display height (RO)
 static int mp_property_height(m_option_t *prop, int action, void *arg,
                               MPContext *mpctx)
 {
-    struct sh_video *sh = mpctx->sh_video;
-    if (!sh)
+    struct dec_video *vd = mpctx->d_video;
+    if (!vd)
         return M_PROPERTY_UNAVAILABLE;
+    struct sh_video *sh = vd->header->video;
     return m_property_int_ro(prop, action, arg,
-                             sh->vf_input ? sh->vf_input->h : sh->disp_h);
+                             vd->vf_input ? vd->vf_input->h : sh->disp_h);
 }
 
 static int property_vo_wh(m_option_t *prop, int action, void *arg,
                           MPContext *mpctx, bool get_w)
 {
     struct vo *vo = mpctx->video_out;
-    if (!mpctx->sh_video && !vo || !vo->hasframe)
+    if (!mpctx->d_video || !vo || !vo->hasframe)
         return M_PROPERTY_UNAVAILABLE;
     return m_property_int_ro(prop, action, arg,
                              get_w ? vo->aspdat.prew : vo->aspdat.preh);
@@ -1530,18 +1532,19 @@ static int mp_property_osd_par(m_option_t *prop, int action, void *arg,
 static int mp_property_fps(m_option_t *prop, int action, void *arg,
                            MPContext *mpctx)
 {
-    if (!mpctx->sh_video)
+    if (!mpctx->d_video)
         return M_PROPERTY_UNAVAILABLE;
-    return m_property_float_ro(prop, action, arg, mpctx->sh_video->fps);
+    return m_property_float_ro(prop, action, arg, mpctx->d_video->header->video->fps);
 }
 
 /// Video aspect (RO)
 static int mp_property_aspect(m_option_t *prop, int action, void *arg,
                               MPContext *mpctx)
 {
-    struct sh_video *sh_video = mpctx->sh_video;
-    if (!mpctx->sh_video)
+    if (!mpctx->d_video)
         return M_PROPERTY_UNAVAILABLE;
+    struct dec_video *d_video = mpctx->d_video;
+    struct sh_video *sh_video = d_video->header->video;
     switch (action) {
     case M_PROPERTY_SET: {
         mpctx->opts->movie_aspect = *(float *)arg;
@@ -1551,7 +1554,7 @@ static int mp_property_aspect(m_option_t *prop, int action, void *arg,
     }
     case M_PROPERTY_GET: {
         float aspect = -1;
-        struct mp_image_params *params = sh_video->vf_input;
+        struct mp_image_params *params = d_video->vf_input;
         if (params && params->d_w && params->d_h) {
             aspect = (float)params->d_w / params->d_h;
         } else if (sh_video->disp_w && sh_video->disp_h) {
diff --git a/mpvcore/player/loadfile.c b/mpvcore/player/loadfile.c
index 856f5469c0..51d3f2ec7e 100644
--- a/mpvcore/player/loadfile.c
+++ b/mpvcore/player/loadfile.c
@@ -99,8 +99,9 @@ void uninit_player(struct MPContext *mpctx, unsigned int mask)
 
     if (mask & INITIALIZED_VCODEC) {
         mpctx->initialized_flags &= ~INITIALIZED_VCODEC;
-        if (mpctx->sh_video)
-            uninit_video(mpctx->sh_video);
+        if (mpctx->d_video)
+            video_uninit(mpctx->d_video);
+        mpctx->d_video = NULL;
         cleanup_demux_stream(mpctx, STREAM_VIDEO);
         mpctx->sync_audio_to_video = false;
     }
@@ -113,7 +114,7 @@ void uninit_player(struct MPContext *mpctx, unsigned int mask)
         mpctx->num_tracks = 0;
         for (int t = 0; t < STREAM_TYPE_COUNT; t++)
             mpctx->current_track[t] = NULL;
-        assert(!mpctx->sh_video && !mpctx->d_audio && !mpctx->sh_sub);
+        assert(!mpctx->d_video && !mpctx->d_audio && !mpctx->sh_sub);
         mpctx->master_demuxer = NULL;
         for (int i = 0; i < mpctx->num_sources; i++) {
             uninit_subs(mpctx->sources[i]);
@@ -269,7 +270,6 @@ static void set_demux_field(struct MPContext *mpctx, enum stream_type type,
     mpctx->sh[type] = s;
     // redundant fields for convenience access
     switch(type) {
-        case STREAM_VIDEO: mpctx->sh_video = s ? s->video : NULL; break;
         case STREAM_SUB: mpctx->sh_sub = s ? s->sub : NULL; break;
     }
 }
@@ -348,7 +348,7 @@ bool timeline_set_part(struct MPContext *mpctx, int i, bool force)
     if (n->source == p->source && !force)
         return false;
     enum stop_play_reason orig_stop_play = mpctx->stop_play;
-    if (!mpctx->sh_video && mpctx->stop_play == KEEP_PLAYING)
+    if (!mpctx->d_video && mpctx->stop_play == KEEP_PLAYING)
         mpctx->stop_play = AT_END_OF_FILE;  // let audio uninit drain data
     uninit_player(mpctx, INITIALIZED_VCODEC | (mpctx->opts->fixed_vo ? 0 : INITIALIZED_VO) | (mpctx->opts->gapless_audio ? 0 : INITIALIZED_AO) | INITIALIZED_ACODEC | INITIALIZED_SUB);
     mpctx->stop_play = orig_stop_play;
@@ -1050,7 +1050,7 @@ static void play_current_file(struct MPContext *mpctx)
     assert(mpctx->stream == NULL);
     assert(mpctx->demuxer == NULL);
     assert(mpctx->d_audio == NULL);
-    assert(mpctx->sh_video == NULL);
+    assert(mpctx->d_video == NULL);
     assert(mpctx->sh_sub == NULL);
 
     char *stream_filename = mpctx->filename;
@@ -1189,14 +1189,14 @@ goto_reopen_demuxer: ;
 
     //================ SETUP STREAMS ==========================
 
-    if (opts->force_fps && mpctx->sh_video) {
-        mpctx->sh_video->fps = opts->force_fps;
-        MP_INFO(mpctx, "FPS forced to be %5.3f.\n", mpctx->sh_video->fps);
+    if (opts->force_fps && mpctx->d_video) {
+        mpctx->d_video->header->video->fps = opts->force_fps;
+        MP_INFO(mpctx, "FPS forced to be %5.3f.\n", mpctx->d_video->header->video->fps);
     }
 
     //==================== START PLAYING =======================
 
-    if (!mpctx->sh_video && !mpctx->d_audio) {
+    if (!mpctx->d_video && !mpctx->d_audio) {
         MP_FATAL(mpctx, "No video or audio streams selected.\n");
 #if HAVE_DVBIN
         if (mpctx->stream->type == STREAMTYPE_DVB) {
diff --git a/mpvcore/player/main.c b/mpvcore/player/main.c
index 528076bc57..84b5aa2651 100644
--- a/mpvcore/player/main.c
+++ b/mpvcore/player/main.c
@@ -200,7 +200,7 @@ static bool handle_help_options(struct MPContext *mpctx)
         opt_exit = 1;
     }
     if (opts->video_decoders && strcmp(opts->video_decoders, "help") == 0) {
-        struct mp_decoder_list *list = mp_video_decoder_list();
+        struct mp_decoder_list *list = video_decoder_list();
         mp_print_decoders(MSGT_CPLAYER, MSGL_INFO, "Video decoders:", list);
         talloc_free(list);
         opt_exit = 1;
diff --git a/mpvcore/player/mp_core.h b/mpvcore/player/mp_core.h
index aa0728d10f..f372c4e137 100644
--- a/mpvcore/player/mp_core.h
+++ b/mpvcore/player/mp_core.h
@@ -199,9 +199,9 @@ typedef struct MPContext {
     struct track *current_track[STREAM_TYPE_COUNT];
 
     struct sh_stream *sh[STREAM_TYPE_COUNT];
-    struct sh_video *sh_video;          // same as sh[STREAM_VIDEO]->video
     struct sh_sub *sh_sub;              // same as sh[STREAM_SUB]->sub
 
+    struct dec_video *d_video;
     struct dec_audio *d_audio;
 
     // Uses: accessing metadata (consider ordered chapters case, where the main
diff --git a/mpvcore/player/osd.c b/mpvcore/player/osd.c
index 04052c359e..6f81deae2e 100644
--- a/mpvcore/player/osd.c
+++ b/mpvcore/player/osd.c
@@ -85,7 +85,6 @@ void write_status_line(struct MPContext *mpctx, const char *line)
 void print_status(struct MPContext *mpctx)
 {
     struct MPOpts *opts = mpctx->opts;
-    sh_video_t * const sh_video = mpctx->sh_video;
 
     update_window_title(mpctx, false);
 
@@ -110,7 +109,7 @@ void print_status(struct MPContext *mpctx)
 
     if (mpctx->d_audio)
         saddf(&line, "A");
-    if (mpctx->sh_video)
+    if (mpctx->d_video)
         saddf(&line, "V");
     saddf(&line, ": ");
 
@@ -131,7 +130,7 @@ void print_status(struct MPContext *mpctx)
         saddf(&line, " x%4.2f", opts->playback_speed);
 
     // A-V sync
-    if (mpctx->d_audio && sh_video && mpctx->sync_audio_to_video) {
+    if (mpctx->d_audio && mpctx->d_video && mpctx->sync_audio_to_video) {
         if (mpctx->last_av_difference != MP_NOPTS_VALUE)
             saddf(&line, " A-V:%7.3f", mpctx->last_av_difference);
         else
@@ -152,7 +151,7 @@ void print_status(struct MPContext *mpctx)
 #endif
     {
         // VO stats
-        if (sh_video && mpctx->drop_frame_cnt)
+        if (mpctx->d_video && mpctx->drop_frame_cnt)
             saddf(&line, " Late: %d", mpctx->drop_frame_cnt);
     }
 
diff --git a/mpvcore/player/playloop.c b/mpvcore/player/playloop.c
index 51daf03bb1..591d31fbca 100644
--- a/mpvcore/player/playloop.c
+++ b/mpvcore/player/playloop.c
@@ -93,7 +93,7 @@ void pause_player(struct MPContext *mpctx)
     mpctx->osd_function = 0;
     mpctx->paused_for_cache = false;
 
-    if (mpctx->video_out && mpctx->sh_video && mpctx->video_out->config_ok)
+    if (mpctx->video_out && mpctx->d_video && mpctx->video_out->config_ok)
         vo_control(mpctx->video_out, VOCTRL_PAUSE, NULL);
 
     if (mpctx->ao && mpctx->d_audio)
@@ -126,7 +126,7 @@ void unpause_player(struct MPContext *mpctx)
 
     if (mpctx->ao && mpctx->d_audio)
         ao_resume(mpctx->ao);
-    if (mpctx->video_out && mpctx->sh_video && mpctx->video_out->config_ok)
+    if (mpctx->video_out && mpctx->d_video && mpctx->video_out->config_ok)
         vo_control(mpctx->video_out, VOCTRL_RESUME, NULL);      // resume video
     (void)get_relative_time(mpctx);     // ignore time that passed during pause
 }
@@ -153,7 +153,7 @@ static bool redraw_osd(struct MPContext *mpctx)
 
 void add_step_frame(struct MPContext *mpctx, int dir)
 {
-    if (!mpctx->sh_video)
+    if (!mpctx->d_video)
         return;
     if (dir > 0) {
         mpctx->step_frames += 1;
@@ -169,14 +169,14 @@ void add_step_frame(struct MPContext *mpctx, int dir)
 
 static void seek_reset(struct MPContext *mpctx, bool reset_ao)
 {
-    if (mpctx->sh_video) {
-        resync_video_stream(mpctx->sh_video);
+    if (mpctx->d_video) {
+        video_resync_stream(mpctx->d_video);
         vo_seek_reset(mpctx->video_out);
-        if (mpctx->sh_video->vf_initialized == 1)
-            vf_chain_seek_reset(mpctx->sh_video->vfilter);
-        mpctx->sh_video->num_buffered_pts = 0;
-        mpctx->sh_video->last_pts = MP_NOPTS_VALUE;
-        mpctx->sh_video->pts = MP_NOPTS_VALUE;
+        if (mpctx->d_video->vf_initialized == 1)
+            vf_chain_seek_reset(mpctx->d_video->vfilter);
+        mpctx->d_video->num_buffered_pts = 0;
+        mpctx->d_video->last_pts = MP_NOPTS_VALUE;
+        mpctx->d_video->pts = MP_NOPTS_VALUE;
         mpctx->video_pts = MP_NOPTS_VALUE;
         mpctx->delay = 0;
         mpctx->time_frame = 0;
@@ -589,7 +589,7 @@ do_seek:
 
 static void update_avsync(struct MPContext *mpctx)
 {
-    if (!mpctx->d_audio || !mpctx->sh_video)
+    if (!mpctx->d_audio || !mpctx->d_video)
         return;
 
     double a_pos = playing_audio_pts(mpctx);
@@ -624,7 +624,7 @@ static void adjust_sync(struct MPContext *mpctx, double frame_time)
         return;
 
     double a_pts = written_audio_pts(mpctx) - mpctx->delay;
-    double v_pts = mpctx->sh_video->pts;
+    double v_pts = mpctx->d_video->pts;
     double av_delay = a_pts - v_pts;
     // Try to sync vo_flip() so it will *finish* at given time
     av_delay += mpctx->last_vo_flip_duration;
@@ -797,7 +797,7 @@ static void handle_backstep(struct MPContext *mpctx)
     double current_pts = mpctx->last_vo_pts;
     mpctx->backstep_active = false;
     bool demuxer_ok = mpctx->demuxer && mpctx->demuxer->accurate_seek;
-    if (demuxer_ok && mpctx->sh_video && current_pts != MP_NOPTS_VALUE) {
+    if (demuxer_ok && mpctx->d_video && current_pts != MP_NOPTS_VALUE) {
         double seek_pts = find_previous_pts(mpctx, current_pts);
         if (seek_pts != MP_NOPTS_VALUE) {
             queue_seek(mpctx, MPSEEK_ABSOLUTE, seek_pts, 1);
@@ -863,7 +863,7 @@ static void handle_keep_open(struct MPContext *mpctx)
 void handle_force_window(struct MPContext *mpctx, bool reconfig)
 {
     // Don't interfere with real video playback
-    if (mpctx->sh_video)
+    if (mpctx->d_video)
         return;
 
     struct vo *vo = mpctx->video_out;
@@ -982,7 +982,7 @@ void run_playloop(struct MPContext *mpctx)
     }
 
     double buffered_audio = -1;
-    while (mpctx->sh_video) {   // never loops, for "break;" only
+    while (mpctx->d_video) {   // never loops, for "break;" only
         struct vo *vo = mpctx->video_out;
         update_fps(mpctx);
 
@@ -990,7 +990,7 @@ void run_playloop(struct MPContext *mpctx)
         if (!vo->frame_loaded && (!mpctx->paused || mpctx->restart_playback)) {
             double frame_time = update_video(mpctx, endpts);
             mp_dbg(MSGT_AVSYNC, MSGL_DBG2, "*** ftime=%5.3f ***\n", frame_time);
-            if (mpctx->sh_video->vf_initialized < 0) {
+            if (mpctx->d_video->vf_initialized < 0) {
                 MP_FATAL(mpctx, "\nFATAL: Could not initialize video filters "
                          "(-vf) or video output (-vo).\n");
                 int uninit = INITIALIZED_VCODEC;
@@ -1016,7 +1016,7 @@ void run_playloop(struct MPContext *mpctx)
         }
 
         if (endpts != MP_NOPTS_VALUE)
-            video_left &= mpctx->sh_video->pts < endpts;
+            video_left &= mpctx->d_video->pts < endpts;
 
         handle_heartbeat_cmd(mpctx);
 
@@ -1070,8 +1070,7 @@ void run_playloop(struct MPContext *mpctx)
         //=================== FLIP PAGE (VIDEO BLT): ======================
 
         vo_new_frame_imminent(vo);
-        struct sh_video *sh_video = mpctx->sh_video;
-        mpctx->video_pts = sh_video->pts;
+        mpctx->video_pts = mpctx->d_video->pts;
         mpctx->last_vo_pts = mpctx->video_pts;
         mpctx->playback_pts = mpctx->video_pts;
         update_subtitles(mpctx);
@@ -1182,7 +1181,7 @@ void run_playloop(struct MPContext *mpctx)
      * should trigger after seek only, when we know there's no audio
      * buffered.
      */
-    if ((mpctx->d_audio || mpctx->sh_video) && !audio_left && !video_left
+    if ((mpctx->d_audio || mpctx->d_video) && !audio_left && !video_left
         && (opts->gapless_audio || buffered_audio < 0.05)
         && (!mpctx->paused || was_restart)) {
         if (end_is_chapter) {
diff --git a/mpvcore/player/screenshot.c b/mpvcore/player/screenshot.c
index aeb15c0fd7..f564b5e9d7 100644
--- a/mpvcore/player/screenshot.c
+++ b/mpvcore/player/screenshot.c
@@ -315,8 +315,8 @@ static struct mp_image *screenshot_get(struct MPContext *mpctx, int mode)
         struct voctrl_screenshot_args args =
                             { .full_window = (mode == MODE_FULL_WINDOW) };
 
-        if (mpctx->sh_video && mpctx->sh_video->vfilter) {
-            struct vf_instance *vfilter = mpctx->sh_video->vfilter;
+        if (mpctx->d_video && mpctx->d_video->vfilter) {
+            struct vf_instance *vfilter = mpctx->d_video->vfilter;
             vfilter->control(vfilter, VFCTRL_SCREENSHOT, &args);
         }
 
diff --git a/mpvcore/player/sub.c b/mpvcore/player/sub.c
index 94368435d1..d659e74851 100644
--- a/mpvcore/player/sub.c
+++ b/mpvcore/player/sub.c
@@ -33,6 +33,7 @@
 #include "sub/dec_sub.h"
 #include "demux/demux.h"
 #include "video/mp_image.h"
+#include "video/decode/dec_video.h"
 
 #include "mp_core.h"
 
@@ -83,8 +84,8 @@ void update_subtitles(struct MPContext *mpctx)
     assert(track && sh_sub);
     struct dec_sub *dec_sub = sh_sub->dec_sub;
 
-    if (mpctx->sh_video && mpctx->sh_video->vf_input) {
-        struct mp_image_params params = *mpctx->sh_video->vf_input;
+    if (mpctx->d_video && mpctx->d_video->vf_input) {
+        struct mp_image_params params = *mpctx->d_video->vf_input;
         sub_control(dec_sub, SD_CTRL_SET_VIDEO_PARAMS, &params);
     }
 
@@ -194,9 +195,11 @@ void reinit_subs(struct MPContext *mpctx)
     assert(dec_sub);
 
     if (!sub_is_initialized(dec_sub)) {
-        int w = mpctx->sh_video ? mpctx->sh_video->disp_w : 0;
-        int h = mpctx->sh_video ? mpctx->sh_video->disp_h : 0;
-        float fps = mpctx->sh_video ? mpctx->sh_video->fps : 25;
+        struct sh_video *sh_video =
+            mpctx->d_video ? mpctx->d_video->header->video : NULL;
+        int w = sh_video ? sh_video->disp_w : 0;
+        int h = sh_video ? sh_video->disp_h : 0;
+        float fps = sh_video ? sh_video->fps : 25;
 
         set_dvdsub_fake_extradata(dec_sub, track->demuxer->stream, w, h);
         sub_set_video_res(dec_sub, w, h);
diff --git a/mpvcore/player/video.c b/mpvcore/player/video.c
index ac6406968f..333b694f9c 100644
--- a/mpvcore/player/video.c
+++ b/mpvcore/player/video.c
@@ -46,63 +46,64 @@
 void update_fps(struct MPContext *mpctx)
 {
 #if HAVE_ENCODING
-    struct sh_video *sh_video = mpctx->sh_video;
-    if (mpctx->encode_lavc_ctx && sh_video)
-        encode_lavc_set_video_fps(mpctx->encode_lavc_ctx, sh_video->fps);
+    struct dec_video *d_video = mpctx->d_video;
+    if (mpctx->encode_lavc_ctx && d_video)
+        encode_lavc_set_video_fps(mpctx->encode_lavc_ctx, d_video->header->video->fps);
 #endif
 }
 
 static void recreate_video_filters(struct MPContext *mpctx)
 {
     struct MPOpts *opts = mpctx->opts;
-    struct sh_video *sh_video = mpctx->sh_video;
-    assert(sh_video);
+    struct dec_video *d_video = mpctx->d_video;
+    assert(d_video);
 
-    vf_uninit_filter_chain(sh_video->vfilter);
+    vf_uninit_filter_chain(d_video->vfilter);
 
     char *vf_arg[] = {
         "_oldargs_", (char *)mpctx->video_out, NULL
     };
-    sh_video->vfilter = vf_open_filter(opts, NULL, "vo", vf_arg);
+    d_video->vfilter = vf_open_filter(opts, NULL, "vo", vf_arg);
 
-    sh_video->vfilter = append_filters(sh_video->vfilter, opts->vf_settings);
+    d_video->vfilter = append_filters(d_video->vfilter, opts->vf_settings);
 
-    struct vf_instance *vf = sh_video->vfilter;
+    struct vf_instance *vf = d_video->vfilter;
     mpctx->osd->render_subs_in_filter
         = vf->control(vf, VFCTRL_INIT_OSD, NULL) == VO_TRUE;
 }
 
 int reinit_video_filters(struct MPContext *mpctx)
 {
-    struct sh_video *sh_video = mpctx->sh_video;
+    struct dec_video *d_video = mpctx->d_video;
 
-    if (!sh_video)
+    if (!d_video)
         return -2;
 
     recreate_video_filters(mpctx);
-    video_reinit_vo(sh_video);
+    video_reinit_vo(d_video);
 
-    return sh_video->vf_initialized > 0 ? 0 : -1;
+    return d_video->vf_initialized > 0 ? 0 : -1;
 }
 
 int reinit_video_chain(struct MPContext *mpctx)
 {
     struct MPOpts *opts = mpctx->opts;
     assert(!(mpctx->initialized_flags & INITIALIZED_VCODEC));
+    assert(!mpctx->d_video);
     init_demux_stream(mpctx, STREAM_VIDEO);
-    sh_video_t *sh_video = mpctx->sh_video;
-    if (!sh_video)
+    struct sh_stream *sh = mpctx->sh[STREAM_VIDEO];
+    if (!sh)
         goto no_video;
 
     MP_VERBOSE(mpctx, "[V] fourcc:0x%X  size:%dx%d  fps:%5.3f\n",
-               mpctx->sh_video->format,
-               mpctx->sh_video->disp_w, mpctx->sh_video->disp_h,
-               mpctx->sh_video->fps);
+               sh->video->format,
+               sh->video->disp_w, sh->video->disp_h,
+               sh->video->fps);
     if (opts->force_fps)
-        mpctx->sh_video->fps = opts->force_fps;
+        sh->video->fps = opts->force_fps;
     update_fps(mpctx);
 
-    if (!mpctx->sh_video->fps && !opts->force_fps && !opts->correct_pts) {
+    if (!sh->video->fps && !opts->force_fps && !opts->correct_pts) {
         MP_ERR(mpctx, "FPS not specified in the "
                "header or invalid, use the -fps option.\n");
     }
@@ -121,26 +122,31 @@ int reinit_video_chain(struct MPContext *mpctx)
         mpctx->initialized_flags |= INITIALIZED_VO;
     }
 
-    // dynamic allocation only to make stheader.h lighter
-    talloc_free(sh_video->hwdec_info);
-    sh_video->hwdec_info = talloc_zero(sh_video, struct mp_hwdec_info);
-    vo_control(mpctx->video_out, VOCTRL_GET_HWDEC_INFO, sh_video->hwdec_info);
-
     update_window_title(mpctx, true);
 
-    if (stream_control(mpctx->sh_video->gsh->demuxer->stream,
-                       STREAM_CTRL_GET_ASPECT_RATIO, &ar) != STREAM_UNSUPPORTED)
-        mpctx->sh_video->stream_aspect = ar;
+    struct dec_video *d_video = talloc_zero(NULL, struct dec_video);
+    mpctx->d_video = d_video;
+    d_video->last_pts = MP_NOPTS_VALUE;
+    d_video->opts = mpctx->opts;
+    d_video->header = sh;
+    mpctx->initialized_flags |= INITIALIZED_VCODEC;
+
+    // dynamic allocation only to make stheader.h lighter
+    talloc_free(d_video->hwdec_info);
+    d_video->hwdec_info = talloc_zero(d_video, struct mp_hwdec_info);
+    vo_control(mpctx->video_out, VOCTRL_GET_HWDEC_INFO, d_video->hwdec_info);
+
+    if (stream_control(sh->demuxer->stream, STREAM_CTRL_GET_ASPECT_RATIO, &ar)
+            != STREAM_UNSUPPORTED)
+        d_video->stream_aspect = ar;
 
     recreate_video_filters(mpctx);
 
-    init_best_video_codec(sh_video, opts->video_decoders);
+    video_init_best_codec(d_video, opts->video_decoders);
 
-    if (!sh_video->initialized)
+    if (!d_video->initialized)
         goto err_out;
 
-    mpctx->initialized_flags |= INITIALIZED_VCODEC;
-
     bool saver_state = opts->pause || !opts->stop_screensaver;
     vo_control(mpctx->video_out, saver_state ? VOCTRL_RESTORE_SCREENSAVER
                                              : VOCTRL_KILL_SCREENSAVER, NULL);
@@ -148,12 +154,9 @@ int reinit_video_chain(struct MPContext *mpctx)
     vo_control(mpctx->video_out, mpctx->paused ? VOCTRL_PAUSE
                                                : VOCTRL_RESUME, NULL);
 
-    sh_video->last_pts = MP_NOPTS_VALUE;
-    sh_video->num_buffered_pts = 0;
-    sh_video->next_frame_time = 0;
     mpctx->last_vf_reconfig_count = 0;
     mpctx->restart_playback = true;
-    mpctx->sync_audio_to_video = !sh_video->gsh->attached_picture;
+    mpctx->sync_audio_to_video = !sh->attached_picture;
     mpctx->delay = 0;
     mpctx->vo_pts_history_seek_ts++;
 
@@ -185,10 +188,10 @@ void mp_force_video_refresh(struct MPContext *mpctx)
 
 static bool filter_output_queued_frame(struct MPContext *mpctx)
 {
-    struct sh_video *sh_video = mpctx->sh_video;
+    struct dec_video *d_video = mpctx->d_video;
     struct vo *video_out = mpctx->video_out;
 
-    struct mp_image *img = vf_chain_output_queued_frame(sh_video->vfilter);
+    struct mp_image *img = vf_chain_output_queued_frame(d_video->vfilter);
     if (img)
         vo_queue_image(video_out, img);
     talloc_free(img);
@@ -208,16 +211,16 @@ static bool load_next_vo_frame(struct MPContext *mpctx, bool eof)
 static void init_filter_params(struct MPContext *mpctx)
 {
     struct MPOpts *opts = mpctx->opts;
-    struct sh_video *sh_video = mpctx->sh_video;
+    struct dec_video *d_video = mpctx->d_video;
 
     // Note that the video decoder already initializes the filter chain. This
     // might recreate the chain a second time, which is not very elegant, but
     // allows us to test whether enabling deinterlacing works with the current
     // video format and other filters.
-    if (sh_video->vf_initialized != 1)
+    if (d_video->vf_initialized != 1)
         return;
 
-    if (sh_video->vf_reconfig_count <= mpctx->last_vf_reconfig_count) {
+    if (d_video->vf_reconfig_count <= mpctx->last_vf_reconfig_count) {
         if (opts->deinterlace >= 0) {
             mp_property_do("deinterlace", M_PROPERTY_SET, &opts->deinterlace,
                            mpctx);
@@ -225,18 +228,18 @@ static void init_filter_params(struct MPContext *mpctx)
     }
     // Setting filter params has to be "stable" (no change if params already
     // set) - checking the reconfig count is just an optimization.
-    mpctx->last_vf_reconfig_count = sh_video->vf_reconfig_count;
+    mpctx->last_vf_reconfig_count = d_video->vf_reconfig_count;
 }
 
 static void filter_video(struct MPContext *mpctx, struct mp_image *frame)
 {
-    struct sh_video *sh_video = mpctx->sh_video;
+    struct dec_video *d_video = mpctx->d_video;
 
     init_filter_params(mpctx);
 
-    frame->pts = sh_video->pts;
-    mp_image_set_params(frame, sh_video->vf_input);
-    vf_filter_frame(sh_video->vfilter, frame);
+    frame->pts = d_video->pts;
+    mp_image_set_params(frame, d_video->vf_input);
+    vf_filter_frame(d_video->vfilter, frame);
     filter_output_queued_frame(mpctx);
 }
 
@@ -249,8 +252,9 @@ static int check_framedrop(struct MPContext *mpctx, double frame_time)
     {
         float delay = opts->playback_speed * ao_get_delay(mpctx->ao);
         float d = delay - mpctx->delay;
+        float fps = mpctx->d_video->header->video->fps;
         if (frame_time < 0)
-            frame_time = mpctx->sh_video->fps > 0 ? 1.0 / mpctx->sh_video->fps : 0;
+            frame_time = fps > 0 ? 1.0 / fps : 0;
         // we should avoid dropping too many frames in sequence unless we
         // are too late. and we allow 100ms A-V delay here:
         if (d < -mpctx->dropped_frames * frame_time - 0.100 && !mpctx->paused
@@ -266,24 +270,25 @@ static int check_framedrop(struct MPContext *mpctx, double frame_time)
 
 static struct demux_packet *video_read_frame(struct MPContext *mpctx)
 {
-    sh_video_t *sh_video = mpctx->sh_video;
-    demuxer_t *demuxer = sh_video->gsh->demuxer;
-    float pts1 = sh_video->last_pts;
+    struct dec_video *d_video = mpctx->d_video;
+    sh_video_t *sh_video = d_video->header->video;
+    demuxer_t *demuxer = d_video->header->demuxer;
+    float pts1 = d_video->last_pts;
 
-    struct demux_packet *pkt = demux_read_packet(sh_video->gsh);
+    struct demux_packet *pkt = demux_read_packet(d_video->header);
     if (!pkt)
         return NULL; // EOF
 
     if (pkt->pts != MP_NOPTS_VALUE)
-        sh_video->last_pts = pkt->pts;
+        d_video->last_pts = pkt->pts;
 
     float frame_time = sh_video->fps > 0 ? 1.0f / sh_video->fps : 0;
 
     // override frame_time for variable/unknown FPS formats:
     if (!mpctx->opts->force_fps) {
-        double next_pts = demux_get_next_pts(sh_video->gsh);
-        double d = next_pts == MP_NOPTS_VALUE ? sh_video->last_pts - pts1
-                                              : next_pts - sh_video->last_pts;
+        double next_pts = demux_get_next_pts(d_video->header);
+        double d = next_pts == MP_NOPTS_VALUE ? d_video->last_pts - pts1
+                                              : next_pts - d_video->last_pts;
         if (d >= 0) {
             if (demuxer->type == DEMUXER_TYPE_TV) {
                 if (d > 0)
@@ -296,20 +301,20 @@ static struct demux_packet *video_read_frame(struct MPContext *mpctx)
         }
     }
 
-    sh_video->pts = sh_video->last_pts;
-    sh_video->next_frame_time = frame_time;
+    d_video->pts = d_video->last_pts;
+    d_video->next_frame_time = frame_time;
     return pkt;
 }
 
 static double update_video_nocorrect_pts(struct MPContext *mpctx)
 {
-    struct sh_video *sh_video = mpctx->sh_video;
+    struct dec_video *d_video = mpctx->d_video;
     double frame_time = 0;
     while (1) {
         // In nocorrect-pts mode there is no way to properly time these frames
         if (load_next_vo_frame(mpctx, false))
             break;
-        frame_time = sh_video->next_frame_time;
+        frame_time = d_video->next_frame_time;
         if (mpctx->restart_playback)
             frame_time = 0;
         struct demux_packet *pkt = video_read_frame(mpctx);
@@ -321,8 +326,8 @@ static double update_video_nocorrect_pts(struct MPContext *mpctx)
         update_fps(mpctx);
         int framedrop_type = check_framedrop(mpctx, frame_time);
 
-        void *decoded_frame = decode_video(sh_video, pkt, framedrop_type,
-                                           sh_video->pts);
+        void *decoded_frame = video_decode(d_video, pkt, framedrop_type,
+                                           d_video->pts);
         talloc_free(pkt);
         if (decoded_frame) {
             filter_video(mpctx, decoded_frame);
@@ -334,62 +339,61 @@ static double update_video_nocorrect_pts(struct MPContext *mpctx)
 
 static double update_video_attached_pic(struct MPContext *mpctx)
 {
-    struct sh_video *sh_video = mpctx->sh_video;
+    struct dec_video *d_video = mpctx->d_video;
 
     // Try to decode the picture multiple times, until it is displayed.
     if (mpctx->video_out->hasframe)
         return -1;
 
     struct mp_image *decoded_frame =
-            decode_video(sh_video, sh_video->gsh->attached_picture, 0, 0);
+            video_decode(d_video, d_video->header->attached_picture, 0, 0);
     if (decoded_frame)
         filter_video(mpctx, decoded_frame);
     load_next_vo_frame(mpctx, true);
-    mpctx->sh_video->pts = MP_NOPTS_VALUE;
+    d_video->pts = MP_NOPTS_VALUE;
     return 0;
 }
 
 static void determine_frame_pts(struct MPContext *mpctx)
 {
-    struct sh_video *sh_video = mpctx->sh_video;
+    struct dec_video *d_video = mpctx->d_video;
     struct MPOpts *opts = mpctx->opts;
 
     if (opts->user_pts_assoc_mode)
-        sh_video->pts_assoc_mode = opts->user_pts_assoc_mode;
-    else if (sh_video->pts_assoc_mode == 0) {
-        if (mpctx->sh_video->gsh->demuxer->timestamp_type == TIMESTAMP_TYPE_PTS
-            && sh_video->codec_reordered_pts != MP_NOPTS_VALUE)
-            sh_video->pts_assoc_mode = 1;
+        d_video->pts_assoc_mode = opts->user_pts_assoc_mode;
+    else if (d_video->pts_assoc_mode == 0) {
+        if (d_video->header->demuxer->timestamp_type == TIMESTAMP_TYPE_PTS
+            && d_video->codec_reordered_pts != MP_NOPTS_VALUE)
+            d_video->pts_assoc_mode = 1;
         else
-            sh_video->pts_assoc_mode = 2;
+            d_video->pts_assoc_mode = 2;
     } else {
-        int probcount1 = sh_video->num_reordered_pts_problems;
-        int probcount2 = sh_video->num_sorted_pts_problems;
-        if (sh_video->pts_assoc_mode == 2) {
+        int probcount1 = d_video->num_reordered_pts_problems;
+        int probcount2 = d_video->num_sorted_pts_problems;
+        if (d_video->pts_assoc_mode == 2) {
             int tmp = probcount1;
             probcount1 = probcount2;
             probcount2 = tmp;
         }
         if (probcount1 >= probcount2 * 1.5 + 2) {
-            sh_video->pts_assoc_mode = 3 - sh_video->pts_assoc_mode;
+            d_video->pts_assoc_mode = 3 - d_video->pts_assoc_mode;
             MP_VERBOSE(mpctx, "Switching to pts association mode "
-                       "%d.\n", sh_video->pts_assoc_mode);
+                       "%d.\n", d_video->pts_assoc_mode);
         }
     }
-    sh_video->pts = sh_video->pts_assoc_mode == 1 ?
-                    sh_video->codec_reordered_pts : sh_video->sorted_pts;
+    d_video->pts = d_video->pts_assoc_mode == 1 ?
+                   d_video->codec_reordered_pts : d_video->sorted_pts;
 }
 
 double update_video(struct MPContext *mpctx, double endpts)
 {
-    struct sh_video *sh_video = mpctx->sh_video;
+    struct dec_video *d_video = mpctx->d_video;
     struct vo *video_out = mpctx->video_out;
-    sh_video->vfilter->control(sh_video->vfilter, VFCTRL_SET_OSD_OBJ,
-                               mpctx->osd); // for vf_sub
+    vf_control(d_video->vfilter, VFCTRL_SET_OSD_OBJ, mpctx->osd); // for vf_sub
     if (!mpctx->opts->correct_pts)
         return update_video_nocorrect_pts(mpctx);
 
-    if (sh_video->gsh->attached_picture)
+    if (d_video->header->attached_picture)
         return update_video_attached_pic(mpctx);
 
     double pts;
@@ -400,7 +404,7 @@ double update_video(struct MPContext *mpctx, double endpts)
         pts = MP_NOPTS_VALUE;
         struct demux_packet *pkt = NULL;
         while (1) {
-            pkt = demux_read_packet(mpctx->sh_video->gsh);
+            pkt = demux_read_packet(d_video->header);
             if (!pkt || pkt->len)
                 break;
             /* Packets with size 0 are assumed to not correspond to frames,
@@ -417,7 +421,7 @@ double update_video(struct MPContext *mpctx, double endpts)
         int framedrop_type = mpctx->hrseek_active && mpctx->hrseek_framedrop ?
                              1 : check_framedrop(mpctx, -1);
         struct mp_image *decoded_frame =
-            decode_video(sh_video, pkt, framedrop_type, pts);
+            video_decode(d_video, pkt, framedrop_type, pts);
         talloc_free(pkt);
         if (decoded_frame) {
             determine_frame_pts(mpctx);
@@ -436,9 +440,9 @@ double update_video(struct MPContext *mpctx, double endpts)
     if (pts == MP_NOPTS_VALUE) {
         MP_ERR(mpctx, "Video pts after filters MISSING\n");
         // Try to use decoder pts from before filters
-        pts = sh_video->pts;
+        pts = d_video->pts;
         if (pts == MP_NOPTS_VALUE)
-            pts = sh_video->last_pts;
+            pts = d_video->last_pts;
     }
     if (endpts == MP_NOPTS_VALUE || pts < endpts)
         add_frame_pts(mpctx, pts);
@@ -447,29 +451,29 @@ double update_video(struct MPContext *mpctx, double endpts)
         return 0;
     }
     mpctx->hrseek_active = false;
-    sh_video->pts = pts;
-    if (sh_video->last_pts == MP_NOPTS_VALUE)
-        sh_video->last_pts = sh_video->pts;
-    else if (sh_video->last_pts > sh_video->pts) {
+    d_video->pts = pts;
+    if (d_video->last_pts == MP_NOPTS_VALUE) {
+        d_video->last_pts = d_video->pts;
+    } else if (d_video->last_pts > d_video->pts) {
         MP_WARN(mpctx, "Decreasing video pts: %f < %f\n",
-                sh_video->pts, sh_video->last_pts);
+                d_video->pts, d_video->last_pts);
         /* If the difference in pts is small treat it as jitter around the
          * right value (possibly caused by incorrect timestamp ordering) and
          * just show this frame immediately after the last one.
          * Treat bigger differences as timestamp resets and start counting
          * timing of later frames from the position of this one. */
-        if (sh_video->last_pts - sh_video->pts > 0.5)
-            sh_video->last_pts = sh_video->pts;
+        if (d_video->last_pts - d_video->pts > 0.5)
+            d_video->last_pts = d_video->pts;
         else
-            sh_video->pts = sh_video->last_pts;
-    } else if (sh_video->pts >= sh_video->last_pts + 60) {
+            d_video->pts = d_video->last_pts;
+    } else if (d_video->pts >= d_video->last_pts + 60) {
         // Assume a PTS difference >= 60 seconds is a discontinuity.
         MP_WARN(mpctx, "Jump in video pts: %f -> %f\n",
-                sh_video->last_pts, sh_video->pts);
-        sh_video->last_pts = sh_video->pts;
+                d_video->last_pts, d_video->pts);
+        d_video->last_pts = d_video->pts;
     }
-    double frame_time = sh_video->pts - sh_video->last_pts;
-    sh_video->last_pts = sh_video->pts;
+    double frame_time = d_video->pts - d_video->last_pts;
+    d_video->last_pts = d_video->pts;
     if (mpctx->d_audio)
         mpctx->delay -= frame_time;
     return frame_time;
diff --git a/video/decode/dec_video.c b/video/decode/dec_video.c
index e6f56a3d57..cdfbd816a9 100644
--- a/video/decode/dec_video.c
+++ b/video/decode/dec_video.c
@@ -56,17 +56,17 @@ const vd_functions_t * const mpcodecs_vd_drivers[] = {
     NULL
 };
 
-int vd_control(struct sh_video *sh_video, int cmd, void *arg)
+int video_vd_control(struct dec_video *d_video, int cmd, void *arg)
 {
-    const struct vd_functions *vd = sh_video->vd_driver;
+    const struct vd_functions *vd = d_video->vd_driver;
     if (vd)
-        return vd->control(sh_video, cmd, arg);
+        return vd->control(d_video, cmd, arg);
     return CONTROL_UNKNOWN;
 }
 
-int set_video_colors(sh_video_t *sh_video, const char *item, int value)
+int video_set_colors(struct dec_video *d_video, const char *item, int value)
 {
-    vf_instance_t *vf = sh_video->vfilter;
+    vf_instance_t *vf = d_video->vfilter;
     vf_equalizer_t data;
 
     data.item = item;
@@ -83,9 +83,9 @@ int set_video_colors(sh_video_t *sh_video, const char *item, int value)
     return 0;
 }
 
-int get_video_colors(sh_video_t *sh_video, const char *item, int *value)
+int video_get_colors(struct dec_video *d_video, const char *item, int *value)
 {
-    vf_instance_t *vf = sh_video->vfilter;
+    vf_instance_t *vf = d_video->vfilter;
     vf_equalizer_t data;
 
     data.item = item;
@@ -101,55 +101,48 @@ int get_video_colors(sh_video_t *sh_video, const char *item, int *value)
     return 0;
 }
 
-void resync_video_stream(sh_video_t *sh_video)
+void video_resync_stream(struct dec_video *d_video)
 {
-    vd_control(sh_video, VDCTRL_RESYNC_STREAM, NULL);
-    sh_video->prev_codec_reordered_pts = MP_NOPTS_VALUE;
-    sh_video->prev_sorted_pts = MP_NOPTS_VALUE;
+    video_vd_control(d_video, VDCTRL_RESYNC_STREAM, NULL);
+    d_video->prev_codec_reordered_pts = MP_NOPTS_VALUE;
+    d_video->prev_sorted_pts = MP_NOPTS_VALUE;
 }
 
-void video_reinit_vo(struct sh_video *sh_video)
+void video_reinit_vo(struct dec_video *d_video)
 {
-    vd_control(sh_video, VDCTRL_REINIT_VO, NULL);
+    video_vd_control(d_video, VDCTRL_REINIT_VO, NULL);
 }
 
-int get_current_video_decoder_lag(sh_video_t *sh_video)
+void video_uninit(struct dec_video *d_video)
 {
-    int ret = -1;
-    vd_control(sh_video, VDCTRL_QUERY_UNSEEN_FRAMES, &ret);
-    return ret;
+    if (d_video->initialized) {
+        mp_tmsg(MSGT_DECVIDEO, MSGL_V, "Uninit video.\n");
+        d_video->vd_driver->uninit(d_video);
+    }
+    talloc_free(d_video->priv);
+    d_video->priv = NULL;
+    vf_uninit_filter_chain(d_video->vfilter);
+    d_video->vfilter = NULL;
+    talloc_free(d_video);
 }
 
-void uninit_video(sh_video_t *sh_video)
+static int init_video_codec(struct dec_video *d_video, const char *decoder)
 {
-    if (!sh_video->initialized)
-        return;
-    mp_tmsg(MSGT_DECVIDEO, MSGL_V, "Uninit video.\n");
-    sh_video->vd_driver->uninit(sh_video);
-    vf_uninit_filter_chain(sh_video->vfilter);
-    sh_video->vfilter = NULL;
-    talloc_free(sh_video->gsh->decoder_desc);
-    sh_video->gsh->decoder_desc = NULL;
-    sh_video->initialized = 0;
-}
+    assert(!d_video->initialized);
 
-static int init_video_codec(sh_video_t *sh_video, const char *decoder)
-{
-    assert(!sh_video->initialized);
-
-    if (!sh_video->vd_driver->init(sh_video, decoder)) {
+    if (!d_video->vd_driver->init(d_video, decoder)) {
         mp_tmsg(MSGT_DECVIDEO, MSGL_V, "Video decoder init failed.\n");
-        //uninit_video(sh_video);
+        //uninit_video(d_video);
         return 0;
     }
 
-    sh_video->initialized = 1;
-    sh_video->prev_codec_reordered_pts = MP_NOPTS_VALUE;
-    sh_video->prev_sorted_pts = MP_NOPTS_VALUE;
+    d_video->initialized = 1;
+    d_video->prev_codec_reordered_pts = MP_NOPTS_VALUE;
+    d_video->prev_sorted_pts = MP_NOPTS_VALUE;
     return 1;
 }
 
-struct mp_decoder_list *mp_video_decoder_list(void)
+struct mp_decoder_list *video_decoder_list(void)
 {
     struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
     for (int i = 0; mpcodecs_vd_drivers[i] != NULL; i++)
@@ -160,7 +153,7 @@ struct mp_decoder_list *mp_video_decoder_list(void)
 static struct mp_decoder_list *mp_select_video_decoders(const char *codec,
                                                         char *selection)
 {
-    struct mp_decoder_list *list = mp_video_decoder_list();
+    struct mp_decoder_list *list = video_decoder_list();
     struct mp_decoder_list *new = mp_select_decoders(list, codec, selection);
     talloc_free(list);
     return new;
@@ -175,13 +168,13 @@ static const struct vd_functions *find_driver(const char *name)
     return NULL;
 }
 
-int init_best_video_codec(sh_video_t *sh_video, char* video_decoders)
+int video_init_best_codec(struct dec_video *d_video, char* video_decoders)
 {
-    assert(!sh_video->initialized);
+    assert(!d_video->initialized);
 
     struct mp_decoder_entry *decoder = NULL;
     struct mp_decoder_list *list =
-        mp_select_video_decoders(sh_video->gsh->codec, video_decoders);
+        mp_select_video_decoders(d_video->header->codec, video_decoders);
 
     mp_print_decoders(MSGT_DECVIDEO, MSGL_V, "Codec list:", list);
 
@@ -192,42 +185,43 @@ int init_best_video_codec(sh_video_t *sh_video, char* video_decoders)
             continue;
         mp_tmsg(MSGT_DECVIDEO, MSGL_V, "Opening video decoder %s:%s\n",
                 sel->family, sel->decoder);
-        sh_video->vd_driver = driver;
-        if (init_video_codec(sh_video, sel->decoder)) {
+        d_video->vd_driver = driver;
+        if (init_video_codec(d_video, sel->decoder)) {
             decoder = sel;
             break;
         }
-        sh_video->vd_driver = NULL;
+        d_video->vd_driver = NULL;
         mp_tmsg(MSGT_DECVIDEO, MSGL_WARN, "Video decoder init failed for "
                 "%s:%s\n", sel->family, sel->decoder);
     }
 
-    if (sh_video->initialized) {
-        sh_video->gsh->decoder_desc =
-            talloc_asprintf(NULL, "%s [%s:%s]", decoder->desc, decoder->family,
+    if (d_video->initialized) {
+        d_video->decoder_desc =
+            talloc_asprintf(d_video, "%s [%s:%s]", decoder->desc, decoder->family,
                             decoder->decoder);
         mp_msg(MSGT_DECVIDEO, MSGL_INFO, "Selected video codec: %s\n",
-               sh_video->gsh->decoder_desc);
+               d_video->decoder_desc);
     } else {
         mp_msg(MSGT_DECVIDEO, MSGL_ERR,
                "Failed to initialize a video decoder for codec '%s'.\n",
-               sh_video->gsh->codec ? sh_video->gsh->codec : "<unknown>");
+               d_video->header->codec ? d_video->header->codec : "<unknown>");
     }
 
     talloc_free(list);
-    return sh_video->initialized;
+    return d_video->initialized;
 }
 
-void *decode_video(sh_video_t *sh_video, struct demux_packet *packet,
+void *video_decode(struct dec_video *d_video, struct demux_packet *packet,
                    int drop_frame, double pts)
 {
     mp_image_t *mpi = NULL;
-    struct MPOpts *opts = sh_video->opts;
+    struct MPOpts *opts = d_video->opts;
 
     if (opts->correct_pts && pts != MP_NOPTS_VALUE) {
-        int delay = get_current_video_decoder_lag(sh_video);
+        int delay = -1;
+        video_vd_control(d_video, VDCTRL_QUERY_UNSEEN_FRAMES, &delay);
         if (delay >= 0) {
-            if (delay > sh_video->num_buffered_pts)
+            if (delay > d_video->num_buffered_pts)
 #if 0
                 // this is disabled because vd_ffmpeg reports the same lag
                 // after seek even when there are no buffered frames,
@@ -237,24 +231,24 @@ void *decode_video(sh_video_t *sh_video, struct demux_packet *packet,
                 ;
 #endif
             else
-                sh_video->num_buffered_pts = delay;
+                d_video->num_buffered_pts = delay;
         }
-        if (sh_video->num_buffered_pts ==
-            sizeof(sh_video->buffered_pts) / sizeof(double))
+        if (d_video->num_buffered_pts ==
+            sizeof(d_video->buffered_pts) / sizeof(double))
             mp_msg(MSGT_DECVIDEO, MSGL_ERR, "Too many buffered pts\n");
         else {
             int i, j;
-            for (i = 0; i < sh_video->num_buffered_pts; i++)
-                if (sh_video->buffered_pts[i] < pts)
+            for (i = 0; i < d_video->num_buffered_pts; i++)
+                if (d_video->buffered_pts[i] < pts)
                     break;
-            for (j = sh_video->num_buffered_pts; j > i; j--)
-                sh_video->buffered_pts[j] = sh_video->buffered_pts[j - 1];
-            sh_video->buffered_pts[i] = pts;
-            sh_video->num_buffered_pts++;
+            for (j = d_video->num_buffered_pts; j > i; j--)
+                d_video->buffered_pts[j] = d_video->buffered_pts[j - 1];
+            d_video->buffered_pts[i] = pts;
+            d_video->num_buffered_pts++;
         }
     }
 
-    mpi = sh_video->vd_driver->decode(sh_video, packet, drop_frame, &pts);
+    mpi = d_video->vd_driver->decode(d_video, packet, drop_frame, &pts);
 
     //------------------------ frame decoded. --------------------
 
@@ -268,39 +262,41 @@ void *decode_video(sh_video_t *sh_video, struct demux_packet *packet,
     else if (opts->field_dominance == 1)
         mpi->fields &= ~MP_IMGFIELD_TOP_FIRST;
 
-    double prevpts = sh_video->codec_reordered_pts;
-    sh_video->prev_codec_reordered_pts = prevpts;
-    sh_video->codec_reordered_pts = pts;
+    double prevpts = d_video->codec_reordered_pts;
+    d_video->prev_codec_reordered_pts = prevpts;
+    d_video->codec_reordered_pts = pts;
     if (prevpts != MP_NOPTS_VALUE && pts <= prevpts
         || pts == MP_NOPTS_VALUE)
-        sh_video->num_reordered_pts_problems++;
-    prevpts = sh_video->sorted_pts;
+        d_video->num_reordered_pts_problems++;
+    prevpts = d_video->sorted_pts;
     if (opts->correct_pts) {
-        if (sh_video->num_buffered_pts) {
-            sh_video->num_buffered_pts--;
-            sh_video->sorted_pts =
-                sh_video->buffered_pts[sh_video->num_buffered_pts];
+        if (d_video->num_buffered_pts) {
+            d_video->num_buffered_pts--;
+            d_video->sorted_pts =
+                d_video->buffered_pts[d_video->num_buffered_pts];
         } else {
             mp_msg(MSGT_CPLAYER, MSGL_ERR,
                    "No pts value from demuxer to use for frame!\n");
-            sh_video->sorted_pts = MP_NOPTS_VALUE;
+            d_video->sorted_pts = MP_NOPTS_VALUE;
         }
     }
-    pts = sh_video->sorted_pts;
+    pts = d_video->sorted_pts;
     if (prevpts != MP_NOPTS_VALUE && pts <= prevpts
         || pts == MP_NOPTS_VALUE)
-        sh_video->num_sorted_pts_problems++;
+        d_video->num_sorted_pts_problems++;
     return mpi;
 }
 
-int mpcodecs_reconfig_vo(sh_video_t *sh, const struct mp_image_params *params)
+int mpcodecs_reconfig_vo(struct dec_video *d_video,
+                         const struct mp_image_params *params)
 {
-    struct MPOpts *opts = sh->opts;
-    vf_instance_t *vf = sh->vfilter;
+    struct MPOpts *opts = d_video->opts;
+    vf_instance_t *vf = d_video->vfilter;
     int vocfg_flags = 0;
     struct mp_image_params p = *params;
+    struct sh_video *sh = d_video->header->video;
 
-    sh->vf_reconfig_count++;
+    d_video->vf_reconfig_count++;
 
     mp_msg(MSGT_DECVIDEO, MSGL_V,
            "VIDEO:  %dx%d  %5.3f fps  %5.1f kbps (%4.1f kB/s)\n",
@@ -336,24 +332,24 @@ int mpcodecs_reconfig_vo(sh_video_t *sh, const struct mp_image_params *params)
             "e.g. -vf filter,scale instead of -vf filter.\n");
         mp_tmsg(MSGT_VFILTER, MSGL_WARN, "Attempted filter chain:\n");
         vf_print_filter_chain(MSGL_WARN, vf);
-        sh->vf_initialized = -1;
+        d_video->vf_initialized = -1;
         return -1;               // failed
     }
-    sh->vfilter = vf;
+    d_video->vfilter = vf;
 
     // autodetect flipping
     bool flip = opts->flip;
     if (flip && !(flags & VFCAP_FLIP)) {
         // we need to flip, but no flipping filter avail.
         vf_add_before_vo(&vf, "flip", NULL);
-        sh->vfilter = vf;
+        d_video->vfilter = vf;
         flip = false;
     }
     // time to do aspect ratio corrections...
 
     float force_aspect = opts->movie_aspect;
-    if (force_aspect > -1.0 && sh->stream_aspect != 0.0)
-        force_aspect = sh->stream_aspect;
+    if (force_aspect > -1.0 && d_video->stream_aspect != 0.0)
+        force_aspect = d_video->stream_aspect;
 
     if (force_aspect >= 0)
         vf_set_dar(&p.d_w, &p.d_h, p.w, p.h, force_aspect);
@@ -386,29 +382,29 @@ int mpcodecs_reconfig_vo(sh_video_t *sh, const struct mp_image_params *params)
 
     if (vf_reconfig_wrapper(vf, &p, vocfg_flags) < 0) {
         mp_tmsg(MSGT_CPLAYER, MSGL_WARN, "FATAL: Cannot initialize video driver.\n");
-        sh->vf_initialized = -1;
+        d_video->vf_initialized = -1;
         return -1;
     }
 
     mp_tmsg(MSGT_VFILTER, MSGL_V, "Video filter chain:\n");
     vf_print_filter_chain(MSGL_V, vf);
 
-    sh->vf_initialized = 1;
+    d_video->vf_initialized = 1;
 
-    if (!sh->vf_input)
-        sh->vf_input = talloc(sh, struct mp_image_params);
-    *sh->vf_input = p;
+    if (!d_video->vf_input)
+        d_video->vf_input = talloc(sh, struct mp_image_params);
+    *d_video->vf_input = p;
 
     if (opts->gamma_gamma != 1000)
-        set_video_colors(sh, "gamma", opts->gamma_gamma);
+        video_set_colors(d_video, "gamma", opts->gamma_gamma);
     if (opts->gamma_brightness != 1000)
-        set_video_colors(sh, "brightness", opts->gamma_brightness);
+        video_set_colors(d_video, "brightness", opts->gamma_brightness);
     if (opts->gamma_contrast != 1000)
-        set_video_colors(sh, "contrast", opts->gamma_contrast);
+        video_set_colors(d_video, "contrast", opts->gamma_contrast);
     if (opts->gamma_saturation != 1000)
-        set_video_colors(sh, "saturation", opts->gamma_saturation);
+        video_set_colors(d_video, "saturation", opts->gamma_saturation);
     if (opts->gamma_hue != 1000)
-        set_video_colors(sh, "hue", opts->gamma_hue);
+        video_set_colors(d_video, "hue", opts->gamma_hue);
 
     return 0;
 }
diff --git a/video/decode/dec_video.h b/video/decode/dec_video.h
index 3f163bb1f7..b706910e5b 100644
--- a/video/decode/dec_video.h
+++ b/video/decode/dec_video.h
@@ -20,24 +20,57 @@
 #define MPLAYER_DEC_VIDEO_H
 
 #include "demux/stheader.h"
+#include "video/hwdec.h"
+#include "video/mp_image.h"
 
 struct osd_state;
 struct mp_decoder_list;
 
-struct mp_decoder_list *mp_video_decoder_list(void);
+struct dec_video {
+    struct MPOpts *opts;
+    struct vf_instance *vfilter;  // video filter chain
+    const struct vd_functions *vd_driver;
+    int vf_initialized;   // -1 failed, 0 not done, 1 done
+    long vf_reconfig_count; // incremented each mpcodecs_reconfig_vo() call
+    struct mp_image_params *vf_input; // video filter input params
+    struct mp_hwdec_info *hwdec_info; // video output hwdec handles
+    int initialized;
+    struct sh_stream *header;
 
-int init_best_video_codec(sh_video_t *sh_video, char* video_decoders);
-void uninit_video(sh_video_t *sh_video);
+    char *decoder_desc;
+
+    void *priv;
+
+    float next_frame_time;
+    double last_pts;
+    double buffered_pts[32];
+    int num_buffered_pts;
+    double codec_reordered_pts;
+    double prev_codec_reordered_pts;
+    int num_reordered_pts_problems;
+    double sorted_pts;
+    double prev_sorted_pts;
+    int num_sorted_pts_problems;
+    int pts_assoc_mode;
+    double pts;
+
+    float stream_aspect;  // aspect ratio in media headers (DVD IFO files)
+    int i_bps;            // == bitrate  (compressed bytes/sec)
+};
+
+struct mp_decoder_list *video_decoder_list(void);
+
+int video_init_best_codec(struct dec_video *d_video, char* video_decoders);
+void video_uninit(struct dec_video *d_video);
 
 struct demux_packet;
-void *decode_video(sh_video_t *sh_video, struct demux_packet *packet,
+void *video_decode(struct dec_video *d_video, struct demux_packet *packet,
                    int drop_frame, double pts);
 
-int get_video_colors(sh_video_t *sh_video, const char *item, int *value);
-int set_video_colors(sh_video_t *sh_video, const char *item, int value);
-void resync_video_stream(sh_video_t *sh_video);
-void video_reinit_vo(struct sh_video *sh_video);
-int get_current_video_decoder_lag(sh_video_t *sh_video);
-int vd_control(struct sh_video *sh_video, int cmd, void *arg);
+int video_get_colors(struct dec_video *d_video, const char *item, int *value);
+int video_set_colors(struct dec_video *d_video, const char *item, int value);
+void video_resync_stream(struct dec_video *d_video);
+void video_reinit_vo(struct dec_video *d_video);
+int video_vd_control(struct dec_video *d_video, int cmd, void *arg);
 
 #endif /* MPLAYER_DEC_VIDEO_H */
diff --git a/video/decode/lavc_dr1.c b/video/decode/lavc_dr1.c
index 15fc44a445..a8de29eb1a 100644
--- a/video/decode/lavc_dr1.c
+++ b/video/decode/lavc_dr1.c
@@ -136,8 +136,8 @@ static int alloc_buffer(FramePool *pool, AVCodecContext *s)
 
 int mp_codec_get_buffer(AVCodecContext *s, AVFrame *frame)
 {
-    sh_video_t *sh = s->opaque;
-    struct lavc_ctx *ctx = sh->context;
+    struct dec_video *vd = s->opaque;
+    struct lavc_ctx *ctx = vd->priv;
 
     if (!ctx->dr1_buffer_pool) {
         ctx->dr1_buffer_pool = av_mallocz(sizeof(*ctx->dr1_buffer_pool));
diff --git a/video/decode/vd.h b/video/decode/vd.h
index 488f9fa061..dd2536ab2a 100644
--- a/video/decode/vd.h
+++ b/video/decode/vd.h
@@ -21,6 +21,7 @@
 
 #include "video/mp_image.h"
 #include "demux/stheader.h"
+#include "dec_video.h"
 
 struct demux_packet;
 struct mp_decoder_list;
@@ -30,10 +31,10 @@ typedef struct vd_functions
 {
     const char *name;
     void (*add_decoders)(struct mp_decoder_list *list);
-    int (*init)(sh_video_t *sh, const char *decoder);
-    void (*uninit)(sh_video_t *sh);
-    int (*control)(sh_video_t *sh, int cmd, void *arg);
-    struct mp_image *(*decode)(struct sh_video *sh, struct demux_packet *pkt,
+    int (*init)(struct dec_video *vd, const char *decoder);
+    void (*uninit)(struct dec_video *vd);
+    int (*control)(struct dec_video *vd, int cmd, void *arg);
+    struct mp_image *(*decode)(struct dec_video *vd, struct demux_packet *pkt,
                                int flags, double *reordered_pts);
 } vd_functions_t;
 
@@ -47,6 +48,6 @@ enum vd_ctrl {
     VDCTRL_REINIT_VO, // reinit filter/VO chain
 };
 
-int mpcodecs_reconfig_vo(sh_video_t *sh, const struct mp_image_params *params);
+int mpcodecs_reconfig_vo(struct dec_video *vd, const struct mp_image_params *params);
 
 #endif /* MPLAYER_VD_H */
diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c
index e6ed8e5602..f7768f130f 100644
--- a/video/decode/vd_lavc.c
+++ b/video/decode/vd_lavc.c
@@ -60,15 +60,15 @@
 
 #include "mpvcore/m_option.h"
 
-static void init_avctx(sh_video_t *sh, const char *decoder,
+static void init_avctx(struct dec_video *vd, const char *decoder,
                        struct vd_lavc_hwdec *hwdec);
-static void uninit_avctx(sh_video_t *sh);
+static void uninit_avctx(struct dec_video *vd);
 static void setup_refcounting_hw(struct AVCodecContext *s);
 
 static enum PixelFormat get_format_hwdec(struct AVCodecContext *avctx,
                                          const enum PixelFormat *pix_fmt);
 
-static void uninit(struct sh_video *sh);
+static void uninit(struct dec_video *vd);
 
 #define OPT_BASE_STRUCT struct MPOpts
 
@@ -116,9 +116,9 @@ static struct vd_lavc_hwdec *find_hwcodec(enum hwdec_type api)
     return NULL;
 }
 
-static bool hwdec_codec_allowed(sh_video_t *sh, const char *codec)
+static bool hwdec_codec_allowed(struct dec_video *vd, const char *codec)
 {
-    bstr s = bstr0(sh->opts->hwdec_codecs);
+    bstr s = bstr0(vd->opts->hwdec_codecs);
     while (s.len) {
         bstr item;
         bstr_split_tok(s, ",", &item, &s);
@@ -213,7 +213,7 @@ static int hwdec_probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info,
     return r;
 }
 
-static bool probe_hwdec(sh_video_t *sh, bool autoprobe, enum hwdec_type api,
+static bool probe_hwdec(struct dec_video *vd, bool autoprobe, enum hwdec_type api,
                         const char *decoder, struct vd_lavc_hwdec **use_hwdec,
                         const char **use_decoder)
 {
@@ -224,7 +224,7 @@ static bool probe_hwdec(sh_video_t *sh, bool autoprobe, enum hwdec_type api,
         return false;
     }
     const char *hw_decoder = NULL;
-    int r = hwdec_probe(hwdec, sh->hwdec_info, decoder, &hw_decoder);
+    int r = hwdec_probe(hwdec, vd->hwdec_info, decoder, &hw_decoder);
     if (r >= 0) {
         *use_hwdec = hwdec;
         *use_decoder = hw_decoder;
@@ -240,11 +240,11 @@ static bool probe_hwdec(sh_video_t *sh, bool autoprobe, enum hwdec_type api,
 }
 
 
-static int init(sh_video_t *sh, const char *decoder)
+static int init(struct dec_video *vd, const char *decoder)
 {
     vd_ffmpeg_ctx *ctx;
-    ctx = sh->context = talloc_zero(NULL, vd_ffmpeg_ctx);
-    ctx->opts = sh->opts;
+    ctx = vd->priv = talloc_zero(NULL, vd_ffmpeg_ctx);
+    ctx->opts = vd->opts;
     ctx->non_dr1_pool = talloc_steal(ctx, mp_image_pool_new(16));
 
     if (bstr_endswith0(bstr0(decoder), "_vdpau")) {
@@ -254,22 +254,22 @@ static int init(sh_video_t *sh, const char *decoder)
                 "option can be used to restrict which codecs are\nenabled, "
                 "otherwise all hardware decoding is tried for all codecs.\n",
                 decoder);
-        uninit(sh);
+        uninit(vd);
         return 0;
     }
 
     struct vd_lavc_hwdec *hwdec = NULL;
     const char *hw_decoder = NULL;
 
-    if (hwdec_codec_allowed(sh, decoder)) {
-        if (sh->opts->hwdec_api == HWDEC_AUTO) {
+    if (hwdec_codec_allowed(vd, decoder)) {
+        if (vd->opts->hwdec_api == HWDEC_AUTO) {
             for (int n = 0; hwdec_list[n]; n++) {
-                if (probe_hwdec(sh, true, hwdec_list[n]->type, decoder,
+                if (probe_hwdec(vd, true, hwdec_list[n]->type, decoder,
                     &hwdec, &hw_decoder))
                     break;
             }
-        } else if (sh->opts->hwdec_api != HWDEC_NONE) {
-            probe_hwdec(sh, false, sh->opts->hwdec_api, decoder,
+        } else if (vd->opts->hwdec_api != HWDEC_NONE) {
+            probe_hwdec(vd, false, vd->opts->hwdec_api, decoder,
                         &hwdec, &hw_decoder);
         }
     } else {
@@ -282,21 +282,21 @@ static int init(sh_video_t *sh, const char *decoder)
         if (hw_decoder)
             decoder = hw_decoder;
         mp_tmsg(MSGT_DECVIDEO, MSGL_INFO, "Trying to use hardware decoding.\n");
-    } else if (sh->opts->hwdec_api != HWDEC_NONE) {
+    } else if (vd->opts->hwdec_api != HWDEC_NONE) {
         mp_tmsg(MSGT_DECVIDEO, MSGL_INFO, "Using software decoding.\n");
     }
 
-    init_avctx(sh, decoder, hwdec);
+    init_avctx(vd, decoder, hwdec);
     if (!ctx->avctx) {
         if (ctx->software_fallback_decoder) {
             mp_tmsg(MSGT_DECVIDEO, MSGL_ERR, "Error initializing hardware "
                     "decoding, falling back to software decoding.\n");
             decoder = ctx->software_fallback_decoder;
             ctx->software_fallback_decoder = NULL;
-            init_avctx(sh, decoder, NULL);
+            init_avctx(vd, decoder, NULL);
         }
         if (!ctx->avctx) {
-            uninit(sh);
+            uninit(vd);
             return 0;
         }
     }
@@ -361,12 +361,13 @@ static void set_from_bih(AVCodecContext *avctx, uint32_t format,
     avctx->coded_height = bih->biHeight;
 }
 
-static void init_avctx(sh_video_t *sh, const char *decoder,
+static void init_avctx(struct dec_video *vd, const char *decoder,
                        struct vd_lavc_hwdec *hwdec)
 {
-    vd_ffmpeg_ctx *ctx = sh->context;
-    struct lavc_param *lavc_param = &sh->opts->lavc_param;
+    vd_ffmpeg_ctx *ctx = vd->priv;
+    struct lavc_param *lavc_param = &vd->opts->lavc_param;
     bool mp_rawvideo = false;
+    struct sh_stream *sh = vd->header;
 
     assert(!ctx->avctx);
 
@@ -379,7 +380,7 @@ static void init_avctx(sh_video_t *sh, const char *decoder,
     if (!lavc_codec)
         return;
 
-    ctx->hwdec_info = sh->hwdec_info;
+    ctx->hwdec_info = vd->hwdec_info;
 
     ctx->do_dr1 = ctx->do_hw_dr1 = 0;
     ctx->pix_fmt = PIX_FMT_NONE;
@@ -388,7 +389,7 @@ static void init_avctx(sh_video_t *sh, const char *decoder,
     ctx->hwdec = hwdec;
     ctx->avctx = avcodec_alloc_context3(lavc_codec);
     AVCodecContext *avctx = ctx->avctx;
-    avctx->opaque = sh;
+    avctx->opaque = vd;
     avctx->codec_type = AVMEDIA_TYPE_VIDEO;
     avctx->codec_id = lavc_codec->id;
 
@@ -409,7 +410,7 @@ static void init_avctx(sh_video_t *sh, const char *decoder,
             avctx->get_format  = get_format_hwdec;
         setup_refcounting_hw(avctx);
         if (ctx->hwdec->init && ctx->hwdec->init(ctx) < 0) {
-            uninit_avctx(sh);
+            uninit_avctx(vd);
             return;
         }
     } else {
@@ -445,7 +446,7 @@ static void init_avctx(sh_video_t *sh, const char *decoder,
             mp_msg(MSGT_DECVIDEO, MSGL_ERR,
                    "Your options /%s/ look like gibberish to me pal\n",
                    lavc_param->avopt);
-            uninit_avctx(sh);
+            uninit_avctx(vd);
             return;
         }
     }
@@ -453,33 +454,33 @@ static void init_avctx(sh_video_t *sh, const char *decoder,
     // Do this after the above avopt handling in case it changes values
     ctx->skip_frame = avctx->skip_frame;
 
-    avctx->codec_tag = sh->format;
-    avctx->coded_width  = sh->disp_w;
-    avctx->coded_height = sh->disp_h;
+    avctx->codec_tag = sh->video->format;
+    avctx->coded_width  = sh->video->disp_w;
+    avctx->coded_height = sh->video->disp_h;
 
     // demux_mkv
-    if (sh->bih)
-        set_from_bih(avctx, sh->format, sh->bih);
+    if (sh->video->bih)
+        set_from_bih(avctx, sh->video->format, sh->video->bih);
 
-    if (mp_rawvideo && sh->format >= IMGFMT_START && sh->format < IMGFMT_END) {
-        avctx->pix_fmt = imgfmt2pixfmt(sh->format);
+    if (mp_rawvideo) {
+        avctx->pix_fmt = imgfmt2pixfmt(sh->video->format);
         avctx->codec_tag = 0;
     }
 
-    if (sh->gsh->lav_headers)
-        mp_copy_lav_codec_headers(avctx, sh->gsh->lav_headers);
+    if (sh->lav_headers)
+        mp_copy_lav_codec_headers(avctx, sh->lav_headers);
 
     /* open it */
     if (avcodec_open2(avctx, lavc_codec, NULL) < 0) {
         mp_tmsg(MSGT_DECVIDEO, MSGL_ERR, "Could not open codec.\n");
-        uninit_avctx(sh);
+        uninit_avctx(vd);
         return;
     }
 }
 
-static void uninit_avctx(sh_video_t *sh)
+static void uninit_avctx(struct dec_video *vd)
 {
-    vd_ffmpeg_ctx *ctx = sh->context;
+    vd_ffmpeg_ctx *ctx = vd->priv;
     AVCodecContext *avctx = ctx->avctx;
 
     if (avctx) {
@@ -504,21 +505,19 @@ static void uninit_avctx(sh_video_t *sh)
     ctx->last_sample_aspect_ratio = (AVRational){0, 0};
 }
 
-static void uninit(sh_video_t *sh)
+static void uninit(struct dec_video *vd)
 {
-    vd_ffmpeg_ctx *ctx = sh->context;
-
-    uninit_avctx(sh);
-    talloc_free(ctx);
+    uninit_avctx(vd);
 }
 
-static void update_image_params(sh_video_t *sh, AVFrame *frame)
+static void update_image_params(struct dec_video *vd, AVFrame *frame)
 {
-    vd_ffmpeg_ctx *ctx = sh->context;
+    vd_ffmpeg_ctx *ctx = vd->priv;
     int width = frame->width;
     int height = frame->height;
     float aspect = av_q2d(frame->sample_aspect_ratio) * width / height;
     int pix_fmt = frame->format;
+    struct sh_video *sh = vd->header->video;
 
 #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54, 40, 0)
     pix_fmt = ctx->avctx->pix_fmt;
@@ -566,8 +565,8 @@ static void update_image_params(sh_video_t *sh, AVFrame *frame)
 static enum PixelFormat get_format_hwdec(struct AVCodecContext *avctx,
                                          const enum PixelFormat *fmt)
 {
-    sh_video_t *sh = avctx->opaque;
-    vd_ffmpeg_ctx *ctx = sh->context;
+    struct dec_video *vd = avctx->opaque;
+    vd_ffmpeg_ctx *ctx = vd->priv;
 
     mp_msg(MSGT_DECVIDEO, MSGL_V, "Pixel formats supported by decoder:");
     for (int i = 0; fmt[i] != PIX_FMT_NONE; i++)
@@ -587,9 +586,9 @@ static enum PixelFormat get_format_hwdec(struct AVCodecContext *avctx,
     return PIX_FMT_NONE;
 }
 
-static struct mp_image *get_surface_hwdec(struct sh_video *sh, AVFrame *pic)
+static struct mp_image *get_surface_hwdec(struct dec_video *vd, AVFrame *pic)
 {
-    vd_ffmpeg_ctx *ctx = sh->context;
+    vd_ffmpeg_ctx *ctx = vd->priv;
 
     /* Decoders using ffmpeg's hwaccel architecture (everything except vdpau)
      * can fall back to software decoding automatically. However, we don't
@@ -633,9 +632,9 @@ static void free_mpi(void *opaque, uint8_t *data)
 
 static int get_buffer2_hwdec(AVCodecContext *avctx, AVFrame *pic, int flags)
 {
-    sh_video_t *sh = avctx->opaque;
+    struct dec_video *vd = avctx->opaque;
 
-    struct mp_image *mpi = get_surface_hwdec(sh, pic);
+    struct mp_image *mpi = get_surface_hwdec(vd, pic);
     if (!mpi)
         return -1;
 
@@ -653,9 +652,9 @@ static void setup_refcounting_hw(AVCodecContext *avctx)
 
 static int get_buffer_hwdec(AVCodecContext *avctx, AVFrame *pic)
 {
-    sh_video_t *sh = avctx->opaque;
+    struct dec_video *vd = avctx->opaque;
 
-    struct mp_image *mpi = get_surface_hwdec(sh, pic);
+    struct mp_image *mpi = get_surface_hwdec(vd, pic);
     if (!mpi)
         return -1;
 
@@ -694,9 +693,9 @@ static void setup_refcounting_hw(AVCodecContext *avctx)
 
 #if HAVE_AVUTIL_REFCOUNTING
 
-static struct mp_image *image_from_decoder(struct sh_video *sh)
+static struct mp_image *image_from_decoder(struct dec_video *vd)
 {
-    vd_ffmpeg_ctx *ctx = sh->context;
+    vd_ffmpeg_ctx *ctx = vd->priv;
     AVFrame *pic = ctx->pic;
 
     struct mp_image *img = mp_image_from_av_frame(pic);
@@ -722,9 +721,9 @@ static bool fb_is_unique(void *b)
     return mp_buffer_is_unique(b);
 }
 
-static struct mp_image *image_from_decoder(struct sh_video *sh)
+static struct mp_image *image_from_decoder(struct dec_video *vd)
 {
-    vd_ffmpeg_ctx *ctx = sh->context;
+    vd_ffmpeg_ctx *ctx = vd->priv;
     AVFrame *pic = ctx->pic;
 
     struct mp_image new = {0};
@@ -749,12 +748,12 @@ static struct mp_image *image_from_decoder(struct sh_video *sh)
 
 #endif /* HAVE_AVUTIL_REFCOUNTING */
 
-static int decode(struct sh_video *sh, struct demux_packet *packet,
+static int decode(struct dec_video *vd, struct demux_packet *packet,
                   int flags, double *reordered_pts, struct mp_image **out_image)
 {
     int got_picture = 0;
     int ret;
-    vd_ffmpeg_ctx *ctx = sh->context;
+    vd_ffmpeg_ctx *ctx = vd->priv;
     AVFrame *pic = ctx->pic;
     AVCodecContext *avctx = ctx->avctx;
     AVPacket pkt;
@@ -781,9 +780,9 @@ static int decode(struct sh_video *sh, struct demux_packet *packet,
     if (!got_picture)
         return 0;                     // skipped image
 
-    update_image_params(sh, pic);
+    update_image_params(vd, pic);
 
-    struct mp_image *mpi = image_from_decoder(sh);
+    struct mp_image *mpi = image_from_decoder(vd);
     assert(mpi->planes[0]);
     mp_image_set_params(mpi, &ctx->image_params);
 
@@ -794,7 +793,7 @@ static int decode(struct sh_video *sh, struct demux_packet *packet,
     mp_image_params_from_image(&vo_params, mpi);
 
     if (!mp_image_params_equals(&vo_params, &ctx->vo_image_params)) {
-        if (mpcodecs_reconfig_vo(sh, &vo_params) < 0) {
+        if (mpcodecs_reconfig_vo(vd, &vo_params) < 0) {
             talloc_free(mpi);
             return -1;
         }
@@ -805,30 +804,30 @@ static int decode(struct sh_video *sh, struct demux_packet *packet,
     return 1;
 }
 
-static struct mp_image *decode_with_fallback(struct sh_video *sh,
+static struct mp_image *decode_with_fallback(struct dec_video *vd,
                                 struct demux_packet *packet,
                                 int flags, double *reordered_pts)
 {
-    vd_ffmpeg_ctx *ctx = sh->context;
+    vd_ffmpeg_ctx *ctx = vd->priv;
     if (!ctx->avctx)
         return NULL;
 
     struct mp_image *mpi = NULL;
-    int res = decode(sh, packet, flags, reordered_pts, &mpi);
+    int res = decode(vd, packet, flags, reordered_pts, &mpi);
     if (res >= 0)
         return mpi;
 
     // Failed hardware decoding? Try again in software.
     if (ctx->software_fallback_decoder) {
-        uninit_avctx(sh);
+        uninit_avctx(vd);
         mp_tmsg(MSGT_DECVIDEO, MSGL_ERR, "Error using hardware "
                 "decoding, falling back to software decoding.\n");
         const char *decoder = ctx->software_fallback_decoder;
         ctx->software_fallback_decoder = NULL;
-        init_avctx(sh, decoder, NULL);
+        init_avctx(vd, decoder, NULL);
         if (ctx->avctx) {
             mpi = NULL;
-            decode(sh, packet, flags, reordered_pts, &mpi);
+            decode(vd, packet, flags, reordered_pts, &mpi);
             return mpi;
         }
     }
@@ -836,9 +835,9 @@ static struct mp_image *decode_with_fallback(struct sh_video *sh,
     return NULL;
 }
 
-static int control(sh_video_t *sh, int cmd, void *arg)
+static int control(struct dec_video *vd, int cmd, void *arg)
 {
-    vd_ffmpeg_ctx *ctx = sh->context;
+    vd_ffmpeg_ctx *ctx = vd->priv;
     AVCodecContext *avctx = ctx->avctx;
     switch (cmd) {
     case VDCTRL_RESYNC_STREAM:
@@ -853,7 +852,7 @@ static int control(sh_video_t *sh, int cmd, void *arg)
         return CONTROL_TRUE;
     case VDCTRL_REINIT_VO:
         if (ctx->vo_image_params.imgfmt)
-            mpcodecs_reconfig_vo(sh, &ctx->vo_image_params);
+            mpcodecs_reconfig_vo(vd, &ctx->vo_image_params);
         return true;
     case VDCTRL_GET_PARAMS:
         *(struct mp_image_params *)arg = ctx->vo_image_params;
diff --git a/video/decode/vdpau_old.c b/video/decode/vdpau_old.c
index 1ef6c7857a..caca5df1a5 100644
--- a/video/decode/vdpau_old.c
+++ b/video/decode/vdpau_old.c
@@ -130,8 +130,8 @@ static void draw_slice_hwdec(struct AVCodecContext *s,
                              const AVFrame *src, int offset[4],
                              int y, int type, int height)
 {
-    sh_video_t *sh = s->opaque;
-    struct lavc_ctx *ctx = sh->context;
+    struct dec_video *vd = s->opaque;
+    struct lavc_ctx *ctx = vd->priv;
     struct priv *p = ctx->hwdec_priv;
     struct vdp_functions *vdp = p->vdp;
     VdpStatus vdp_st;