From 3ee43c0365862ff493792cd14dc277a9fed6d24f Mon Sep 17 00:00:00 2001
From: Rohit Yadav <rohityadav89@gmail.com>
Date: Sat, 10 Mar 2012 01:12:16 +0530
Subject: [PATCH] es_out: play previous frames

The most acceptable solution will be to have the decode save a range of decoded frames around given current time,
So, when a seek occurs frames after and before that point are stored in a link-list and depending upon what kind of
behaviour is needed, in this case next frame or previous frame, that particular node can simply be used to extract
already decoded frame it has. Note: for playing frame-by-frame, prev or next, we don't need audio frames, just video frames.

The current patch is just an attempt that may be used by VLMC and libvlc based apps to play frame by frame both forward
and backward in time.

Signed-off-by: Rohit Yadav <rohityadav89@gmail.com>
---
 include/vlc/libvlc_media_player.h   |    6 +++++
 include/vlc_keys.h                  |    1 +
 lib/media_player.c                  |   10 +++++++++
 modules/control/hotkeys.c           |    6 +++++
 modules/gui/qt4/actions_manager.cpp |    2 +-
 src/config/keys.c                   |    1 +
 src/input/decoder.c                 |   24 ++++++++++++++++++++++
 src/input/decoder.h                 |    6 +++++
 src/input/es_out.c                  |   37 +++++++++++++++++++++++++++++++++++
 src/input/es_out.h                  |    7 ++++++
 src/input/es_out_timeshift.c        |   10 +++++++++
 src/input/input.c                   |   16 +++++++++++++++
 src/input/input_internal.h          |    1 +
 src/input/var.c                     |   18 +++++++++++++++++
 src/libvlc-module.c                 |    9 +++++++-
 src/video_output/video_output.c     |   13 ++++++++++++
 src/video_output/vout_control.h     |    5 ++++
 17 files changed, 170 insertions(+), 2 deletions(-)

diff --git a/include/vlc/libvlc_media_player.h b/include/vlc/libvlc_media_player.h
index cc57521..a806d75 100644
--- a/include/vlc/libvlc_media_player.h
+++ b/include/vlc/libvlc_media_player.h
@@ -793,6 +793,12 @@ LIBVLC_API int libvlc_media_player_is_seekable( libvlc_media_player_t *p_mi );
  */
 LIBVLC_API int libvlc_media_player_can_pause( libvlc_media_player_t *p_mi );
 
