Added record support at the stream_t level in core.
authorLaurent Aimar <fenrir@videolan.org>
Tue, 26 Aug 2008 10:54:18 +0000 (12:54 +0200)
committerLaurent Aimar <fenrir@videolan.org>
Tue, 26 Aug 2008 21:27:56 +0000 (23:27 +0200)
The record access_filter will become useless as soon as all demuxers that can
support it will be modified.
The record support is half done. I will also add es_out_t record functionnalities,
and a way to selected one of them if both are supported.

14 files changed:
include/vlc_demux.h
include/vlc_input.h
include/vlc_stream.h
modules/control/hotkeys.c
modules/demux/ts.c
modules/gui/qt4/components/interface_widgets.cpp
src/input/control.c
src/input/decoder.c
src/input/demux.c
src/input/input.c
src/input/input_internal.h
src/input/stream.c
src/input/var.c
src/libvlc-module.c

index 5b5c582..2d8e1c0 100644 (file)
@@ -124,8 +124,14 @@ enum demux_query_e
     /* Attachments */
     DEMUX_GET_ATTACHMENTS,      /* arg1=input_attachment_t***, int* res=can fail */
 
+    /* RECORD you should accept it only if the stream can be recorded without
+     * any modification or header addition. */
+    DEMUX_CAN_RECORD,           /* arg1=bool*   res=can fail(assume false) */
+    DEMUX_SET_RECORD_STATE,     /* arg1=bool    res=can fail */
+
+
     /* II. Specific access_demux queries */
-    DEMUX_CAN_PAUSE,            /* arg1= bool*    can fail (assume false)*/
+    DEMUX_CAN_PAUSE = 0x1000,   /* arg1= bool*    can fail (assume false)*/
     DEMUX_SET_PAUSE_STATE,      /* arg1= bool     can fail */
 
     DEMUX_GET_PTS_DELAY,        /* arg1= int64_t*       cannot fail */
index f97d196..70f56c6 100644 (file)
@@ -524,7 +524,11 @@ enum input_query_e
     INPUT_GET_ATTACHMENT,  /* arg1=input_attachment_t**, arg2=char*  res=can fail */
 
     /* On the fly input slave */
-    INPUT_ADD_SLAVE        /* arg1= char * */
+    INPUT_ADD_SLAVE,       /* arg1= char * */
+
+    /* On the fly record while playing */
+    INPUT_SET_RECORD_STATE, /* arg1=bool    res=can fail */
+    INPUT_GET_RECORD_STATE, /* arg1=bool*   res=can fail */
 };
 
 VLC_EXPORT( int, input_vaControl,( input_thread_t *, int i_query, va_list  ) );
index eba91cf..92aee35 100644 (file)
@@ -64,7 +64,11 @@ enum stream_query_e
     STREAM_CONTROL_ACCESS,  /* arg1= int i_access_query, args   res: can fail
                              if access unreachable or access control answer */
 
-    STREAM_GET_CONTENT_TYPE,   /**< arg1= char **         res=can file */
+    STREAM_GET_CONTENT_TYPE,    /**< arg1= char **         res=can fail */
+
+    /* SET_RECORD:
+     * XXX only data read through stream_Read/Block will be recorded */
+    STREAM_SET_RECORD_STATE,     /**< arg1=bool, arg2=const char *psz_ext (if arg1 is true)  res=can fail */
 };
 
 VLC_EXPORT( int, stream_Read, ( stream_t *s, void *p_read, int i_read ) );
index 22a0887..c5fbc19 100644 (file)
@@ -810,6 +810,19 @@ static void Run( intf_thread_t *p_intf )
             {
                 osd_MenuActivate( VLC_OBJECT(p_intf) );
             }
+            else if( i_action == ACTIONID_RECORD )
+            {
+                if( var_GetBool( p_input, "can-record" ) )
+                {
+                    const bool b_record = !var_GetBool( p_input, "record" );
+
+                    if( b_record )
+                        vout_OSDMessage( p_intf, DEFAULT_CHAN, _("Recording") );
+                    else
+                        vout_OSDMessage( p_intf, DEFAULT_CHAN, _("Recording done") );
+                    var_SetBool( p_input, "record", b_record );
+                }
+            }
         }
         if( p_vout )
             vlc_object_release( p_vout );
index a49ba41..6b62688 100644 (file)
@@ -370,6 +370,9 @@ struct demux_sys_t
 
     /* */
     bool        b_meta;
