+static void box_send( sout_mux_t *p_mux, bo_t *box );
+
+static block_t *bo_to_sout( sout_instance_t *p_sout, bo_t *box );
+
+static bo_t *GetMoovBox( sout_mux_t *p_mux );
+
+static block_t *ConvertSUBT( block_t *);
+static block_t *ConvertAVC1( block_t * );
+
+/*****************************************************************************
+ * Open:
+ *****************************************************************************/
+static int Open( vlc_object_t *p_this )
+{
+ sout_mux_t *p_mux = (sout_mux_t*)p_this;
+ sout_mux_sys_t *p_sys;
+ bo_t *box;
+
+ msg_Dbg( p_mux, "Mp4 muxer opened" );
+ config_ChainParse( p_mux, SOUT_CFG_PREFIX, ppsz_sout_options, p_mux->p_cfg );
+
+ p_mux->pf_control = Control;
+ p_mux->pf_addstream = AddStream;
+ p_mux->pf_delstream = DelStream;
+ p_mux->pf_mux = Mux;
+ p_mux->p_sys = p_sys = malloc( sizeof( sout_mux_sys_t ) );
+ p_sys->i_pos = 0;
+ p_sys->i_nb_streams = 0;
+ p_sys->pp_streams = NULL;
+ p_sys->i_mdat_pos = 0;
+ p_sys->b_mov = p_mux->psz_mux && !strcmp( p_mux->psz_mux, "mov" );
+ p_sys->b_3gp = p_mux->psz_mux && !strcmp( p_mux->psz_mux, "3gp" );
+ p_sys->i_dts_start = 0;
+
+
+ if( !p_sys->b_mov )
+ {
+ /* Now add ftyp header */
+ box = box_new( "ftyp" );
+ if( p_sys->b_3gp ) bo_add_fourcc( box, "3gp4" );
+ else bo_add_fourcc( box, "isom" );
+ bo_add_32be ( box, 0 );
+ if( p_sys->b_3gp ) bo_add_fourcc( box, "3gp4" );
+ else bo_add_fourcc( box, "mp41" );
+ box_fix( box );
+
+ p_sys->i_pos += box->i_buffer;
+ p_sys->i_mdat_pos = p_sys->i_pos;
+
+ box_send( p_mux, box );
+ }
+
+ /* FIXME FIXME
+ * Quicktime actually doesn't like the 64 bits extensions !!! */
+ p_sys->b_64_ext = false;
+
+ /* Now add mdat header */
+ box = box_new( "mdat" );
+ bo_add_64be ( box, 0 ); // enough to store an extended size
+
+ p_sys->i_pos += box->i_buffer;
+
+ box_send( p_mux, box );
+
+ return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * Close:
+ *****************************************************************************/
+static void Close( vlc_object_t * p_this )
+{
+ sout_mux_t *p_mux = (sout_mux_t*)p_this;
+ sout_mux_sys_t *p_sys = p_mux->p_sys;
+ block_t *p_hdr;
+ bo_t bo, *moov;
+ vlc_value_t val;
+
+ int i_trak;
+ uint64_t i_moov_pos;
+
+ msg_Dbg( p_mux, "Close" );
+
+ /* Update mdat size */
+ bo_init( &bo, 0, NULL, true );
+ if( p_sys->i_pos - p_sys->i_mdat_pos >= (((uint64_t)1)<<32) )
+ {
+ /* Extended size */
+ bo_add_32be ( &bo, 1 );
+ bo_add_fourcc( &bo, "mdat" );
+ bo_add_64be ( &bo, p_sys->i_pos - p_sys->i_mdat_pos );
+ }
+ else
+ {
+ bo_add_32be ( &bo, 8 );
+ bo_add_fourcc( &bo, "wide" );
+ bo_add_32be ( &bo, p_sys->i_pos - p_sys->i_mdat_pos - 8 );
+ bo_add_fourcc( &bo, "mdat" );
+ }
+ p_hdr = bo_to_sout( p_mux->p_sout, &bo );
+ free( bo.p_buffer );
+
+ sout_AccessOutSeek( p_mux->p_access, p_sys->i_mdat_pos );
+ sout_AccessOutWrite( p_mux->p_access, p_hdr );
+
+ /* Create MOOV header */
+ i_moov_pos = p_sys->i_pos;
+ moov = GetMoovBox( p_mux );
+
+ /* Check we need to create "fast start" files */
+ var_Get( p_this, SOUT_CFG_PREFIX "faststart", &val );
+ p_sys->b_fast_start = val.b_bool;
+ while( p_sys->b_fast_start )
+ {
+ /* Move data to the end of the file so we can fit the moov header
+ * at the start */
+ block_t *p_buf;
+ int64_t i_chunk, i_size = p_sys->i_pos - p_sys->i_mdat_pos;
+ int i_moov_size = moov->i_buffer;
+
+ while( i_size > 0 )
+ {
+ i_chunk = __MIN( 32768, i_size );
+ p_buf = block_New( p_mux, i_chunk );
+ sout_AccessOutSeek( p_mux->p_access,
+ p_sys->i_mdat_pos + i_size - i_chunk );
+ if( sout_AccessOutRead( p_mux->p_access, p_buf ) < i_chunk )
+ {
+ msg_Warn( p_this, "read() not supported by access output, "
+ "won't create a fast start file" );
+ p_sys->b_fast_start = false;
+ block_Release( p_buf );
+ break;
+ }
+ sout_AccessOutSeek( p_mux->p_access, p_sys->i_mdat_pos + i_size +
+ i_moov_size - i_chunk );
+ sout_AccessOutWrite( p_mux->p_access, p_buf );
+ i_size -= i_chunk;
+ }
+
+ if( !p_sys->b_fast_start ) break;
+
+ /* Fix-up samples to chunks table in MOOV header */
+ for( i_trak = 0; i_trak < p_sys->i_nb_streams; i_trak++ )
+ {
+ mp4_stream_t *p_stream = p_sys->pp_streams[i_trak];
+ unsigned int i;
+ int i_chunk;
+
+ moov->i_buffer = p_stream->i_stco_pos;
+ for( i_chunk = 0, i = 0; i < p_stream->i_entry_count; i_chunk++ )
+ {
+ if( p_stream->b_stco64 )
+ bo_add_64be( moov, p_stream->entry[i].i_pos + i_moov_size);
+ else
+ bo_add_32be( moov, p_stream->entry[i].i_pos + i_moov_size);
+
+ while( i < p_stream->i_entry_count )
+ {
+ if( i + 1 < p_stream->i_entry_count &&
+ p_stream->entry[i].i_pos + p_stream->entry[i].i_size
+ != p_stream->entry[i + 1].i_pos )
+ {
+ i++;
+ break;
+ }
+
+ i++;
+ }
+ }
+ }
+
+ moov->i_buffer = i_moov_size;
+ i_moov_pos = p_sys->i_mdat_pos;
+ p_sys->b_fast_start = false;
+ }
+
+ /* Write MOOV header */
+ sout_AccessOutSeek( p_mux->p_access, i_moov_pos );
+ box_send( p_mux, moov );
+
+ /* Clean-up */
+ for( i_trak = 0; i_trak < p_sys->i_nb_streams; i_trak++ )
+ {
+ mp4_stream_t *p_stream = p_sys->pp_streams[i_trak];
+
+ es_format_Clean( &p_stream->fmt );
+ free( p_stream->entry );
+ free( p_stream );
+ }
+ if( p_sys->i_nb_streams ) free( p_sys->pp_streams );
+ free( p_sys );
+}
+
+/*****************************************************************************
+ * Control:
+ *****************************************************************************/
+static int Control( sout_mux_t *p_mux, int i_query, va_list args )
+{
+ VLC_UNUSED(p_mux);
+ bool *pb_bool;
+
+ switch( i_query )
+ {
+ case MUX_CAN_ADD_STREAM_WHILE_MUXING:
+ pb_bool = (bool*)va_arg( args, bool * );
+ *pb_bool = false;
+ return VLC_SUCCESS;
+
+ case MUX_GET_ADD_STREAM_WAIT:
+ pb_bool = (bool*)va_arg( args, bool * );
+ *pb_bool = true;
+ return VLC_SUCCESS;
+
+ case MUX_GET_MIME: /* Not needed, as not streamable */
+ default:
+ return VLC_EGENERIC;
+ }
+}
+
+/*****************************************************************************
+ * AddStream:
+ *****************************************************************************/
+static int AddStream( sout_mux_t *p_mux, sout_input_t *p_input )
+{
+ sout_mux_sys_t *p_sys = p_mux->p_sys;
+ mp4_stream_t *p_stream;
+
+ switch( p_input->p_fmt->i_codec )
+ {
+ case VLC_FOURCC( 'm', 'p', '4', 'a' ):
+ case VLC_FOURCC( 'm', 'p', '4', 'v' ):
+ case VLC_FOURCC( 'm', 'p', 'g', 'a' ):
+ case VLC_FOURCC( 'm', 'p', 'g', 'v' ):
+ case VLC_FOURCC( 'M', 'J', 'P', 'G' ):
+ case VLC_FOURCC( 'm', 'j', 'p', 'b' ):
+ case VLC_FOURCC( 'S', 'V', 'Q', '1' ):
+ case VLC_FOURCC( 'S', 'V', 'Q', '3' ):
+ case VLC_FOURCC( 'H', '2', '6', '3' ):
+ case VLC_FOURCC( 'h', '2', '6', '4' ):
+ case VLC_FOURCC( 's', 'a', 'm', 'r' ):
+ case VLC_FOURCC( 's', 'a', 'w', 'b' ):
+ case VLC_FOURCC( 'Y', 'V', '1', '2' ):
+ case VLC_FOURCC( 'Y', 'U', 'Y', '2' ):
+ break;
+ case VLC_FOURCC( 's', 'u', 'b', 't' ):
+ msg_Warn( p_mux, "subtitle track added like in .mov (even when creating .mp4)" );
+ break;
+ default:
+ msg_Err( p_mux, "unsupported codec %4.4s in mp4",
+ (char*)&p_input->p_fmt->i_codec );
+ return VLC_EGENERIC;
+ }
+
+ p_stream = malloc( sizeof( mp4_stream_t ) );
+ es_format_Copy( &p_stream->fmt, p_input->p_fmt );
+ p_stream->i_track_id = p_sys->i_nb_streams + 1;
+ p_stream->i_length_neg = 0;
+ p_stream->i_entry_count = 0;
+ p_stream->i_entry_max = 1000;
+ p_stream->entry =
+ calloc( p_stream->i_entry_max, sizeof( mp4_entry_t ) );
+ p_stream->i_dts_start = 0;
+ p_stream->i_duration = 0;
+
+ p_input->p_sys = p_stream;
+
+ msg_Dbg( p_mux, "adding input" );
+
+ TAB_APPEND( p_sys->i_nb_streams, p_sys->pp_streams, p_stream );
+ return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * DelStream:
+ *****************************************************************************/
+static int DelStream( sout_mux_t *p_mux, sout_input_t *p_input )
+{
+ VLC_UNUSED(p_input);
+ msg_Dbg( p_mux, "removing input" );
+ return VLC_SUCCESS;
+}
+
+static int MuxGetStream( sout_mux_t *p_mux, int *pi_stream, mtime_t *pi_dts )
+{
+ mtime_t i_dts;
+ int i_stream, i;
+
+ for( i = 0, i_dts = 0, i_stream = -1; i < p_mux->i_nb_inputs; i++ )
+ {
+ block_fifo_t *p_fifo = p_mux->pp_inputs[i]->p_fifo;
+ block_t *p_buf;
+
+ if( block_FifoCount( p_fifo ) <= 1 )
+ {
+ if( p_mux->pp_inputs[i]->p_fmt->i_cat != SPU_ES )
+ {
+ return -1; // wait that all fifo have at least 2 packets
+ }
+ /* For SPU, we wait only 1 packet */
+ continue;
+ }
+
+ p_buf = block_FifoShow( p_fifo );
+ if( i_stream < 0 || p_buf->i_dts < i_dts )
+ {
+ i_dts = p_buf->i_dts;
+ i_stream = i;
+ }
+ }
+ if( pi_stream )
+ {
+ *pi_stream = i_stream;
+ }
+ if( pi_dts )
+ {
+ *pi_dts = i_dts;
+ }
+ return i_stream;
+}
+
+/*****************************************************************************
+ * Mux:
+ *****************************************************************************/
+static int Mux( sout_mux_t *p_mux )
+{
+ sout_mux_sys_t *p_sys = p_mux->p_sys;
+
+ for( ;; )
+ {
+ sout_input_t *p_input;
+ int i_stream;
+ mp4_stream_t *p_stream;
+ block_t *p_data;
+ mtime_t i_dts;
+
+ if( MuxGetStream( p_mux, &i_stream, &i_dts) < 0 )
+ {
+ return( VLC_SUCCESS );
+ }
+
+ p_input = p_mux->pp_inputs[i_stream];
+ p_stream = (mp4_stream_t*)p_input->p_sys;
+
+again:
+ p_data = block_FifoGet( p_input->p_fifo );
+ if( p_stream->fmt.i_codec == VLC_FOURCC( 'h', '2', '6', '4' ) )
+ {
+ p_data = ConvertAVC1( p_data );
+ }
+ else if( p_stream->fmt.i_codec == VLC_FOURCC( 's', 'u', 'b', 't' ) )
+ {
+ p_data = ConvertSUBT( p_data );
+ }
+ if( p_data == NULL ) goto again;
+
+ if( p_stream->fmt.i_cat != SPU_ES )
+ {
+ /* Fix length of the sample */
+ if( block_FifoCount( p_input->p_fifo ) > 0 )
+ {
+ block_t *p_next = block_FifoShow( p_input->p_fifo );
+ int64_t i_diff = p_next->i_dts - p_data->i_dts;
+
+ if( i_diff < INT64_C(1000000 ) ) /* protection */
+ {
+ p_data->i_length = i_diff;
+ }
+ }
+ if( p_data->i_length <= 0 )
+ {
+ msg_Warn( p_mux, "i_length <= 0" );
+ p_stream->i_length_neg += p_data->i_length - 1;
+ p_data->i_length = 1;
+ }
+ else if( p_stream->i_length_neg < 0 )
+ {
+ int64_t i_recover = __MIN( p_data->i_length / 4, - p_stream->i_length_neg );
+
+ p_data->i_length -= i_recover;
+ p_stream->i_length_neg += i_recover;
+ }
+ }
+
+ /* Save starting time */
+ if( p_stream->i_entry_count == 0 )
+ {
+ p_stream->i_dts_start = p_data->i_dts;
+
+ /* Update global dts_start */
+ if( p_sys->i_dts_start <= 0 ||
+ p_stream->i_dts_start < p_sys->i_dts_start )
+ {
+ p_sys->i_dts_start = p_stream->i_dts_start;
+ }
+ }
+
+ if( p_stream->fmt.i_cat == SPU_ES && p_stream->i_entry_count > 0 )
+ {
+ int64_t i_length = p_data->i_dts - p_stream->i_last_dts;
+
+ if( i_length <= 0 )
+ {
+ /* FIXME handle this broken case */
+ i_length = 1;
+ }
+
+ /* Fix last entry */
+ if( p_stream->entry[p_stream->i_entry_count-1].i_length <= 0 )
+ {
+ p_stream->entry[p_stream->i_entry_count-1].i_length = i_length;
+ }
+ }
+
+
+ /* add index entry */
+ p_stream->entry[p_stream->i_entry_count].i_pos = p_sys->i_pos;
+ p_stream->entry[p_stream->i_entry_count].i_size = p_data->i_buffer;
+ p_stream->entry[p_stream->i_entry_count].i_pts_dts=
+ __MAX( p_data->i_pts - p_data->i_dts, 0 );
+ p_stream->entry[p_stream->i_entry_count].i_length = p_data->i_length;
+ p_stream->entry[p_stream->i_entry_count].i_flags = p_data->i_flags;
+
+ p_stream->i_entry_count++;
+ /* XXX: -1 to always have 2 entry for easy adding of empty SPU */
+ if( p_stream->i_entry_count >= p_stream->i_entry_max - 1 )
+ {
+ p_stream->i_entry_max += 1000;
+ p_stream->entry =
+ realloc( p_stream->entry,
+ p_stream->i_entry_max * sizeof( mp4_entry_t ) );
+ }
+
+ /* update */
+ p_stream->i_duration += p_data->i_length;
+ p_sys->i_pos += p_data->i_buffer;
+
+ /* Save the DTS */
+ p_stream->i_last_dts = p_data->i_dts;
+
+ /* write data */
+ sout_AccessOutWrite( p_mux->p_access, p_data );
+
+ if( p_stream->fmt.i_cat == SPU_ES )
+ {
+ int64_t i_length = p_stream->entry[p_stream->i_entry_count-1].i_length;
+
+ if( i_length != 0 )
+ {
+ /* TODO */
+ msg_Dbg( p_mux, "writing an empty sub" ) ;
+
+ /* Append a idx entry */
+ p_stream->entry[p_stream->i_entry_count].i_pos = p_sys->i_pos;
+ p_stream->entry[p_stream->i_entry_count].i_size = 3;
+ p_stream->entry[p_stream->i_entry_count].i_pts_dts= 0;
+ p_stream->entry[p_stream->i_entry_count].i_length = 0;
+ p_stream->entry[p_stream->i_entry_count].i_flags = 0;
+
+ /* XXX: No need to grow the entry here */
+ p_stream->i_entry_count++;
+
+ /* Fix last dts */
+ p_stream->i_last_dts += i_length;
+
+ /* Write a " " */
+ p_data = block_New( p_mux, 3 );
+ p_data->p_buffer[0] = 0;
+ p_data->p_buffer[1] = 1;
+ p_data->p_buffer[2] = ' ';
+
+ p_sys->i_pos += p_data->i_buffer;
+
+ sout_AccessOutWrite( p_mux->p_access, p_data );
+ }