Commit dbe002fd authored by Christophe Massiot's avatar Christophe Massiot

* New (experimental and incomplete) demuxstream module. It bypasses the

stream output, and takes what comes from a DVB card and sends it onto
the network. I didn't activate it in configure.ac because I suspect there
might be problems with iovec on Windows (and there is probably a better
way to do it).
parent 96e75a5f
......@@ -3,6 +3,7 @@ SOURCES_flac = flac.c
SOURCES_ogg = ogg.c
SOURCES_m3u = m3u.c
SOURCES_demuxdump = demuxdump.c
SOURCES_demuxstream = demuxstream.c
SOURCES_rawdv = rawdv.c
SOURCES_au = au.c
SOURCES_wav = wav.c
......
/*****************************************************************************
* demuxstream.c: Read an MPEG stream from the satellite and stream it
*****************************************************************************
* Copyright (C) 2003 VideoLAN
* $Id: demuxstream.c,v 1.1 2003/11/06 16:06:28 massiot Exp $
*
* Authors: Henri Fallon <henri@via.ecp.fr>
* Johan Bilien <jobi@via.ecp.fr>
* Laurent Aimar <fenrir@via.ecp.fr>
* Christophe Massiot <massiot@via.ecp.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
/*
* Note :
* The goal of this module is to stream packets coming from a DVB interface
* as fast as possible onto the network, like VLS does. This is a copy/paste
* from modules/demux/mpeg/ts.c, because I need to parse the PMT to set
* the demux filters in the satellite module. So I need the full TS module.
* It would probably have been better not to copy/paste and use some kind
* of hooks, but it is too complicated for me. Use this module that way :
*
* vlc satellite/demuxstream: --program 4242 --demuxstream-mrl \
* udp:192.168.255.1:1234
*
* Also note that this module doesn't rebuild the PAT/PMT, and doesn't
* filter the PIDs : it just takes whatever comes from the DVB interface
* and sends it to the network. The client VLC should have no problem to
* deal with it, but other implementations will probably die.
*
* This file is part of the Dreambox port of VLC.
* --Meuuh
*/
/*****************************************************************************
* Preamble
*****************************************************************************/
#include <stdlib.h>
#include <unistd.h>
#include <sys/uio.h>
#include <vlc/vlc.h>
#include <vlc/input.h>
#include "iso_lang.h"
#if defined MODULE_NAME_IS_demuxstream
# ifdef HAVE_DVBPSI_DR_H
# include <dvbpsi/dvbpsi.h>
# include <dvbpsi/descriptor.h>
# include <dvbpsi/pat.h>
# include <dvbpsi/pmt.h>
# include <dvbpsi/dr.h>
# else
# include "dvbpsi.h"
# include "descriptor.h"
# include "tables/pat.h"
# include "tables/pmt.h"
# include "descriptors/dr.h"
# endif
#endif
#include "mpeg/system.h"
#include "codecs.h"
#include "network.h"
/*****************************************************************************
* Constants
*****************************************************************************/
#define TS_READ_ONCE 7
#define DEFAULT_PORT 1234
/*****************************************************************************
* Private structure
*****************************************************************************/
struct demux_sys_t
{
module_t * p_module;
mpeg_demux_t mpeg;
int i_handle;
};
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static int Activate ( vlc_object_t * );
static void Deactivate ( vlc_object_t * );
static int Demux ( input_thread_t * );
#if defined MODULE_NAME_IS_demuxstream_old
static void TSDemuxPSI ( input_thread_t *, data_packet_t *,
es_descriptor_t *, vlc_bool_t );
static void TSDecodePAT( input_thread_t *, es_descriptor_t *);
static void TSDecodePMT( input_thread_t *, es_descriptor_t *);
#define PSI_CALLBACK TSDemuxPSI
#elif defined MODULE_NAME_IS_demuxstream
static void TS_DVBPSI_DemuxPSI ( input_thread_t *, data_packet_t *,
es_descriptor_t *, vlc_bool_t );
static void TS_DVBPSI_HandlePAT ( input_thread_t *, dvbpsi_pat_t * );
static void TS_DVBPSI_HandlePMT ( input_thread_t *, dvbpsi_pmt_t * );
#define PSI_CALLBACK TS_DVBPSI_DemuxPSI
#endif
/*****************************************************************************
* Module descriptor
*****************************************************************************/
#define VLS_BACKWARDS_COMPAT_TEXT N_("Compatibility with pre-0.4 VLS")
#define VLS_BACKWARDS_COMPAT_LONGTEXT N_( \
"The protocol for transmitting A/52 audio streams changed between VLC " \
"0.3.x and 0.4. By default VLC assumes you have the latest VLS. In case " \
"you're using an old version, select this option.")
#define BUGGY_PSI_TEXT N_("Buggy PSI")
#define BUGGY_PSI_LONGTEXT N_( \
"If you have a stream whose PSI packets do not feature incremented " \
"continuity counters, select this option.")
#define MRL_TEXT N_("Output MRL")
vlc_module_begin();
#if defined MODULE_NAME_IS_demuxstream_old
set_description( _("ISO 13818-1 MPEG Transport Stream demuxstream") );
set_capability( "demux", 0 );
add_shortcut( "demuxstream_old" );
#elif defined MODULE_NAME_IS_demuxstream
set_description( _("ISO 13818-1 MPEG Transport Stream demuxstream (libdvbpsi)") );
set_capability( "demux", 0 );
add_shortcut( "demuxstream" );
#endif
add_category_hint( N_("Miscellaneous"), NULL, VLC_TRUE );
add_bool( "vls-backwards-compat", 0, NULL,
VLS_BACKWARDS_COMPAT_TEXT, VLS_BACKWARDS_COMPAT_LONGTEXT, VLC_TRUE );
add_bool( "buggy-psi", 0, NULL, BUGGY_PSI_TEXT, BUGGY_PSI_LONGTEXT, VLC_TRUE );
add_string( "demuxstream-mrl", "", NULL, MRL_TEXT, NULL, VLC_FALSE );
set_callbacks( Activate, Deactivate );
vlc_module_end();
/*****************************************************************************
* Activate: initialize TS structures
*****************************************************************************/
static int Activate( vlc_object_t * p_this )
{
input_thread_t * p_input = (input_thread_t *)p_this;
demux_sys_t * p_demux;
es_descriptor_t * p_pat_es;
es_ts_data_t * p_demux_data;
stream_ts_data_t * p_stream_data;
byte_t * p_peek;
char * psz_mrl = config_GetPsz( p_input, "demuxstream-mrl" );
char * psz_parser = psz_mrl;
char * psz_dst_addr;
int i_dst_port;
module_t *p_network;
network_socket_t socket_desc;
/* Open the output socket */
if ( psz_mrl == NULL || !*psz_mrl )
{
msg_Err( p_input, "invalid demuxstream-mrl" );
return -1;
}
if ( strncmp( psz_mrl, "udp:", 4 ) )
{
msg_Err( p_input, "demuxstream-mrl should start with udp: (%s)",
psz_mrl );
return -1;
}
psz_parser += 4;
while ( *psz_parser == '/' )
{
psz_parser++;
}
psz_dst_addr = psz_parser;
i_dst_port = 0;
if ( *psz_parser == '[' )
{
while( *psz_parser && *psz_parser != ']' )
{
psz_parser++;
}
}
while( *psz_parser && *psz_parser != ':' )
{
psz_parser++;
}
if( *psz_parser == ':' )
{
*psz_parser = '\0';
psz_parser++;
i_dst_port = atoi( psz_parser );
}
if( i_dst_port <= 0 )
{
i_dst_port = DEFAULT_PORT;
}
socket_desc.i_type = NETWORK_UDP;
socket_desc.psz_server_addr = psz_dst_addr;
socket_desc.i_server_port = i_dst_port;
socket_desc.psz_bind_addr = "";
socket_desc.i_bind_port = 0;
socket_desc.i_ttl = 0;
p_input->p_private = (void*)&socket_desc;
if( !( p_network = module_Need( p_input,
"network", "" ) ) )
{
msg_Err( p_input, "failed to open a connection (udp)" );
return( VLC_EGENERIC );
}
module_Unneed( p_input, p_network );
/* Set the demux function */
p_input->pf_demux = Demux;
p_input->pf_demux_control = demux_vaControlDefault;
#if 0
/* XXX Unused already done by src/input.c */
/* Initialize access plug-in structures. */
if( p_input->i_mtu == 0 )
{
/* Improve speed. */
msg_Dbg( p_input, "using default mtu (%d) with bufsize (%d)\n",
p_input->i_mtu, INPUT_DEFAULT_BUFSIZE );
p_input->i_bufsize = INPUT_DEFAULT_BUFSIZE;
}
#endif
/* Have a peep at the show. */
if( input_Peek( p_input, &p_peek, 1 ) < 1 )
{
msg_Err( p_input, "cannot peek()" );
return -1;
}
if( *p_peek != TS_SYNC_CODE )
{
msg_Err( p_input, "this does not look like a TS stream, continuing" );
}
/* Adapt the bufsize for our only use. */
if( p_input->i_mtu != 0 )
{
/* Have minimum granularity to avoid bottlenecks at the input level. */
p_input->i_bufsize = (p_input->i_mtu / TS_PACKET_SIZE) * TS_PACKET_SIZE;
}
p_demux = p_input->p_demux_data = malloc( sizeof(demux_sys_t ) );
if( p_demux == NULL )
{
return -1;
}
p_demux->i_handle = socket_desc.i_handle;
p_input->p_private = (void*)&p_demux->mpeg;
p_demux->p_module = module_Need( p_input, "mpeg-system", NULL );
if( p_demux->p_module == NULL )
{
free( p_input->p_demux_data );
return -1;
}
vlc_mutex_lock( &p_input->stream.stream_lock );
if( input_InitStream( p_input, sizeof( stream_ts_data_t ) ) == -1 )
{
module_Unneed( p_input, p_demux->p_module );
free( p_input->p_demux_data );
return -1;
}
p_stream_data = (stream_ts_data_t *)p_input->stream.p_demux_data;
p_stream_data->i_pat_version = PAT_UNINITIALIZED ;
p_stream_data->b_buggy_psi = config_GetInt( p_input, "buggy-psi" );
#ifdef MODULE_NAME_IS_demuxstream
p_stream_data->p_pat_handle = (dvbpsi_handle *)
dvbpsi_AttachPAT( (dvbpsi_pat_callback) &TS_DVBPSI_HandlePAT, p_input );
if( p_stream_data->p_pat_handle == NULL )
{
msg_Err( p_input, "could not create PAT decoder" );
module_Unneed( p_input, p_demux->p_module );
free( p_input->p_demux_data );
return -1;
}
#endif
/* We'll have to catch the PAT in order to continue
* Then the input will catch the PMT and then the others ES
* The PAT es is indepedent of any program. */
p_pat_es = input_AddES( p_input, NULL, 0x00,
UNKNOWN_ES, NULL, sizeof( es_ts_data_t ) );
p_pat_es->i_fourcc = VLC_FOURCC( 'p', 'a', 't', ' ' );
p_demux_data = (es_ts_data_t *)p_pat_es->p_demux_data;
p_demux_data->b_psi = 1;
p_demux_data->i_psi_type = PSI_IS_PAT;
p_demux_data->p_psi_section = malloc(sizeof(psi_section_t));
p_demux_data->p_psi_section->b_is_complete = 1;
p_demux_data->i_continuity_counter = 0xFF;
vlc_mutex_unlock( &p_input->stream.stream_lock );
return 0;
}
/*****************************************************************************
* Deactivate: deinitialize TS structures
*****************************************************************************/
static void Deactivate( vlc_object_t * p_this )
{
input_thread_t * p_input = (input_thread_t *)p_this;
module_Unneed( p_input, p_input->p_demux_data->p_module );
close( p_input->p_demux_data->i_handle );
free( p_input->p_demux_data );
}
/*****************************************************************************
* Demux: reads and demuxes data packets
*****************************************************************************
* Returns -1 in case of error, 0 in case of EOF, otherwise the number of
* packets.
*****************************************************************************/
static int Demux( input_thread_t * p_input )
{
demux_sys_t * p_demux = p_input->p_demux_data;
int i_read_once = TS_READ_ONCE;
int i, j;
data_packet_t * p_data[i_read_once];
struct iovec vector[i_read_once];
for( i = 0; i < i_read_once; i++ )
{
ssize_t i_result;
i_result = p_demux->mpeg.pf_read_ts( p_input, &p_data[i] );
if( i_result <= 0 )
{
return i_result;
}
vector[i].iov_base = p_data[i]->p_demux_start;
vector[i].iov_len = TS_PACKET_SIZE;
}
/* Send the buffer immediately. */
writev( p_demux->i_handle, vector, i );
for( j = 0; j < i; j++ )
{
p_demux->mpeg.pf_demux_ts( p_input, p_data[j],
(psi_callback_t) &PSI_CALLBACK );
}
return i_read_once;
}
#if defined MODULE_NAME_IS_demuxstream_old
/*
* PSI demultiplexing and decoding without libdvbpsi
*/
/*****************************************************************************
* DemuxPSI : makes up complete PSI data
*****************************************************************************/
static void TSDemuxPSI( input_thread_t * p_input, data_packet_t * p_data,
es_descriptor_t * p_es, vlc_bool_t b_unit_start )
{
es_ts_data_t * p_demux_data;
p_demux_data = (es_ts_data_t *)p_es->p_demux_data;
#define p_psi (p_demux_data->p_psi_section)
#define p (p_data->p_payload_start)
if( b_unit_start )
{
/* unit_start set to 1 -> presence of a pointer field
* (see ISO/IEC 13818 (2.4.4.2) which should be set to 0x00 */
if( (uint8_t)p[0] != 0x00 )
{
msg_Warn( p_input,
"non-zero pointer field found, trying to continue" );
p+=(uint8_t)p[0];
}
else
{
p++;
}
/* This is the begining of a new section */
if( ((uint8_t)(p[1]) & 0xc0) != 0x80 )
{
msg_Warn( p_input, "invalid PSI packet" );
p_psi->b_trash = 1;
}
else
{
p_psi->i_section_length = ((p[1] & 0xF) << 8) | p[2];
p_psi->b_section_complete = 0;
p_psi->i_read_in_section = 0;
p_psi->i_section_number = (uint8_t)p[6];
if( p_psi->b_is_complete || p_psi->i_section_number == 0 )
{
/* This is a new PSI packet */
p_psi->b_is_complete = 0;
p_psi->b_trash = 0;
p_psi->i_version_number = ( p[5] >> 1 ) & 0x1f;
p_psi->i_last_section_number = (uint8_t)p[7];
/* We'll write at the begining of the buffer */
p_psi->p_current = p_psi->buffer;
}
else
{
if( p_psi->b_section_complete )
{
/* New Section of an already started PSI */
p_psi->b_section_complete = 0;
if( p_psi->i_version_number != (( p[5] >> 1 ) & 0x1f) )
{
msg_Warn( p_input,
"PSI version differs inside same PAT" );
p_psi->b_trash = 1;
}
if( p_psi->i_section_number + 1 != (uint8_t)p[6] )
{
msg_Warn( p_input,
"PSI Section discontinuity, packet lost?" );
p_psi->b_trash = 1;
}
else
p_psi->i_section_number++;
}
else
{
msg_Warn( p_input, "got unexpected new PSI section" );
p_psi->b_trash = 1;
}
}
}
} /* b_unit_start */
if( !p_psi->b_trash )
{
/* read */
if( (p_data->p_payload_end - p) >=
( p_psi->i_section_length - p_psi->i_read_in_section ) )
{
/* The end of the section is in this TS packet */
memcpy( p_psi->p_current, p,
(p_psi->i_section_length - p_psi->i_read_in_section) );
p_psi->b_section_complete = 1;
p_psi->p_current +=
(p_psi->i_section_length - p_psi->i_read_in_section);
if( p_psi->i_section_number == p_psi->i_last_section_number )
{
/* This was the last section of PSI */
p_psi->b_is_complete = 1;
switch( p_demux_data->i_psi_type)
{
case PSI_IS_PAT:
TSDecodePAT( p_input, p_es );
break;
case PSI_IS_PMT:
TSDecodePMT( p_input, p_es );
break;
default:
msg_Warn( p_input, "received unknown PSI in DemuxPSI" );
}
}
}
else
{
memcpy( p_psi->buffer, p, p_data->p_payload_end - p );
p_psi->i_read_in_section += p_data->p_payload_end - p;
p_psi->p_current += p_data->p_payload_end - p;
}
}
#undef p_psi
#undef p
input_DeletePacket( p_input->p_method_data, p_data );
return ;
}
/*****************************************************************************
* DecodePAT : Decodes Programm association table and deal with it
*****************************************************************************/
static void TSDecodePAT( input_thread_t * p_input, es_descriptor_t * p_es )
{
stream_ts_data_t * p_stream_data;
es_ts_data_t * p_demux_data;
pgrm_descriptor_t * p_pgrm;
es_descriptor_t * p_current_es;
byte_t * p_current_data;
int i_section_length, i_program_id, i_pmt_pid;
int i_loop, i_current_section;
vlc_bool_t b_changed = 0;
p_demux_data = (es_ts_data_t *)p_es->p_demux_data;
p_stream_data = (stream_ts_data_t *)p_input->stream.p_demux_data;
#define p_psi (p_demux_data->p_psi_section)
/* Not so fast, Mike ! If the PAT version has changed, we first check
* that its content has really changed before doing anything */
if( p_stream_data->i_pat_version != p_psi->i_version_number )
{
int i_programs = p_input->stream.i_pgrm_number;
p_current_data = p_psi->buffer;
do
{
i_section_length = ((uint32_t)(p_current_data[1] & 0xF) << 8) |
p_current_data[2];
i_current_section = (uint8_t)p_current_data[6];
for( i_loop = 0;
( i_loop < (i_section_length - 9) / 4 ) && !b_changed;
i_loop++ )
{
i_program_id = ( (uint32_t)*(p_current_data + i_loop * 4 + 8) << 8 )
| *(p_current_data + i_loop * 4 + 9);
i_pmt_pid = ( ((uint32_t)*(p_current_data + i_loop * 4 + 10) & 0x1F)
<< 8 )
| *(p_current_data + i_loop * 4 + 11);
if( i_program_id )
{
if( (p_pgrm = input_FindProgram( p_input, i_program_id ))
&& (p_current_es = input_FindES( p_input, i_pmt_pid ))
&& p_current_es->p_pgrm == p_pgrm
&& p_current_es->i_id == i_pmt_pid
&& ((es_ts_data_t *)p_current_es->p_demux_data)->b_psi
&& ((es_ts_data_t *)p_current_es->p_demux_data)
->i_psi_type == PSI_IS_PMT )
{
i_programs--;
}
else
{
b_changed = 1;
}
}
}
p_current_data += 3 + i_section_length;
} while( ( i_current_section < p_psi->i_last_section_number )
&& !b_changed );
/* If we didn't find the expected amount of programs, the PAT has
* changed. Otherwise, it only changed if b_changed is already != 0 */
b_changed = b_changed || i_programs;
}
if( b_changed )
{
/* PAT has changed. We are going to delete all programs and
* create new ones. We chose not to only change what was needed
* as a PAT change may mean the stream is radically changing and
* this is a secure method to avoid crashes */
es_ts_data_t * p_es_demux;
pgrm_ts_data_t * p_pgrm_demux;
p_current_data = p_psi->buffer;
/* Delete all programs */
while( p_input->stream.i_pgrm_number )
{
input_DelProgram( p_input, p_input->stream.pp_programs[0] );
}
do
{
i_section_length = ((uint32_t)(p_current_data[1] & 0xF) << 8) |
p_current_data[2];
i_current_section = (uint8_t)p_current_data[6];
for( i_loop = 0; i_loop < (i_section_length - 9) / 4 ; i_loop++ )
{
i_program_id = ( (uint32_t)*(p_current_data + i_loop * 4 + 8) << 8 )