+
+    /* */
+    bool        b_start_record;
 };
 
 static int Demux    ( demux_t *p_demux );
@@ -642,6 +645,7 @@ static int Open( vlc_object_t *p_this )
     p_sys->b_udp_out = false;
     p_sys->i_ts_read = 50;
     p_sys->csa = NULL;
+    p_sys->b_start_record = false;
 
     /* Init PAT handler */
     pat = &p_sys->pid[0];
@@ -1091,6 +1095,13 @@ static int Demux( demux_t *p_demux )
             }
         }
 
+        if( p_sys->b_start_record )
+        {
+            /* Enable recording once synchronized */
+            stream_Control( p_demux->s, STREAM_SET_RECORD_STATE, true, "ts" );
+            p_sys->b_start_record = false;
+        }
+
         if( p_sys->b_udp_out )
         {
             memcpy( &p_sys->buffer[i_pkt * p_sys->i_packet_size],
@@ -1190,6 +1201,7 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
 {
     demux_sys_t *p_sys = p_demux->p_sys;
     double f, *pf;
+    bool b_bool, *pb_bool;
     int64_t i64;
     int64_t *pi64;
     int i_int;
@@ -1386,6 +1398,19 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
             return VLC_SUCCESS;
         }
 
+        case DEMUX_CAN_RECORD:
+            pb_bool = (bool*)va_arg( args, bool * );
+            *pb_bool = true;
+            return VLC_SUCCESS;
+
+        case DEMUX_SET_RECORD_STATE:
+            b_bool = (bool)va_arg( args, int );
+
+            if( !b_bool )
+                stream_Control( p_demux->s, STREAM_SET_RECORD_STATE, false );
+            p_sys->b_start_record = b_bool;
+            return VLC_SUCCESS;
+
         case DEMUX_GET_FPS:
         case DEMUX_SET_TIME:
         default:
index e10a0ba..305eed3 100644 (file)
@@ -372,13 +372,12 @@ void AdvControlsWidget::enableInput( bool enable )
         input_item_t *p_item = input_GetItem( THEMIM->getInput() );
         i_input_id = p_item->i_id;
 
-        if( var_Type( THEMIM->getInput(), "record-toggle" ) == VLC_VAR_VOID )
-            recordButton->setVisible( true );
-        else
-            recordButton->setVisible( false );
+        recordButton->setVisible( var_GetBool( THEMIM->getInput(), "can-record" ) );
     }
     else
+    {
         recordButton->setVisible( false );
+    }
 
     ABButton->setEnabled( enable );
     recordButton->setEnabled( enable );
@@ -464,8 +463,8 @@ void AdvControlsWidget::record()
     if( p_input )
     {
         /* This method won't work fine if the stream can't be cut anywhere */
-        if( var_Type( p_input, "record-toggle" ) == VLC_VAR_VOID )
-            var_TriggerCallback( p_input, "record-toggle" );
+        const bool b_recording = var_GetBool( p_input, "record" );
+        var_SetBool( p_input, "record", !b_recording );
 #if 0
         else
         {
index 123c9f9..1e286f8 100644 (file)
@@ -63,6 +63,7 @@ int input_vaControl( input_thread_t *p_input, int i_query, va_list args )
     int *pi_bkmk;
 
     int i_int, *pi_int;
+    bool b_bool, *pb_bool;
     double f, *pf;
     int64_t i_64, *pi_64;
 
@@ -598,6 +599,15 @@ int input_vaControl( input_thread_t *p_input, int i_query, va_list args )
             return VLC_EGENERIC;
         }
 
+        case INPUT_SET_RECORD_STATE:
+            b_bool = (bool)va_arg( args, int );
+            var_SetBool( p_input, "record", b_bool );
+            return VLC_SUCCESS;
+
+        case INPUT_GET_RECORD_STATE:
+            pb_bool = (bool*)va_arg( args, bool* );
+            *pb_bool = var_GetBool( p_input, "record" );
+            return VLC_SUCCESS;
 
         default:
             msg_Err( p_input, "unknown query in input_vaControl" );
index dace0f6..ff75d76 100644 (file)
@@ -734,33 +734,34 @@ static void VoutFlushPicture( vout_thread_t *p_vout )
     vlc_mutex_unlock( &p_vout->picture_lock );
 }
 
-
-static void optimize_video_pts( decoder_t *p_dec )
+static void DecoderOptimizePtsDelay( decoder_t *p_dec )
 {
-    picture_t * oldest_pict = NULL;
-    picture_t * youngest_pict = NULL;
-    int i;
+    input_thread_t *p_input = p_dec->p_owner->p_input;
+    vout_thread_t *p_vout = p_dec->p_owner->p_vout;
+    input_thread_private_t *p_priv = p_input->p;
 
-    input_thread_t * p_input = p_dec->p_owner->p_input;
-    vout_thread_t * p_vout = p_dec->p_owner->p_vout;
-    input_thread_private_t * p_priv = p_input->p;
+    picture_t *p_old = NULL;
+    picture_t *p_young = NULL;
+    int i;
 
     /* Enable with --auto-adjust-pts-delay */
-    if( !p_priv->pts_adjust.auto_adjust ) return;
+    if( !p_priv->pts_adjust.b_auto_adjust )
+        return;
 
     for( i = 0; i < I_RENDERPICTURES; i++ )
     {
-        picture_t * pic = PP_RENDERPICTURE[i];
-        if( pic->i_status != READY_PICTURE )
+        picture_t *p_pic = PP_RENDERPICTURE[i];
+
+        if( p_pic->i_status != READY_PICTURE )
             continue;
 
-        if( !oldest_pict || pic->date < oldest_pict->date )
-            oldest_pict = pic;
-        if( !youngest_pict || pic->date > youngest_pict->date )
-            youngest_pict = pic;
+        if( !p_old || p_pic->date < p_old->date )
+            p_old = p_pic;
+        if( !p_young || p_pic->date > p_young->date )
+            p_young = p_pic;
     }
 
-    if( !youngest_pict || !oldest_pict )
+    if( !p_young || !p_old )
         return;
 
     /* Try to find if we can reduce the pts
@@ -775,59 +776,58 @@ static void optimize_video_pts( decoder_t *p_dec )
      * pts<->dts delay in the muxed stream. That is
      * why we may end up in having a negative pts_delay,
      * to compensate that artificial delay. */
-    mtime_t buffer_size = youngest_pict->date - oldest_pict->date;
-    int64_t pts_slide = 0;
-    if( buffer_size < 10000 )
+    const mtime_t i_buffer_length = p_young->date - p_old->date;
+    int64_t i_pts_slide = 0;
+    if( i_buffer_length < 10000 )
     {
         if( p_priv->pts_adjust.i_num_faulty > 10 )
         {
-            pts_slide = __MAX(p_input->i_pts_delay *3 / 2, 10000);
+            i_pts_slide = __MAX(p_input->i_pts_delay *3 / 2, 10000);
             p_priv->pts_adjust.i_num_faulty = 0;
         }
-        if( p_priv->pts_adjust.to_high )
+        if( p_priv->pts_adjust.b_to_high )
         {
-            p_priv->pts_adjust.to_high = !p_priv->pts_adjust.to_high;
+            p_priv->pts_adjust.b_to_high = !p_priv->pts_adjust.b_to_high;
             p_priv->pts_adjust.i_num_faulty = 0;
         }
         p_priv->pts_adjust.i_num_faulty++;
     }
-    else if( buffer_size > 100000 )
+    else if( i_buffer_length > 100000 )
     {
         if( p_priv->pts_adjust.i_num_faulty > 25 )
         {
-            pts_slide = -buffer_size/2;
+            i_pts_slide = -i_buffer_length/2;
             p_priv->pts_adjust.i_num_faulty = 0;
         }
-        if( p_priv->pts_adjust.to_high )
+        if( p_priv->pts_adjust.b_to_high )
         {
-            p_priv->pts_adjust.to_high = !p_priv->pts_adjust.to_high;
+            p_priv->pts_adjust.b_to_high = !p_priv->pts_adjust.b_to_high;
             p_priv->pts_adjust.i_num_faulty = 0;
         }
         p_priv->pts_adjust.i_num_faulty++;
     }
-    if( pts_slide )
+    if( i_pts_slide != 0 )
     {
-        mtime_t origi_delay = p_input->i_pts_delay;
+        const mtime_t i_pts_delay_org = p_input->i_pts_delay;
 
-        p_input->i_pts_delay += pts_slide;
+        p_input->i_pts_delay += i_pts_slide;
 
         /* Don't play with the pts delay for more than -2<->3sec */
         if( p_input->i_pts_delay < -2000000 )
             p_input->i_pts_delay = -2000000;
         else if( p_input->i_pts_delay > 3000000 )
             p_input->i_pts_delay = 3000000;
-        pts_slide = p_input->i_pts_delay - origi_delay;
+        i_pts_slide = p_input->i_pts_delay - i_pts_delay_org;
 
         msg_Dbg( p_input, "Sliding the pts by %dms pts delay at %dms picture buffer was %dms",
-            (int)pts_slide/1000, (int)p_input->i_pts_delay/1000, (int)buffer_size/1000);
+            (int)i_pts_slide/1000, (int)p_input->i_pts_delay/1000, (int)i_buffer_length/1000);
 
         vlc_mutex_lock( &p_vout->picture_lock );
         /* Slide all the picture */
         for( i = 0; i < I_RENDERPICTURES; i++ )
-            PP_RENDERPICTURE[i]->date += pts_slide;
+            PP_RENDERPICTURE[i]->date += i_pts_slide;
         /* FIXME: slide aout/spu */
         vlc_mutex_unlock( &p_vout->picture_lock );
-
     }
 }
 