+/**
+ * Display the previous frame (if supported)
+ *
+ * \param p_mi the media player
+ */
+LIBVLC_API void libvlc_media_player_previous_frame( libvlc_media_player_t *p_mi );
 
 /**
  * Display the next frame (if supported)
diff --git a/include/vlc_keys.h b/include/vlc_keys.h
index f08243b..2e703ef 100644
--- a/include/vlc_keys.h
+++ b/include/vlc_keys.h
@@ -116,6 +116,7 @@ typedef enum vlc_action {
     ACTIONID_JUMP_FORWARD_MEDIUM,
     ACTIONID_JUMP_BACKWARD_LONG,
     ACTIONID_JUMP_FORWARD_LONG,
+    ACTIONID_FRAME_PREVIOUS,
     ACTIONID_FRAME_NEXT,
     ACTIONID_POSITION,
     ACTIONID_VOL_MUTE,
diff --git a/lib/media_player.c b/lib/media_player.c
index b426637..1906ff8 100644
--- a/lib/media_player.c
+++ b/lib/media_player.c
@@ -1399,6 +1399,16 @@ int libvlc_media_player_can_pause( libvlc_media_player_t *p_mi )
     return b_can_pause;
 }
 
+void libvlc_media_player_previous_frame( libvlc_media_player_t *p_mi )
+{
+    input_thread_t *p_input_thread = libvlc_get_input_thread ( p_mi );
+    if( p_input_thread != NULL )
+    {
+        var_TriggerCallback( p_input_thread, "frame-previous" );
+        vlc_object_release( p_input_thread );
+    }
+}
+
 void libvlc_media_player_next_frame( libvlc_media_player_t *p_mi )
 {
     input_thread_t *p_input_thread = libvlc_get_input_thread ( p_mi );
diff --git a/modules/control/hotkeys.c b/modules/control/hotkeys.c
index d606f27..ee04e59 100644
--- a/modules/control/hotkeys.c
+++ b/modules/control/hotkeys.c
@@ -697,6 +697,12 @@ static int PutAction( intf_thread_t *p_intf, int i_action )
             {
                 playlist_Stop( p_playlist );
             }
+            else if( i_action == ACTIONID_FRAME_PREVIOUS )
+            {
+                var_TriggerCallback( p_input, "frame-previous" );
+                DisplayMessage( p_vout, SPU_DEFAULT_CHANNEL,
+                                "%s", _("Previous frame") );
+            }
             else if( i_action == ACTIONID_FRAME_NEXT )
             {
                 var_TriggerCallback( p_input, "frame-next" );
diff --git a/modules/gui/qt4/actions_manager.cpp b/modules/gui/qt4/actions_manager.cpp
index 7cca077..abc09e4 100644
--- a/modules/gui/qt4/actions_manager.cpp
+++ b/modules/gui/qt4/actions_manager.cpp
@@ -174,7 +174,7 @@ void ActionsManager::frame()
 {
     input_thread_t *p_input = THEMIM->getInput();
     if( p_input )
-        var_TriggerCallback( p_input, "frame-next" );
+        var_TriggerCallback( p_input, "frame-previous" );
 }
 
 void ActionsManager::toggleMuteAudio()
diff --git a/src/config/keys.c b/src/config/keys.c
index b833e02..1fad66f 100644
--- a/src/config/keys.c
+++ b/src/config/keys.c
@@ -252,6 +252,7 @@ static const struct action actions[] =
     { "deinterlace", ACTIONID_DEINTERLACE, },
     { "disc-menu", ACTIONID_DISC_MENU, },
     { "faster", ACTIONID_FASTER, },
+    { "frame-previous", ACTIONID_FRAME_PREVIOUS, },
     { "frame-next", ACTIONID_FRAME_NEXT, },
     { "incr-scalefactor", ACTIONID_SCALE_UP, },
     { "intf-boss", ACTIONID_INTF_BOSS, },
diff --git a/src/input/decoder.c b/src/input/decoder.c
index 2e81f78..e6eb2e2 100644
--- a/src/input/decoder.c
+++ b/src/input/decoder.c
@@ -601,6 +601,30 @@ void input_DecoderWaitBuffering( decoder_t *p_dec )
     vlc_mutex_unlock( &p_owner->lock );
 }
 
+void input_DecoderFramePrevious( decoder_t *p_dec, mtime_t *pi_duration )
+{
+    decoder_owner_sys_t *p_owner = p_dec->p_owner;
+
+    *pi_duration = 0;
+
+    vlc_mutex_lock( &p_owner->lock );
+    if( p_dec->fmt_out.i_cat == VIDEO_ES )
+    {
+        if( p_owner->b_paused && p_owner->p_vout )
+        {
+            vout_PreviousPicture( p_owner->p_vout, pi_duration );
+            p_owner->pause.i_ignore++;
+            vlc_cond_signal( &p_owner->wait_request );
+        }
+    }
+    else
+    {
+        /* TODO subtitle should not be flushed */
+        DecoderFlush( p_dec );
+    }
+    vlc_mutex_unlock( &p_owner->lock );
+}
+
 void input_DecoderFrameNext( decoder_t *p_dec, mtime_t *pi_duration )
 {
     decoder_owner_sys_t *p_owner = p_dec->p_owner;
diff --git a/src/input/decoder.h b/src/input/decoder.h
index 1c9e1b6..9c90adf 100644
--- a/src/input/decoder.h
+++ b/src/input/decoder.h
@@ -84,6 +84,12 @@ int input_DecoderGetCcState( decoder_t *, bool *pb_decode, int i_channel );
 void input_DecoderIsCcPresent( decoder_t *, bool pb_present[4] );
 
 /**
+ * This function force the display of the previous picture and fills the stream
+ * time consumed.
+ */
+void input_DecoderFramePrevious( decoder_t *p_dec, mtime_t *pi_duration );
+
+/**
  * This function force the display of the next picture and fills the stream
  * time consumed.
  */
diff --git a/src/input/es_out.c b/src/input/es_out.c
index 9ae8b36..ec821f3 100644
--- a/src/input/es_out.c
+++ b/src/input/es_out.c
@@ -784,6 +784,39 @@ static void EsOutProgramsChangeRate( es_out_t *out )
         input_clock_ChangeRate( p_sys->pgrm[i]->p_clock, p_sys->i_rate );
 }
 
+static void EsOutFramePrevious( es_out_t *out )
+{
+    es_out_sys_t *p_sys = out->p_sys;
+    es_out_id_t *p_es_video = NULL;
+
+    if( p_sys->b_buffering )
+    {
+        msg_Warn( p_sys->p_input, "buffering, ignoring 'frame previous'" );
+        return;
+    }
+
+    assert( p_sys->b_paused );
+
+    for( int i = 0; i < p_sys->i_es; i++ )
+    {
+        es_out_id_t *p_es = p_sys->es[i];
+
+        if( p_es->fmt.i_cat == VIDEO_ES && p_es->p_dec )
+        {
+            p_es_video = p_es;
+            break;
+        }
+    }
+
+    if( !p_es_video )
+    {
+        msg_Warn( p_sys->p_input, "No video track selected, ignoring 'frame previous'" );
+        return;
+    }
+
+    var_SetTime( p_sys->p_input, "time", var_GetTime( p_sys->p_input, "time" ) - CLOCK_FREQ*4.0f/( p_sys->p_input->p->f_fps) );
+}
+
 static void EsOutFrameNext( es_out_t *out )
 {
     es_out_sys_t *p_sys = out->p_sys;
@@ -2599,6 +2632,10 @@ static int EsOutControlLocked( es_out_t *out, int i_query, va_list args )
             return VLC_SUCCESS;
         }
 
+        case ES_OUT_SET_FRAME_PREVIOUS:
+            EsOutFramePrevious( out );
+            return VLC_SUCCESS;
+
         case ES_OUT_SET_FRAME_NEXT:
             EsOutFrameNext( out );
             return VLC_SUCCESS;
diff --git a/src/input/es_out.h b/src/input/es_out.h
index 56cefed..4efe915 100644
--- a/src/input/es_out.h
+++ b/src/input/es_out.h
@@ -69,6 +69,9 @@ enum es_out_query_private_e
     ES_OUT_SET_TIME,                                /* arg1=mtime_t             res=can fail */
 
     /* Set next frame */
+    ES_OUT_SET_FRAME_PREVIOUS,                      /*                          res=can fail */
+
+    /* Set next frame */
     ES_OUT_SET_FRAME_NEXT,                          /*                          res=can fail */
 
     /* Set position/time/length */
@@ -134,6 +137,10 @@ static inline int es_out_SetTime( es_out_t *p_out, mtime_t i_date )
 {
     return es_out_Control( p_out, ES_OUT_SET_TIME, i_date );
 }
+static inline int es_out_SetFramePrevious( es_out_t *p_out )
+{
+    return es_out_Control( p_out, ES_OUT_SET_FRAME_PREVIOUS );
+}
 static inline int es_out_SetFrameNext( es_out_t *p_out )
 {
     return es_out_Control( p_out, ES_OUT_SET_FRAME_NEXT );
diff --git a/src/input/es_out_timeshift.c b/src/input/es_out_timeshift.c
index ca4d8d0..cedb795 100644
--- a/src/input/es_out_timeshift.c
+++ b/src/input/es_out_timeshift.c
@@ -567,6 +567,12 @@ static int ControlLockedSetTime( es_out_t *p_out, mtime_t i_date )
     msg_Err( p_sys->p_input, "EsOutTimeshift does not yet support time change" );
     return VLC_EGENERIC;
 }
+static int ControlLockedSetFramePrevious( es_out_t *p_out )
+{
+    es_out_sys_t *p_sys = p_out->p_sys;
+
+    return es_out_SetFramePrevious( p_sys->p_out );
+}
 static int ControlLockedSetFrameNext( es_out_t *p_out )
 {
     es_out_sys_t *p_sys = p_out->p_sys;
@@ -672,6 +678,10 @@ static int ControlLocked( es_out_t *p_out, int i_query, va_list args )
 
         return ControlLockedSetTime( p_out, i_date );
     }