@@ -872,7 +872,7 @@ static void DecoderDecodeVideo( decoder_t *p_dec, block_t *p_block )
 
         vout_DatePicture( p_vout, p_pic, p_pic->date );
 
-        optimize_video_pts( p_dec );
+        DecoderOptimizePtsDelay( p_dec );
 
         vout_DisplayPicture( p_vout, p_pic );
     }
index b924e65..aa9c89f 100644 (file)
@@ -280,6 +280,8 @@ int demux_vaControlHelper( stream_t *s,
         case DEMUX_GET_TITLE_INFO:
         case DEMUX_SET_GROUP:
         case DEMUX_GET_ATTACHMENTS:
+        case DEMUX_CAN_RECORD:
+        case DEMUX_SET_RECORD_STATE:
             return VLC_EGENERIC;
 
         default:
@@ -526,6 +528,7 @@ static int DStreamControl( stream_t *s, int i_query, va_list args )
 
         case STREAM_CONTROL_ACCESS:
         case STREAM_GET_CONTENT_TYPE:
+        case STREAM_SET_RECORD_STATE:
             return VLC_EGENERIC;
 
         default:
index fdd907e..1fad35a 100644 (file)
@@ -112,6 +112,7 @@ static void AppendAttachment( int *pi_attachment, input_attachment_t ***ppp_atta
  *  - seekable (if you can seek, it doesn't say if 'bar display' has be shown
  *    or not, for that check position != 0.0)
  *  - can-pause
+ *  - can-record (if a stream can be recorded while playing)
  *  - teletext-es to get the index of spu track that is teletext --1 if no teletext)
  * * For intf callback upon changes
  *  - intf-change
@@ -184,6 +185,7 @@ static input_thread_t *Create( vlc_object_t *p_parent, input_item_t *p_item,
     p_input->p->i_title_offset = p_input->p->i_seekpoint_offset = 0;
     p_input->i_state = INIT_S;
     p_input->p->i_rate  = INPUT_RATE_DEFAULT;
+    p_input->p->b_recording = false;
     TAB_INIT( p_input->p->i_bookmark, p_input->p->bookmark );
     TAB_INIT( p_input->p->i_attachment, p_input->p->attachment );
     p_input->p->p_es_out = NULL;
@@ -235,6 +237,8 @@ static input_thread_t *Create( vlc_object_t *p_parent, input_item_t *p_item,
     /* Create Objects variables for public Get and Set */
     input_ControlVarInit( p_input );
 
+    /* */
+    p_input->p->pts_adjust.b_auto_adjust = var_GetBool( p_input, "auto-adjust-pts-delay" );
     p_input->p->input.i_cr_average = var_GetInteger( p_input, "cr-average" );
 
     if( !p_input->b_preparsing )
@@ -1452,6 +1456,12 @@ static bool Control( input_thread_t *p_input, int i_type,
         case INPUT_CONTROL_SET_POSITION_OFFSET:
         {
             double f_pos;
+
+            if( p_input->p->b_recording )
+            {
+                msg_Err( p_input, "INPUT_CONTROL_SET_POSITION(_OFFSET) ignored while recording" );
+                break;
+            }
             if( i_type == INPUT_CONTROL_SET_POSITION )
             {
                 f_pos = val.f_float;
@@ -1489,6 +1499,12 @@ static bool Control( input_thread_t *p_input, int i_type,
             int64_t i_time;
             int i_ret;
 
+            if( p_input->p->b_recording )
+            {
+                msg_Err( p_input, "INPUT_CONTROL_SET_TIME(_OFFSET) ignored while recording" );
+                break;
+            }
+
             if( i_type == INPUT_CONTROL_SET_TIME )
             {
                 i_time = val.i_time;
@@ -1744,6 +1760,11 @@ static bool Control( input_thread_t *p_input, int i_type,
         case INPUT_CONTROL_SET_TITLE:
         case INPUT_CONTROL_SET_TITLE_NEXT:
         case INPUT_CONTROL_SET_TITLE_PREV:
+            if( p_input->p->b_recording )
+            {
+                msg_Err( p_input, "INPUT_CONTROL_SET_TITLE(*) ignored while recording" );
+                break;
+            }
             if( p_input->p->input.b_title_demux &&
                 p_input->p->input.i_title > 0 )
             {
@@ -1791,6 +1812,12 @@ static bool Control( input_thread_t *p_input, int i_type,
         case INPUT_CONTROL_SET_SEEKPOINT:
         case INPUT_CONTROL_SET_SEEKPOINT_NEXT:
         case INPUT_CONTROL_SET_SEEKPOINT_PREV:
+            if( p_input->p->b_recording )
+            {
+                msg_Err( p_input, "INPUT_CONTROL_SET_SEEKPOINT(*) ignored while recording" );
+                break;
+            }
+
             if( p_input->p->input.b_title_demux &&
                 p_input->p->input.i_title > 0 )
             {
@@ -1918,6 +1945,24 @@ static bool Control( input_thread_t *p_input, int i_type,
             }
             break;
 
+        case INPUT_CONTROL_SET_RECORD_STATE:
+            if( p_input->p->input.b_can_record )
+            {
+                if( !!p_input->p->b_recording != !!val.b_bool )
+                {
+                    if( demux_Control( p_input->p->input.p_demux,
+                                       DEMUX_SET_RECORD_STATE, val.b_bool ) )
+                        val.b_bool = false;
+
+                    p_input->p->b_recording = val.b_bool;
+                }
+
+                var_Change( p_input, "record", VLC_VAR_SETVALUE, &val, NULL );
+
+                b_force_update = true;
+            }
+            break;
+
         case INPUT_CONTROL_SET_BOOKMARK:
         default:
             msg_Err( p_input, "not yet implemented" );
@@ -2056,8 +2101,8 @@ static void UpdateItemLength( input_thread_t *p_input, int64_t i_length )
  *****************************************************************************/
 static input_source_t *InputSourceNew( input_thread_t *p_input )
 {
-    (void)p_input;
-    input_source_t *in = (input_source_t*) malloc( sizeof( input_source_t ) );
+    VLC_UNUSED(p_input);
+    input_source_t *in = malloc( sizeof( input_source_t ) );
     if( in )
         memset( in, 0, sizeof( input_source_t ) );
     return in;
@@ -2320,6 +2365,10 @@ static int InputSourceInit( input_thread_t *p_input,
             goto error;
         }
 
+        if( demux_Control( in->p_demux, DEMUX_CAN_RECORD, &in->b_can_record ) )
+            in->b_can_record = false;
+        var_SetBool( p_input, "can-record", in->b_can_record );
+
         /* Get title from demux */
         if( !p_input->b_preparsing && in->i_title <= 0 )
         {
index c8e0641..b7dfd01 100644 (file)
@@ -63,6 +63,7 @@ typedef struct
     bool b_can_pause;
     bool b_can_pace_control;
     bool b_can_rate_control;
+    bool b_can_record;
     bool b_rescale_ts;
 
     bool       b_eof;   /* eof of demuxer */
@@ -84,6 +85,7 @@ struct input_thread_private_t
     bool        b_can_rate_control;
 
     int         i_rate;
+    bool        b_recording;
     /* */
     int64_t     i_start;    /* :start-time,0 by default */
     int64_t     i_stop;     /* :stop-time, 0 if none */
@@ -116,10 +118,11 @@ struct input_thread_private_t
     input_source_t **slave;
 
     /* pts delay fixup */
-    struct {
+    struct
+    {
         int  i_num_faulty;
-        bool to_high;
-        bool auto_adjust;
+        bool b_to_high;
+        bool b_auto_adjust;
     } pts_adjust;
 
     /* Stats counters */
@@ -191,6 +194,8 @@ enum input_control_e
     INPUT_CONTROL_SET_SPU_DELAY,
 
     INPUT_CONTROL_ADD_SLAVE,
+
+    INPUT_CONTROL_SET_RECORD_STATE,
 };
 
 /* Internal helpers */
index 7e35d37..db14a44 100644 (file)
 # include "config.h"
 #endif
 
+#include <dirent.h>
+
 #include <vlc_common.h>
+#include <vlc_charset.h>
+#include <vlc_strings.h>
+#include <vlc_osd.h>
 
 #include <assert.h>
 
@@ -186,6 +191,14 @@ struct stream_sys_t
 
     /* Preparse mode ? */
     bool      b_quick;
+
+    /* */
+    struct
+    {
+        bool b_active;
+
+        FILE *f;    /* TODO it could be replaced by access_output_t one day */
+    } record;
 };
 
 /* Method 1: */
@@ -212,6 +225,8 @@ static int AStreamControl( stream_t *s, int i_query, va_list );
 static void AStreamDestroy( stream_t *s );
 static void UStreamDestroy( stream_t *s );
 static int  ASeek( stream_t *s, int64_t i_pos );
+static int  ARecordSetState( stream_t *s, bool b_record, const char *psz_extension );
+static void ARecordWrite( stream_t *s, const uint8_t *p_buffer, size_t i_buffer );
 
 /****************************************************************************
  * Method 3 helpers:
@@ -314,6 +329,8 @@ stream_t *stream_AccessNew( access_t *p_access, bool b_quick )
     else
         p_sys->method = Stream;
 
+    p_sys->record.b_active = false;
+
     p_sys->i_pos = p_access->info.i_pos;
 
     /* Stats */
@@ -512,9 +529,15 @@ static void AStreamDestroy( stream_t *s )
 
     vlc_object_detach( s );
 
-    if( p_sys->method == Block ) block_ChainRelease( p_sys->block.p_first );
-    else if ( p_sys->method == Immediate ) free( p_sys->immediate.p_buffer );
-    else free( p_sys->stream.p_buffer );
+    if( p_sys->record.b_active )
+        ARecordSetState( s, false, NULL );
+
+    if( p_sys->method == Block )
+        block_ChainRelease( p_sys->block.p_first );
+    else if ( p_sys->method == Immediate )
+        free( p_sys->immediate.p_buffer );
+    else
+        free( p_sys->stream.p_buffer );
 
     free( p_sys->p_peek );
 
@@ -617,6 +640,8 @@ static int AStreamControl( stream_t *s, int i_query, va_list args )
     access_t     *p_access = p_sys->p_access;
 
     bool *p_bool;
+    bool b_bool;
+    const char *psz_string;
     int64_t    *pi_64, i_64;
     int        i_int;
 
@@ -677,6 +702,12 @@ static int AStreamControl( stream_t *s, int i_query, va_list args )
         case STREAM_GET_CONTENT_TYPE:
             return access_Control( p_access, ACCESS_GET_CONTENT_TYPE,
                                     va_arg( args, char ** ) );
+        case STREAM_SET_RECORD_STATE:
+            b_bool = (bool)va_arg( args, int );
+            psz_string = NULL;
+            if( b_bool )
+                psz_string = (const char*)va_arg( args, const char* );
+            return ARecordSetState( s, b_bool, psz_string );
 
         default:
             msg_Err( s, "invalid stream_vaControl query=0x%x", i_query );
@@ -685,7 +716,120 @@ static int AStreamControl( stream_t *s, int i_query, va_list args )
     return VLC_SUCCESS;
 }
 
+/****************************************************************************
+ * ARecord*: record stream functions
+ ****************************************************************************/
+
+/* TODO FIXME nearly the same logic that snapshot code */
+static char *ARecordGetFileName( stream_t *s, const char *psz_path, const char *psz_prefix, const char *psz_extension )
+{
+    char *psz_file;
+    DIR *path;
+
+    path = utf8_opendir( psz_path );
+    if( path )
+    {
+        closedir( path );
+
+        const char *psz_prefix = "vlc-record-%Y-%m-%d-%H:%M:%S-$p";  // TODO allow conf ?
+        char *psz_tmp = str_format( s, psz_prefix );
+        if( !psz_tmp )
+            return NULL;
+
+        filename_sanitize( psz_tmp );
+        if( asprintf( &psz_file, "%s"DIR_SEP"%s.%s",
+                      psz_path, psz_tmp, psz_extension ) < 0 )
+            psz_file = NULL;
+        free( psz_tmp );
+        return psz_file;
+    }
+    else
+    {
+        psz_file = str_format( s, psz_path );
+        path_sanitize( psz_file );
+        return psz_file;
+    }
+}
+
+static int  ARecordStart( stream_t *s, const char *psz_extension )
+{
+    stream_sys_t *p_sys = s->p_sys;
+
+    DIR *path;
+    char *psz_file;
+    FILE *f;
+
+    /* */
+    if( !psz_extension )
+        psz_extension = "dat";
+
+    /* Retreive path */
+    char *psz_path = var_CreateGetString( s, "input-record-path" );
+    if( !psz_path || *psz_path == '\0' )
+    {
+        free( psz_path );
+        psz_path = strdup( config_GetHomeDir() );
+    }
+
+    if( !psz_path )
+        return VLC_ENOMEM;
+
+    /* Create file name
+     * TODO allow prefix configuration */
+    psz_file = ARecordGetFileName( s, psz_path, "vlc-record-%Y-%m-%d-%H:%M:%S-$p", psz_extension );
+
+    free( psz_path );
+
+    if( !psz_file )
+        return VLC_ENOMEM;
+
+    f = utf8_fopen( psz_file, "wb" );
+    if( !f )
+    {
+        free( psz_file );
+        return VLC_EGENERIC;
+    }
+    msg_Dbg( s, "Recording into %s", psz_file );
+    free( psz_file );
+
+    /* */
+    p_sys->record.f = f;
+    p_sys->record.b_active = true;
+    return VLC_SUCCESS;
+}
+static int  ARecordStop( stream_t *s )
+{
+    stream_sys_t *p_sys = s->p_sys;
+
+    assert( p_sys->record.b_active );
 
+    msg_Dbg( s, "Recording completed" );
+    fclose( p_sys->record.f );
+    p_sys->record.b_active = false;
+    return VLC_SUCCESS;
+}
+
+static int  ARecordSetState( stream_t *s, bool b_record, const char *psz_extension )
+{
+    stream_sys_t *p_sys = s->p_sys;
+
+    if( !!p_sys->record.b_active == !!b_record )
+        return VLC_SUCCESS;
+
+    if( b_record )
+        return ARecordStart( s, psz_extension );
+    else
+        return ARecordStop( s );
+}
+static void ARecordWrite( stream_t *s, const uint8_t *p_buffer, size_t i_buffer )
+{
+    stream_sys_t *p_sys = s->p_sys;
+
+    assert( p_sys->record.b_active );
+
+    if( i_buffer )
+        fwrite( p_buffer, 1, i_buffer, p_sys->record.f );
+}
 
 /****************************************************************************
  * Method 1:
@@ -821,6 +965,9 @@ static int AStreamReadBlock( stream_t *s, void *p_read, unsigned int i_read )
         }
     }
 
+    if( p_sys->record.b_active && i_data > 0 )
+        ARecordWrite( s, p_read, i_data );
+
     p_sys->i_pos += i_data;
     return i_data;
 }
@@ -1165,6 +1312,9 @@ static int AStreamReadStream( stream_t *s, void *p_read, unsigned int i_read )
         }
     }
 
+    if( p_sys->record.b_active && i_data > 0 )
+        ARecordWrite( s, p_read, i_data );
+
     return i_data;
 }
 
@@ -1552,6 +1702,9 @@ static int AStreamReadImmediate( stream_t *s, void *p_read, unsigned int i_read
         }
     }
 
+    if( p_sys->record.b_active && i_copy > 0 )
+        ARecordWrite( s, p_read, i_copy );
+
     p_sys->i_pos += i_to_read;
 
     return i_to_read + i_copy;
index 9cd9629..7dbb405 100644 (file)
@@ -61,6 +61,10 @@ static int EsDelayCallback ( vlc_object_t *p_this, char const *psz_cmd,
 static int BookmarkCallback( vlc_object_t *p_this, char const *psz_cmd,
                              vlc_value_t oldval, vlc_value_t newval, void * );
 
+static int RecordCallback( vlc_object_t *p_this, char const *psz_cmd,
+                           vlc_value_t oldval, vlc_value_t newval,
+                           void *p_data );
+
 typedef struct
 {
     const char *psz_name;
@@ -93,6 +97,7 @@ static const vlc_input_callback_t p_input_callbacks[] =
     CALLBACK( "video-es", ESCallback ),
     CALLBACK( "audio-es", ESCallback ),
     CALLBACK( "spu-es", ESCallback ),
+    CALLBACK( "record", RecordCallback ),
 
     CALLBACK( NULL, NULL )
 };
@@ -189,9 +194,6 @@ void input_ControlVarInit ( input_thread_t *p_input )
     val.i_time = 0;
     var_Change( p_input, "spu-delay", VLC_VAR_SETVALUE, &val, NULL );
 
-    p_input->p->pts_adjust.auto_adjust = var_CreateGetBool(
-            p_input, "auto-adjust-pts-delay" );
-
     /* Video ES */
     var_Create( p_input, "video-es", VLC_VAR_INTEGER | VLC_VAR_HASCHOICE );
     text.psz_string = _("Video Track");
@@ -404,8 +406,6 @@ void input_ControlVarTitle( input_thread_t *p_input, int i_title )
  *****************************************************************************/
 void input_ConfigVarInit ( input_thread_t *p_input )
 {
-    vlc_value_t val;
-
     /* Create Object Variables for private use only */
 
     if( !p_input->b_preparsing )
@@ -460,14 +460,22 @@ void input_ConfigVarInit ( input_thread_t *p_input )
                     VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
         var_Create( p_input, "clock-synchro",
                     VLC_VAR_INTEGER | VLC_VAR_DOINHERIT);
+        var_Create( p_input, "auto-adjust-pts-delay",
+                    VLC_VAR_BOOL | VLC_VAR_DOINHERIT);
     }
 
     var_Create( p_input, "seekable", VLC_VAR_BOOL );
-    val.b_bool = true; /* Fixed later*/
-    var_Change( p_input, "seekable", VLC_VAR_SETVALUE, &val, NULL );
+    var_SetBool( p_input, "seekable", true ); /* Fixed later*/
+
     var_Create( p_input, "can-pause", VLC_VAR_BOOL );
-    val.b_bool = true; /* Fixed later*/
-    var_Change( p_input, "can-pause", VLC_VAR_SETVALUE, &val, NULL );
+    var_SetBool( p_input, "can-pause", true ); /* Fixed later*/
+
+    var_Create( p_input, "can-record", VLC_VAR_BOOL );
+    var_SetBool( p_input, "can-record", false ); /* Fixed later*/
+
+    var_Create( p_input, "record", VLC_VAR_BOOL );
+    var_SetBool( p_input, "record", false );
+
     var_Create( p_input, "teletext-es", VLC_VAR_INTEGER );
     var_SetInteger( p_input, "teletext-es", -1 );
 
@@ -776,3 +784,15 @@ static int BookmarkCallback( vlc_object_t *p_this, char const *psz_cmd,
 
     return VLC_SUCCESS;
 }
+
+static int RecordCallback( 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);
+
+    input_ControlPush( p_input, INPUT_CONTROL_SET_RECORD_STATE, &newval );
+
+    return VLC_SUCCESS;
+}
index fb69a12..a7d6310 100644 (file)
@@ -675,6 +675,10 @@ static const char *const ppsz_clock_descriptions[] =
     "the form \"{name=bookmark-name,time=optional-time-offset," \
     "bytes=optional-byte-offset},{...}\"")
 
+#define INPUT_RECORD_PATH_TEXT N_("Record directory or filename")
+#define INPUT_RECORD_PATH_LONGTEXT N_( \
+    "Directory or filename where the records will be stored" )
+
 // DEPRECATED
 #define SUB_CAT_LONGTEXT N_( \
     "These options allow you to modify the behavior of the subpictures " \
@@ -1737,6 +1741,9 @@ vlc_module_begin();
     add_bool( "network-synchronisation", false, NULL, NETSYNC_TEXT,
               NETSYNC_LONGTEXT, true );
 
+    add_string( "input-record-path", NULL, NULL, INPUT_RECORD_PATH_TEXT,
+                INPUT_RECORD_PATH_LONGTEXT, true );
+
 /* Decoder options */
     add_category_hint( N_("Decoders"), CODEC_CAT_LONGTEXT , true );
     add_string( "codec", NULL, NULL, CODEC_TEXT,