+    case ES_OUT_SET_FRAME_PREVIOUS:
+    {
+        return ControlLockedSetFramePrevious( p_out );
+    }
     case ES_OUT_SET_FRAME_NEXT:
     {
         return ControlLockedSetFrameNext( p_out );
diff --git a/src/input/input.c b/src/input/input.c
index 792fd05..cc652f2 100644
--- a/src/input/input.c
+++ b/src/input/input.c
@@ -2136,6 +2136,22 @@ static bool Control( input_thread_t *p_input,
             }
             break;
 
+        case INPUT_CONTROL_SET_FRAME_PREVIOUS:
+            if( p_input->p->i_state == PAUSE_S )
+            {
+                es_out_SetFramePrevious( p_input->p->p_es_out );
+            }
+            else if( p_input->p->i_state == PLAYING_S )
+            {
+                ControlPause( p_input, i_control_date );
+            }
+            else
+            {
+                msg_Err( p_input, "invalid state for frame previous" );
+            }
+            b_force_update = true;
+            break;
+
         case INPUT_CONTROL_SET_FRAME_NEXT:
             if( p_input->p->i_state == PAUSE_S )
             {
diff --git a/src/input/input_internal.h b/src/input/input_internal.h
index e65acdf..072c19f 100644
--- a/src/input/input_internal.h
+++ b/src/input/input_internal.h
@@ -207,6 +207,7 @@ enum input_control_e
 
     INPUT_CONTROL_SET_RECORD_STATE,
 
+    INPUT_CONTROL_SET_FRAME_PREVIOUS,
     INPUT_CONTROL_SET_FRAME_NEXT,
 };
 
diff --git a/src/input/var.c b/src/input/var.c
index 9613fe2..7766699 100644
--- a/src/input/var.c
+++ b/src/input/var.c
@@ -66,6 +66,9 @@ static int BookmarkCallback( vlc_object_t *p_this, char const *psz_cmd,
 static int RecordCallback( vlc_object_t *p_this, char const *psz_cmd,
                            vlc_value_t oldval, vlc_value_t newval,
                            void *p_data );
+static int FramePreviousCallback( vlc_object_t *p_this, char const *psz_cmd,
+                              vlc_value_t oldval, vlc_value_t newval,
+                              void *p_data );
 static int FrameNextCallback( vlc_object_t *p_this, char const *psz_cmd,
                               vlc_value_t oldval, vlc_value_t newval,
                               void *p_data );
@@ -102,6 +105,7 @@ static const vlc_input_callback_t p_input_callbacks[] =
     CALLBACK( "audio-es", ESCallback ),
     CALLBACK( "spu-es", ESCallback ),
     CALLBACK( "record", RecordCallback ),
+    CALLBACK( "frame-previous", FramePreviousCallback ),
     CALLBACK( "frame-next", FrameNextCallback ),
 
     CALLBACK( NULL, NULL )
@@ -138,6 +142,7 @@ void input_ControlVarInit ( input_thread_t *p_input )
     /* Rate */
     var_Create( p_input, "rate", VLC_VAR_FLOAT | VLC_VAR_DOINHERIT );
 
+    var_Create( p_input, "frame-previous", VLC_VAR_VOID );
     var_Create( p_input, "frame-next", VLC_VAR_VOID );
 
     /* Position */
@@ -814,6 +819,19 @@ static int RecordCallback( vlc_object_t *p_this, char const *psz_cmd,
     return VLC_SUCCESS;
 }
 
+static int FramePreviousCallback( vlc_object_t *p_this, char const *psz_cmd,
+                              vlc_value_t oldval, vlc_value_t newval,
+                              void *p_data )
+{
+    input_thread_t *p_input = (input_thread_t*)p_this;
+    VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval); VLC_UNUSED(p_data);
+    VLC_UNUSED(newval);
+
+    input_ControlPush( p_input, INPUT_CONTROL_SET_FRAME_PREVIOUS, NULL );
+
+    return VLC_SUCCESS;
+}
+
 static int FrameNextCallback( vlc_object_t *p_this, char const *psz_cmd,
                               vlc_value_t oldval, vlc_value_t newval,
                               void *p_data )
diff --git a/src/libvlc-module.c b/src/libvlc-module.c
index dbf84f2..06c9825 100644
--- a/src/libvlc-module.c
+++ b/src/libvlc-module.c
@@ -1393,9 +1393,12 @@ static const char *const ppsz_albumart_descriptions[] =
 #define JFLONG_KEY_TEXT N_("Long forward jump")
 #define JFLONG_KEY_LONGTEXT \
     N_("Select the hotkey to make a long forward jump.")
+#define FRAME_PREVIOUS_KEY_TEXT N_("Previous frame")
+#define FRAME_PREVIOUS_KEY_LONGTEXT \
+    N_("Select the hotkey to go to the previous video frame.")
 #define FRAME_NEXT_KEY_TEXT N_("Next frame")
 #define FRAME_NEXT_KEY_LONGTEXT \
-    N_("Select the hotkey to got to the next video frame.")
+    N_("Select the hotkey to go to the next video frame.")
 
 #define JIEXTRASHORT_TEXT N_("Very short jump length")
 #define JIEXTRASHORT_LONGTEXT N_("Very short jump length, in seconds.")
@@ -2309,6 +2312,7 @@ vlc_module_begin ()
 #   define KEY_JUMP_PMEDIUM       "Command+Shift+Right"
 #   define KEY_JUMP_MLONG         "Command+Shift+Alt+Left"
 #   define KEY_JUMP_PLONG         "Command+Shift+Alt+Right"
+#   define KEY_FRAME_PREVIOUS     "Ctrl+e"
 #   define KEY_FRAME_NEXT         "e"
 #   define KEY_NAV_ACTIVATE       "Enter"
 #   define KEY_NAV_UP             "Up"
@@ -2423,6 +2427,7 @@ vlc_module_begin ()
 #   define KEY_JUMP_PMEDIUM       "Ctrl+Right"
 #   define KEY_JUMP_MLONG         "Ctrl+Alt+Left"
 #   define KEY_JUMP_PLONG         "Ctrl+Alt+Right"
+#   define KEY_FRAME_PREVIOUS     "Ctrl+e"
 #   define KEY_FRAME_NEXT         "e"
 #   define KEY_NAV_ACTIVATE       "Enter"
 #   define KEY_NAV_UP             "Up"
@@ -2559,6 +2564,8 @@ vlc_module_begin ()
              JBLONG_KEY_LONGTEXT, false )
     add_key( "key-jump+long", KEY_JUMP_PLONG, JFLONG_KEY_TEXT,
              JFLONG_KEY_LONGTEXT, false )
+    add_key( "key-frame-previous", KEY_FRAME_PREVIOUS, FRAME_PREVIOUS_KEY_TEXT,
+             FRAME_PREVIOUS_KEY_LONGTEXT, false )
     add_key( "key-frame-next", KEY_FRAME_NEXT, FRAME_NEXT_KEY_TEXT,
              FRAME_NEXT_KEY_LONGTEXT, false )
     add_key( "key-nav-activate", KEY_NAV_ACTIVATE, NAV_ACTIVATE_KEY_TEXT,
diff --git a/src/video_output/video_output.c b/src/video_output/video_output.c
index a8e4e24..5ca1684 100644
--- a/src/video_output/video_output.c
+++ b/src/video_output/video_output.c
@@ -342,6 +342,19 @@ void vout_FixLeaks( vout_thread_t *vout )
 
     vlc_mutex_unlock(&vout->p->picture_lock);
 }
+
+void vout_PreviousPicture(vout_thread_t *vout, mtime_t *duration)
+{
+    vout_control_cmd_t cmd;
+    vout_control_cmd_Init(&cmd, VOUT_CONTROL_STEP);
+    cmd.u.time_ptr = duration;
+
+    vout->p->step.timestamp = vout->p->displayed.timestamp;
+
+    vout_control_Push(&vout->p->control, &cmd);
+    vout_control_WaitEmpty(&vout->p->control);
+}
+
 void vout_NextPicture(vout_thread_t *vout, mtime_t *duration)
 {
     vout_control_cmd_t cmd;
diff --git a/src/video_output/vout_control.h b/src/video_output/vout_control.h
index 12b057d..2c744e8 100644
--- a/src/video_output/vout_control.h
+++ b/src/video_output/vout_control.h
@@ -61,6 +61,11 @@ void vout_FixLeaks( vout_thread_t *p_vout );
 void vout_Reset( vout_thread_t *p_vout );
 
 /**
+ * This function will force to display the previous picture while paused
+ */
+void vout_PreviousPicture( vout_thread_t *p_vout, mtime_t *pi_duration );
+
+/**
  * This function will force to display the next picture while paused
  */
 void vout_NextPicture( vout_thread_t *p_vout, mtime_t *pi_duration );
-- 
1.7.5.4

