/***************************************************************************** * linsys_sdi.c: SDI capture for Linear Systems/Computer Modules cards ***************************************************************************** * Copyright (C) 2009-2011 VideoLAN * * Authors: Christophe Massiot * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ #ifdef HAVE_CONFIG_H # include "config.h" #endif /***************************************************************************** * Preamble *****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "linsys_sdi.h" #undef ZVBI_DEBUG #include #define SDI_DEVICE "/dev/sdirx%u" #define SDI_BUFFERS_FILE "/sys/class/sdi/sdirx%u/buffers" #define SDI_BUFSIZE_FILE "/sys/class/sdi/sdirx%u/bufsize" #define SDI_MODE_FILE "/sys/class/sdi/sdirx%u/mode" #define READ_TIMEOUT 80000 #define RESYNC_TIMEOUT 500000 #define CLOCK_GAP INT64_C(500000) #define START_DATE INT64_C(4294967296) #define DEMUX_BUFFER_SIZE 1350000 #define MAX_AUDIOS 4 #define SAMPLERATE_TOLERANCE 0.1f /***************************************************************************** * Module descriptor *****************************************************************************/ #define LINK_TEXT N_("Link #") #define LINK_LONGTEXT N_( \ "Allows you to set the desired link of the board for the capture (starting at 0)." ) #define VIDEO_TEXT N_("Video ID") #define VIDEO_LONGTEXT N_( \ "Allows you to set the ES ID of the video." ) #define VIDEO_ASPECT_TEXT N_("Aspect ratio") #define VIDEO_ASPECT_LONGTEXT N_( \ "Allows you to force the aspect ratio of the video." ) #define AUDIO_TEXT N_("Audio configuration") #define AUDIO_LONGTEXT N_( \ "Allows you to set audio configuration (id=group,pair:id=group,pair...)." ) #define TELX_TEXT N_("Teletext configuration") #define TELX_LONGTEXT N_( \ "Allows you to set Teletext configuration (id=line1-lineN with both fields)." ) #define TELX_LANG_TEXT N_("Teletext language") #define TELX_LANG_LONGTEXT N_( \ "Allows you to set Teletext language (page=lang/type,...)." ) static int Open ( vlc_object_t * ); static void Close( vlc_object_t * ); static int DemuxOpen ( vlc_object_t * ); static void DemuxClose( vlc_object_t * ); vlc_module_begin() set_description( N_("SDI Input") ) set_shortname( N_("SDI") ) set_category( CAT_INPUT ) set_subcategory( SUBCAT_INPUT_ACCESS ) add_integer( "linsys-sdi-link", 0, LINK_TEXT, LINK_LONGTEXT, true ) add_integer( "linsys-sdi-id-video", 0, VIDEO_TEXT, VIDEO_LONGTEXT, true ) add_string( "linsys-sdi-aspect-ratio", "", VIDEO_ASPECT_TEXT, VIDEO_ASPECT_LONGTEXT, true ) add_string( "linsys-sdi-audio", "0=1,1", AUDIO_TEXT, AUDIO_LONGTEXT, true ) add_string( "linsys-sdi-telx", "", TELX_TEXT, TELX_LONGTEXT, true ) add_string( "linsys-sdi-telx-lang", "", TELX_LANG_TEXT, TELX_LANG_LONGTEXT, true ) set_capability( "access_demux", 0 ) add_shortcut( "linsys-sdi" ) set_callbacks( Open, Close ) add_submodule() set_description( N_("SDI Demux") ) set_capability( "demux", 0 ) set_callbacks( DemuxOpen, DemuxClose ) vlc_module_end() /***************************************************************************** * Local prototypes *****************************************************************************/ typedef struct sdi_audio_t { unsigned int i_group, i_pair; /* SDI parser */ int32_t i_delay; unsigned int i_rate; uint8_t i_block_number; int16_t *p_buffer; unsigned int i_left_samples, i_right_samples, i_nb_samples, i_max_samples; /* ES stuff */ int i_id; es_out_id_t *p_es; } sdi_audio_t; enum { STATE_NOSYNC, STATE_STARTSYNC, STATE_ANCSYNC, STATE_LINESYNC, STATE_ACTIVESYNC, STATE_VBLANKSYNC, STATE_PICSYNC, STATE_SYNC, }; struct demux_sys_t { /* device reader */ int i_fd; unsigned int i_link; uint8_t **pp_buffers; unsigned int i_buffers, i_current_buffer; unsigned int i_buffer_size; /* SDI sync */ int i_state; vlc_tick_t i_last_state_change; unsigned int i_anc_size, i_active_size, i_picture_size; unsigned int i_line_offset, i_nb_lines; /* SDI parser */ unsigned int i_line_buffer; unsigned int i_current_line; uint8_t *p_line_buffer; block_t *p_current_picture; uint8_t *p_y, *p_u, *p_v; uint8_t *p_wss_buffer; uint8_t *p_telx_buffer; /* picture decoding */ unsigned int i_frame_rate, i_frame_rate_base; unsigned int i_width, i_height, i_aspect, i_forced_aspect; unsigned int i_block_size; unsigned int i_telx_line, i_telx_count; char *psz_telx, *psz_telx_lang; bool b_hd, b_vbi; vbi_raw_decoder rd_wss, rd_telx; vlc_tick_t i_next_date; int i_incr; /* ES stuff */ int i_id_video; es_out_id_t *p_es_video; sdi_audio_t p_audios[MAX_AUDIOS]; es_out_id_t *p_es_telx; }; static int Control( demux_t *, int, va_list ); static int DemuxControl( demux_t *, int, va_list ); static int Demux( demux_t * ); static int DemuxDemux( demux_t * ); static int InitWSS( demux_t *p_demux ); static int InitTelx( demux_t *p_demux ); static int HandleSDBuffer( demux_t *p_demux, uint8_t *p_buffer, unsigned int i_buffer_size ); static int InitCapture( demux_t *p_demux ); static void CloseCapture( demux_t *p_demux ); static int Capture( demux_t *p_demux ); /***************************************************************************** * DemuxOpen: *****************************************************************************/ static int DemuxOpen( vlc_object_t *p_this ) { demux_t *p_demux = (demux_t *)p_this; demux_sys_t *p_sys; char *psz_parser; /* Fill p_demux field */ p_demux->pf_demux = DemuxDemux; p_demux->pf_control = DemuxControl; p_demux->p_sys = p_sys = calloc( 1, sizeof( demux_sys_t ) ); if( unlikely(!p_sys ) ) return VLC_ENOMEM; p_sys->i_state = STATE_NOSYNC; p_sys->i_last_state_change = mdate(); /* SDI AR */ unsigned int i_num, i_den; if ( !var_InheritURational( p_demux, &i_num, &i_den, "linsys-hdsdi-aspect-ratio" ) && i_den != 0 ) p_sys->i_forced_aspect = p_sys->i_aspect = i_num * VOUT_ASPECT_FACTOR / i_den; else p_sys->i_forced_aspect = 0; /* */ p_sys->i_id_video = var_InheritInteger( p_demux, "linsys-sdi-id-video" ); /* Audio ES */ char *psz_string = psz_parser = var_InheritString( p_demux, "linsys-sdi-audio" ); int i = 0; while ( psz_parser != NULL && *psz_parser ) { int i_id, i_group, i_pair; char *psz_next = strchr( psz_parser, '=' ); if ( psz_next != NULL ) { *psz_next = '\0'; i_id = strtol( psz_parser, NULL, 0 ); psz_parser = psz_next + 1; } else i_id = 0; psz_next = strchr( psz_parser, ':' ); if ( psz_next != NULL ) { *psz_next = '\0'; psz_next++; } if ( sscanf( psz_parser, "%d,%d", &i_group, &i_pair ) == 2 ) { p_sys->p_audios[i].i_group = i_group; p_sys->p_audios[i].i_pair = i_pair; p_sys->p_audios[i].i_id = i_id; i++; } else msg_Warn( p_demux, "malformed audio configuration (%s)", psz_parser ); psz_parser = psz_next; } free( psz_string ); /* Teletext ES */ p_sys->psz_telx = var_InheritString( p_demux, "linsys-sdi-telx" ); p_sys->psz_telx_lang = var_InheritString( p_demux, "linsys-sdi-telx-lang" ); return VLC_SUCCESS; } static int Open( vlc_object_t *p_this ) { demux_t *p_demux = (demux_t *)p_this; demux_sys_t *p_sys; int i_ret; if ( (i_ret = DemuxOpen( p_this )) != VLC_SUCCESS ) return i_ret; /* Fill p_demux field */ p_demux->pf_demux = Demux; p_demux->pf_control = Control; p_sys = p_demux->p_sys; p_sys->i_link = var_InheritInteger( p_demux, "linsys-sdi-link" ); if( InitCapture( p_demux ) != VLC_SUCCESS ) { free( p_sys ); return VLC_EGENERIC; } return VLC_SUCCESS; } /***************************************************************************** * DemuxClose: *****************************************************************************/ static void DemuxClose( vlc_object_t *p_this ) { demux_t *p_demux = (demux_t *)p_this; demux_sys_t *p_sys = p_demux->p_sys; free( p_sys->psz_telx ); free( p_sys->psz_telx_lang ); free( p_sys ); } static void Close( vlc_object_t *p_this ) { demux_t *p_demux = (demux_t *)p_this; CloseCapture( p_demux ); DemuxClose( p_this ); } /***************************************************************************** * DemuxDemux: *****************************************************************************/ static int DemuxDemux( demux_t *p_demux ) { block_t *p_block = vlc_stream_Block( p_demux->s, DEMUX_BUFFER_SIZE ); int i_ret; if ( p_block == NULL ) return 0; i_ret = HandleSDBuffer( p_demux, p_block->p_buffer, p_block->i_buffer ); block_Release( p_block ); return ( i_ret == VLC_SUCCESS ); } static int Demux( demux_t *p_demux ) { return ( Capture( p_demux ) == VLC_SUCCESS ); } /***************************************************************************** * Control: *****************************************************************************/ static int DemuxControl( demux_t *p_demux, int i_query, va_list args ) { return demux_vaControlHelper( p_demux->s, -1, -1, 270000000, 1, i_query, args ); } static int Control( demux_t *p_demux, int i_query, va_list args ) { bool *pb; int64_t *pi64; switch( i_query ) { /* Special for access_demux */ case DEMUX_CAN_PAUSE: case DEMUX_CAN_CONTROL_PACE: /* TODO */ pb = va_arg( args, bool * ); *pb = false; return VLC_SUCCESS; case DEMUX_GET_PTS_DELAY: pi64 = va_arg( args, int64_t * ); *pi64 = INT64_C(1000) * var_InheritInteger( p_demux, "live-caching" ); return VLC_SUCCESS; /* TODO implement others */ default: return VLC_EGENERIC; } } /***************************************************************************** * Video, audio & VBI decoding *****************************************************************************/ #define WSS_LINE 23 struct block_extension_t { bool b_progressive; /**< is it a progressive frame ? */ bool b_top_field_first; /**< which field is first */ unsigned int i_nb_fields; /**< # of displayed fields */ unsigned int i_aspect; /**< aspect ratio of frame */ }; static int NewFrame( demux_t *p_demux ) { demux_sys_t *p_sys = p_demux->p_sys; p_sys->p_current_picture = block_Alloc( p_sys->i_block_size ); if( unlikely( !p_sys->p_current_picture ) ) return VLC_ENOMEM; p_sys->p_y = p_sys->p_current_picture->p_buffer; p_sys->p_u = p_sys->p_y + p_sys->i_width * p_sys->i_height; p_sys->p_v = p_sys->p_u + p_sys->i_width * p_sys->i_height / 4; for ( int i = 0; i < MAX_AUDIOS; i++ ) { sdi_audio_t *p_audio = &p_sys->p_audios[i]; p_audio->i_left_samples = p_audio->i_right_samples = 0; } return VLC_SUCCESS; } static int StartDecode( demux_t *p_demux ) { demux_sys_t *p_sys = p_demux->p_sys; es_format_t fmt; char *psz_parser; p_sys->i_next_date = START_DATE; p_sys->i_incr = 1000000 * p_sys->i_frame_rate_base / p_sys->i_frame_rate; p_sys->i_block_size = p_sys->i_width * p_sys->i_height * 3 / 2 + sizeof(struct block_extension_t); if( NewFrame( p_demux ) != VLC_SUCCESS ) return VLC_ENOMEM; /* Video ES */ es_format_Init( &fmt, VIDEO_ES, VLC_CODEC_I420 ); fmt.i_id = p_sys->i_id_video; fmt.video.i_frame_rate = p_sys->i_frame_rate; fmt.video.i_frame_rate_base = p_sys->i_frame_rate_base; fmt.video.i_width = p_sys->i_width; fmt.video.i_height = p_sys->i_height; int i_aspect = p_sys->i_forced_aspect ? p_sys->i_forced_aspect : p_sys->i_aspect; fmt.video.i_sar_num = i_aspect * fmt.video.i_height / fmt.video.i_width; fmt.video.i_sar_den = VOUT_ASPECT_FACTOR; p_sys->p_es_video = es_out_Add( p_demux->out, &fmt ); if ( p_sys->b_vbi && InitWSS( p_demux ) != VLC_SUCCESS ) p_sys->b_vbi = 0; /* Teletext ES */ psz_parser = p_sys->psz_telx; if ( psz_parser != NULL && *psz_parser ) { if ( !p_sys->b_vbi ) { msg_Warn( p_demux, "VBI is unsupported on this input stream" ); } else { int i_id; char *psz_next = strchr( psz_parser, '=' ); if ( psz_next != NULL ) { *psz_next = '\0'; i_id = strtol( psz_parser, NULL, 0 ); psz_parser = psz_next + 1; } else i_id = 0; psz_next = strchr( psz_parser, '-' ); if ( psz_next != NULL ) *psz_next++ = '\0'; p_sys->i_telx_line = strtol( psz_parser, NULL, 0 ) - 1; if ( psz_next != NULL ) p_sys->i_telx_count = strtol( psz_next, NULL, 0 ) - p_sys->i_telx_line - 1 + 1; else p_sys->i_telx_count = 1; if ( InitTelx( p_demux ) == VLC_SUCCESS ) { int i_dr_size = 0; uint8_t *p_dr = NULL; msg_Dbg( p_demux, "capturing VBI lines %d-%d and %d-%d", p_sys->i_telx_line + 1, p_sys->i_telx_line + 1 + p_sys->i_telx_count - 1, p_sys->i_telx_line + 1 + 313, p_sys->i_telx_line + 1 + 313 + p_sys->i_telx_count - 1 ); es_format_Init( &fmt, SPU_ES, VLC_CODEC_TELETEXT ); fmt.i_id = i_id; /* Teletext language & type */ psz_parser = p_sys->psz_telx_lang; while ( (psz_next = strchr( psz_parser, '=' )) != NULL ) { int i_page; *psz_next++ = '\0'; if ( !psz_next[0] || !psz_next[1] || !psz_next[2] ) break; i_page = strtol( psz_parser, NULL, 0 ); i_dr_size += 5; p_dr = realloc( p_dr, i_dr_size ); p_dr[i_dr_size - 5] = *psz_next++; p_dr[i_dr_size - 4] = *psz_next++; p_dr[i_dr_size - 3] = *psz_next++; if ( *psz_next == '/' ) { psz_next++; p_dr[i_dr_size - 2] = strtol( psz_next, &psz_next, 0 ) << 3; } else /* subtitle for hearing impaired */ p_dr[i_dr_size - 2] = 0x5 << 3; p_dr[i_dr_size - 2] |= (i_page / 100) & 0x7; p_dr[i_dr_size - 1] = i_page % 100; if ( *psz_next == ',' ) psz_next++; psz_parser = psz_next; } fmt.i_extra = i_dr_size; fmt.p_extra = p_dr; p_sys->p_es_telx = es_out_Add( p_demux->out, &fmt ); } else p_sys->i_telx_count = 0; } } return VLC_SUCCESS; } static void StopDecode( demux_t *p_demux ) { demux_sys_t *p_sys = p_demux->p_sys; if ( p_sys->i_state != STATE_SYNC ) return; free( p_sys->p_line_buffer ); block_Release( p_sys->p_current_picture ); p_sys->p_current_picture = NULL; es_out_Del( p_demux->out, p_sys->p_es_video ); if ( p_sys->b_vbi ) { free( p_sys->p_wss_buffer ); p_sys->p_wss_buffer = NULL; vbi_raw_decoder_destroy( &p_sys->rd_wss ); if ( p_sys->p_es_telx ) { es_out_Del( p_demux->out, p_sys->p_es_telx ); free( p_sys->p_telx_buffer ); p_sys->p_telx_buffer = NULL; vbi_raw_decoder_destroy( &p_sys->rd_telx ); } } for ( int i = 0; i < MAX_AUDIOS; i++ ) { sdi_audio_t *p_audio = &p_sys->p_audios[i]; if ( p_audio->i_group && p_audio->p_es != NULL ) { es_out_Del( p_demux->out, p_audio->p_es ); p_audio->p_es = NULL; free( p_audio->p_buffer ); p_audio->p_buffer = NULL; } } } static void InitVideo( demux_t *p_demux ) { demux_sys_t *p_sys = p_demux->p_sys; int i_total_width = (p_sys->i_anc_size + p_sys->i_active_size) * 4 / 5; p_sys->i_width = (p_sys->i_active_size - 5) * 4 / 10; if ( p_sys->i_nb_lines == 625 ) { /* PAL */ p_sys->i_frame_rate = 25; p_sys->i_frame_rate_base = 1; p_sys->i_height = 576; p_sys->i_aspect = 4 * VOUT_ASPECT_FACTOR / 3; p_sys->b_hd = false; } else if ( p_sys->i_nb_lines == 525 ) { /* NTSC */ p_sys->i_frame_rate = 30000; p_sys->i_frame_rate_base = 1001; p_sys->i_height = 480; p_sys->i_aspect = 4 * VOUT_ASPECT_FACTOR / 3; p_sys->b_hd = false; } else if ( p_sys->i_nb_lines == 1125 && i_total_width == 2640 ) { /* 1080i50 or 1080p25 */ p_sys->i_frame_rate = 25; p_sys->i_frame_rate_base = 1; p_sys->i_height = 1080; p_sys->i_aspect = 16 * VOUT_ASPECT_FACTOR / 9; p_sys->b_hd = true; } else if ( p_sys->i_nb_lines == 1125 && i_total_width == 2200 ) { /* 1080i60 or 1080p30 */ p_sys->i_frame_rate = 30000; p_sys->i_frame_rate_base = 1001; p_sys->i_height = 1080; p_sys->i_aspect = 16 * VOUT_ASPECT_FACTOR / 9; p_sys->b_hd = true; } else if ( p_sys->i_nb_lines == 750 && i_total_width == 1980 ) { /* 720p50 */ p_sys->i_frame_rate = 50; p_sys->i_frame_rate_base = 1; p_sys->i_height = 720; p_sys->i_aspect = 16 * VOUT_ASPECT_FACTOR / 9; p_sys->b_hd = true; } else if ( p_sys->i_nb_lines == 750 && i_total_width == 1650 ) { /* 720p60 */ p_sys->i_frame_rate = 60000; p_sys->i_frame_rate_base = 1001; p_sys->i_height = 720; p_sys->i_aspect = 16 * VOUT_ASPECT_FACTOR / 9; p_sys->b_hd = true; } else { msg_Warn( p_demux, "unable to determine video type" ); /* Put sensitive defaults */ p_sys->i_frame_rate = 25; p_sys->i_frame_rate_base = 1; p_sys->i_height = p_sys->i_nb_lines; p_sys->i_aspect = 16 * VOUT_ASPECT_FACTOR / 9; p_sys->b_hd = true; } p_sys->b_vbi = !p_sys->b_hd; } static void DecodeVideo( demux_t *p_demux ) { demux_sys_t *p_sys = p_demux->p_sys; struct block_extension_t ext; /* FIXME: progressive formats ? */ ext.b_progressive = false; ext.i_nb_fields = 2; ext.b_top_field_first = true; ext.i_aspect = p_sys->i_forced_aspect ? p_sys->i_forced_aspect : p_sys->i_aspect; memcpy( &p_sys->p_current_picture->p_buffer[p_sys->i_block_size - sizeof(struct block_extension_t)], &ext, sizeof(struct block_extension_t) ); p_sys->p_current_picture->i_dts = p_sys->p_current_picture->i_pts = p_sys->i_next_date; es_out_Send( p_demux->out, p_sys->p_es_video, p_sys->p_current_picture ); } static int InitWSS( demux_t *p_demux ) { demux_sys_t *p_sys = p_demux->p_sys; vbi_raw_decoder_init( &p_sys->rd_wss ); p_sys->rd_wss.scanning = 625; p_sys->rd_wss.sampling_format = VBI_PIXFMT_UYVY; p_sys->rd_wss.sampling_rate = 13.5e6; p_sys->rd_wss.bytes_per_line = 720 * 2; p_sys->rd_wss.offset = 9.5e-6 * 13.5e6; p_sys->rd_wss.start[0] = 23; p_sys->rd_wss.count[0] = 1; p_sys->rd_wss.start[1] = 0; p_sys->rd_wss.count[1] = 0; p_sys->rd_wss.interlaced = FALSE; p_sys->rd_wss.synchronous = TRUE; if ( vbi_raw_decoder_add_services( &p_sys->rd_wss, VBI_SLICED_WSS_625, /* strict */ 2 ) == 0 ) { msg_Warn( p_demux, "cannot initialize zvbi for WSS" ); vbi_raw_decoder_destroy ( &p_sys->rd_telx ); return VLC_EGENERIC; } p_sys->p_wss_buffer = malloc( p_sys->i_width * 2 ); if( !p_sys->p_wss_buffer ) { vbi_raw_decoder_destroy ( &p_sys->rd_telx ); return VLC_ENOMEM; } return VLC_SUCCESS; } static void DecodeWSS( demux_t *p_demux ) { demux_sys_t *p_sys = p_demux->p_sys; vbi_sliced p_sliced[1]; if ( vbi_raw_decode( &p_sys->rd_wss, p_sys->p_wss_buffer, p_sliced ) == 0 ) { p_sys->i_aspect = 4 * VOUT_ASPECT_FACTOR / 3; } else { unsigned int i_old_aspect = p_sys->i_aspect; uint8_t *p = p_sliced[0].data; int i_aspect, i_parity; i_aspect = p[0] & 15; i_parity = i_aspect; i_parity ^= i_parity >> 2; i_parity ^= i_parity >> 1; i_aspect &= 7; if ( !(i_parity & 1) ) msg_Warn( p_demux, "WSS parity error" ); else if ( i_aspect == 7 ) p_sys->i_aspect = 16 * VOUT_ASPECT_FACTOR / 9; else p_sys->i_aspect = 4 * VOUT_ASPECT_FACTOR / 3; if ( p_sys->i_aspect != i_old_aspect ) msg_Dbg( p_demux, "new WSS information (ra=%x md=%x cod=%x hlp=%x rvd=%x sub=%x pos=%x srd=%x c=%x cp=%x)", i_aspect, (p[0] & 0x10) >> 4, (p[0] & 0x20) >> 5, (p[0] & 0x40) >> 6, (p[0] & 0x80) >> 7, p[1] & 0x01, (p[1] >> 1) & 3, (p[1] & 0x08) >> 3, (p[1] & 0x10) >> 4, (p[1] & 0x20) >> 5 ); } } static int InitTelx( demux_t *p_demux ) { demux_sys_t *p_sys = p_demux->p_sys; vbi_raw_decoder_init( &p_sys->rd_telx ); p_sys->rd_telx.scanning = 625; p_sys->rd_telx.sampling_format = VBI_PIXFMT_UYVY; p_sys->rd_telx.sampling_rate = 13.5e6; p_sys->rd_telx.bytes_per_line = 720 * 2; p_sys->rd_telx.offset = 9.5e-6 * 13.5e6; p_sys->rd_telx.start[0] = p_sys->i_telx_line + 1; p_sys->rd_telx.count[0] = p_sys->i_telx_count; p_sys->rd_telx.start[1] = p_sys->i_telx_line + 1 + 313; p_sys->rd_telx.count[1] = p_sys->i_telx_count; p_sys->rd_telx.interlaced = FALSE; p_sys->rd_telx.synchronous = TRUE; if ( vbi_raw_decoder_add_services( &p_sys->rd_telx, VBI_SLICED_TELETEXT_B, 0 ) == 0 ) { msg_Warn( p_demux, "cannot initialize zvbi for Teletext" ); vbi_raw_decoder_destroy ( &p_sys->rd_telx ); return VLC_EGENERIC; } p_sys->p_telx_buffer = vlc_alloc( p_sys->i_telx_count * p_sys->i_width, 4 ); if( !p_sys->p_telx_buffer ) { vbi_raw_decoder_destroy ( &p_sys->rd_telx ); return VLC_ENOMEM; } return VLC_SUCCESS; } static int DecodeTelx( demux_t *p_demux ) { demux_sys_t *p_sys = p_demux->p_sys; vbi_sliced p_sliced[p_sys->i_telx_count * 2]; int i_nb_slices = vbi_raw_decode( &p_sys->rd_telx, p_sys->p_telx_buffer, p_sliced ); if ( i_nb_slices ) { /* 3, 7, 11, 15, etc. */ int i_nb_slices_rounded = 3 + (i_nb_slices / 4) * 4; int i; uint8_t *p; block_t *p_block = block_Alloc( 1 + i_nb_slices_rounded * 46 ); if( unlikely( !p_block ) ) return VLC_ENOMEM; p_block->p_buffer[0] = 0x10; /* FIXME ? data_identifier */ p = p_block->p_buffer + 1; for ( i = 0; i < i_nb_slices; i++ ) { int i_line = p_sliced[i].line; p[0] = 0x3; /* FIXME data_unit_id == subtitles */ p[1] = 0x2c; /* data_unit_length */ /* reserved | field_parity (kind of inverted) | line */ p[2] = 0xc0 | (i_line > 313 ? 0 : 0x20) | (i_line % 313); p[3] = 0xe4; /* framing code */ for ( int j = 0; j < 42; j++ ) p[4 + j] = vbi_rev8( p_sliced[i].data[j] ); p += 46; } /* Let's stuff */ for ( ; i < i_nb_slices_rounded; i++ ) { p[0] = 0xff; p[1] = 0x2c; memset( p + 2, 0xff, 44 ); p += 46; } p_block->i_dts = p_block->i_pts = p_sys->i_next_date; es_out_Send( p_demux->out, p_sys->p_es_telx, p_block ); } return VLC_SUCCESS; } static int InitAudio( demux_t *p_demux, sdi_audio_t *p_audio ) { demux_sys_t *p_sys = p_demux->p_sys; es_format_t fmt; msg_Dbg( p_demux, "starting audio %u/%u rate:%u delay:%d", p_audio->i_group, p_audio->i_pair, p_audio->i_rate, p_audio->i_delay ); es_format_Init( &fmt, AUDIO_ES, VLC_CODEC_S16L ); fmt.i_id = p_audio->i_id; fmt.audio.i_channels = 2; fmt.audio.i_physical_channels = AOUT_CHANS_STEREO; fmt.audio.i_rate = p_audio->i_rate; fmt.audio.i_bitspersample = 16; fmt.audio.i_blockalign = fmt.audio.i_channels * fmt.audio.i_bitspersample / 8; fmt.i_bitrate = fmt.audio.i_channels * fmt.audio.i_rate * fmt.audio.i_bitspersample; p_audio->p_es = es_out_Add( p_demux->out, &fmt ); p_audio->i_nb_samples = p_audio->i_rate * p_sys->i_frame_rate_base / p_sys->i_frame_rate; p_audio->i_max_samples = (float)p_audio->i_nb_samples * (1.f + SAMPLERATE_TOLERANCE); p_audio->p_buffer = vlc_alloc( p_audio->i_max_samples, sizeof(int16_t) * 2 ); p_audio->i_left_samples = p_audio->i_right_samples = 0; p_audio->i_block_number = 0; if( unlikely( !p_audio->p_buffer ) ) return VLC_ENOMEM; return VLC_SUCCESS; } /* Fast and efficient linear resampling routine */ static void ResampleAudio( int16_t *p_out, int16_t *p_in, unsigned int i_out, unsigned int i_in ) { unsigned int i_remainder = 0; float f_last_sample = (float)*p_in / 32768.f; *p_out = *p_in; p_out += 2; p_in += 2; for ( unsigned int i = 1; i < i_in; i++ ) { float f_in = (float)*p_in / 32768.f; while ( i_remainder < i_out ) { float f_out = f_last_sample; f_out += (f_in - f_last_sample) * i_remainder / i_out; if ( f_out >= 1.f ) *p_out = 32767; else if ( f_out < -1.f ) *p_out = -32768; else *p_out = f_out * 32768.f; p_out += 2; i_remainder += i_in; } f_last_sample = f_in; p_in += 2; i_remainder -= i_out; } } static int DecodeAudio( demux_t *p_demux, sdi_audio_t *p_audio ) { demux_sys_t *p_sys = p_demux->p_sys; block_t *p_block; int16_t *p_output; if ( p_audio->p_buffer == NULL ) return VLC_EGENERIC; if ( !p_audio->i_left_samples && !p_audio->i_right_samples ) { msg_Warn( p_demux, "no audio %u/%u", p_audio->i_group, p_audio->i_pair ); return VLC_EGENERIC; } if ( p_audio->i_left_samples < (float)p_audio->i_nb_samples * (1.f - SAMPLERATE_TOLERANCE) || p_audio->i_left_samples > (float)p_audio->i_nb_samples * (1.f + SAMPLERATE_TOLERANCE) ) { msg_Warn( p_demux, "left samplerate out of tolerance for audio %u/%u (%u vs. %u)", p_audio->i_group, p_audio->i_pair, p_audio->i_left_samples, p_audio->i_nb_samples ); return VLC_EGENERIC; } if ( p_audio->i_right_samples < (float)p_audio->i_nb_samples * (1.f - SAMPLERATE_TOLERANCE) || p_audio->i_right_samples > (float)p_audio->i_nb_samples * (1.f + SAMPLERATE_TOLERANCE) ) { msg_Warn( p_demux, "right samplerate out of tolerance for audio %u/%u (%u vs. %u)", p_audio->i_group, p_audio->i_pair, p_audio->i_right_samples, p_audio->i_nb_samples ); return VLC_EGENERIC; } p_block = block_Alloc( p_audio->i_nb_samples * sizeof(int16_t) * 2 ); if( unlikely( !p_block ) ) return VLC_ENOMEM; p_block->i_dts = p_block->i_pts = p_sys->i_next_date + (vlc_tick_t)p_audio->i_delay * INT64_C(1000000) / p_audio->i_rate; p_output = (int16_t *)p_block->p_buffer; if ( p_audio->i_left_samples == p_audio->i_nb_samples && p_audio->i_right_samples == p_audio->i_nb_samples ) memcpy( p_output, p_audio->p_buffer, p_audio->i_nb_samples * sizeof(int16_t) * 2 ); else { ResampleAudio( p_output, p_audio->p_buffer, p_audio->i_nb_samples, p_audio->i_left_samples ); ResampleAudio( p_output + 1, p_audio->p_buffer + 1, p_audio->i_nb_samples, p_audio->i_right_samples ); } es_out_Send( p_demux->out, p_audio->p_es, p_block ); return VLC_SUCCESS; } static int DecodeFrame( demux_t *p_demux ) { demux_sys_t *p_sys = p_demux->p_sys; if ( p_sys->b_vbi ) { DecodeWSS( p_demux ); if ( p_sys->i_height == 576 ) { /* For PAL, erase first half of line 23, last half of line 623, * and line 624 ; no need to erase chrominance */ memset( p_sys->p_y, 0, p_sys->i_width / 2 ); memset( p_sys->p_y + p_sys->i_width * 574 + p_sys->i_width / 2, 0, p_sys->i_width * 3 / 2 ); } } if ( p_sys->i_telx_count ) if ( DecodeTelx( p_demux ) != VLC_SUCCESS ) return VLC_ENOMEM; for ( int i = 0; i < MAX_AUDIOS; i++ ) { if ( p_sys->p_audios[i].i_group && p_sys->p_audios[i].p_es != NULL ) if( DecodeAudio( p_demux, &p_sys->p_audios[i] ) != VLC_SUCCESS ) return VLC_EGENERIC; } DecodeVideo( p_demux ); es_out_SetPCR( p_demux->out, p_sys->i_next_date ); p_sys->i_next_date += p_sys->i_incr; if( NewFrame( p_demux ) != VLC_SUCCESS ) return VLC_ENOMEM; return VLC_SUCCESS; } /***************************************************************************** * SDI syntax parsing stuff *****************************************************************************/ #define FIELD_1_VBLANK_EAV 0xB6 #define FIELD_1_VBLANK_SAV 0xAB #define FIELD_1_ACTIVE_EAV 0x9D #define FIELD_1_ACTIVE_SAV 0x80 #define FIELD_2_VBLANK_EAV 0xF1 #define FIELD_2_VBLANK_SAV 0xEC #define FIELD_2_ACTIVE_EAV 0xDA #define FIELD_2_ACTIVE_SAV 0xC7 static const uint8_t *FindReferenceCode( uint8_t i_code, const uint8_t *p_parser, const uint8_t *p_end ) { while ( p_parser <= p_end - 5 ) { if ( p_parser[0] == 0xff && p_parser[1] == 0x3 && p_parser[2] == 0x0 && p_parser[3] == 0x0 && p_parser[4] == i_code ) return p_parser; p_parser += 5; } return NULL; } static const uint8_t *CountReference( unsigned int *pi_count, uint8_t i_code, const uint8_t *p_parser, const uint8_t *p_end ) { const uint8_t *p_tmp = FindReferenceCode( i_code, p_parser, p_end ); if ( p_tmp == NULL ) { *pi_count += p_end - p_parser; return NULL; } *pi_count += p_tmp - p_parser; return p_tmp; } static const uint8_t *GetLine( demux_t *p_demux, const uint8_t **pp_parser, const uint8_t *p_end ) { demux_sys_t *p_sys = p_demux->p_sys; unsigned int i_total_size = p_sys->i_anc_size + p_sys->i_active_size; const uint8_t *p_tmp; if ( p_sys->i_line_buffer ) { unsigned int i_remaining = i_total_size - p_sys->i_line_buffer; memcpy( p_sys->p_line_buffer + p_sys->i_line_buffer, *pp_parser, i_remaining ); *pp_parser += i_remaining; p_sys->i_line_buffer = 0; return p_sys->p_line_buffer; } if ( p_end - *pp_parser < (int)i_total_size ) { memcpy( p_sys->p_line_buffer, *pp_parser, p_end - *pp_parser ); p_sys->i_line_buffer = p_end - *pp_parser; return NULL; } p_tmp = *pp_parser; *pp_parser += i_total_size; return p_tmp; } #define U (uint16_t)((p_line[0]) | ((p_line[1] & 0x3) << 8)) #define Y1 (uint16_t)((p_line[1] >> 2) | ((p_line[2] & 0xf) << 6)) #define V (uint16_t)((p_line[2] >> 4) | ((p_line[3] & 0x3f) << 4)) #define Y2 (uint16_t)((p_line[3] >> 6) | (p_line[4] << 2)) static void UnpackVBI( const uint8_t *p_line, unsigned int i_size, uint8_t *p_dest ) { const uint8_t *p_end = p_line + i_size; while ( p_line < p_end ) { *p_dest++ = (U + 2) / 4; *p_dest++ = (Y1 + 2) / 4; *p_dest++ = (V + 2) / 4; *p_dest++ = (Y2 + 2) / 4; p_line += 5; } } /* For lines 0 [4] or 1 [4] */ static void Unpack01( const uint8_t *p_line, unsigned int i_size, uint8_t *p_y, uint8_t *p_u, uint8_t *p_v ) { const uint8_t *p_end = p_line + i_size; while ( p_line < p_end ) { *p_u++ = (U + 2) / 4; *p_y++ = (Y1 + 2) / 4; *p_v++ = (V + 2) / 4; *p_y++ = (Y2 + 2) / 4; p_line += 5; } } /* For lines 2 [4] */ static void Unpack2( const uint8_t *p_line, unsigned int i_size, uint8_t *p_y, uint8_t *p_u, uint8_t *p_v ) { const uint8_t *p_end = p_line + i_size; while ( p_line < p_end ) { uint16_t tmp; tmp = 3 * *p_u; tmp += (U + 2) / 4; *p_u++ = tmp / 4; *p_y++ = (Y1 + 2) / 4; tmp = 3 * *p_v; tmp += (V + 2) / 4; *p_v++ = tmp / 4; *p_y++ = (Y2 + 2) / 4; p_line += 5; } } /* For lines 3 [4] */ static void Unpack3( const uint8_t *p_line, unsigned int i_size, uint8_t *p_y, uint8_t *p_u, uint8_t *p_v ) { const uint8_t *p_end = p_line + i_size; while ( p_line < p_end ) { uint16_t tmp; tmp = *p_u; tmp += 3 * (U + 2) / 4; *p_u++ = tmp / 4; *p_y++ = (Y1 + 2) / 4; tmp = *p_v; tmp += 3 * (V + 2) / 4; *p_v++ = tmp / 4; *p_y++ = (Y2 + 2) / 4; p_line += 5; } } #undef U #undef Y1 #undef V #undef Y2 #define A0 (uint16_t)((p_anc[0]) | ((p_anc[1] & 0x3) << 8)) #define A1 (uint16_t)((p_anc[1] >> 2) | ((p_anc[2] & 0xf) << 6)) #define A2 (uint16_t)((p_anc[2] >> 4) | ((p_anc[3] & 0x3f) << 4)) #define A3 (uint16_t)((p_anc[3] >> 6) | (p_anc[4] << 2)) static void UnpackAnc( const uint8_t *p_anc, unsigned int i_size, uint16_t *p_dest ) { const uint8_t *p_end = p_anc + i_size; while ( p_anc <= p_end - 5 ) { *p_dest++ = A0; *p_dest++ = A1; *p_dest++ = A2; *p_dest++ = A3; p_anc += 5; } } #undef A0 #undef A1 #undef A2 #undef A3 static int HasAncillary( const uint8_t *p_anc ) { return ( (p_anc[0] == 0x0 && p_anc[1] == 0xfc && p_anc[2] == 0xff && (p_anc[3] & 0x3f) == 0x3f) ); } static void HandleAudioData( demux_t *p_demux, const uint16_t *p_anc, uint8_t i_data_count, uint8_t i_group, uint8_t i_block_number ) { demux_sys_t *p_sys = p_demux->p_sys; if ( i_data_count % 3 ) { msg_Warn( p_demux, "malformed audio data for group %u", i_group ); return; } for ( int i = 0; i < MAX_AUDIOS; i++ ) { sdi_audio_t *p_audio = &p_sys->p_audios[i]; if ( p_audio->i_group == i_group ) { const uint16_t *x = p_anc; /* SMPTE 272M says that when parsing a frame, if an audio config * structure is present we will encounter it first. Otherwise * it is assumed to be 48 kHz. */ if ( p_audio->p_es == NULL ) { p_audio->i_rate = 48000; p_audio->i_delay = 0; if( InitAudio( p_demux, p_audio ) != VLC_SUCCESS ) return; } if ( i_block_number ) { if ( p_audio->i_block_number + 1 != i_block_number ) msg_Warn( p_demux, "audio data block discontinuity (%"PRIu8"->%"PRIu8") for group %"PRIu8, p_audio->i_block_number, i_block_number, i_group ); if ( i_block_number == 0xff ) p_audio->i_block_number = 0; else p_audio->i_block_number = i_block_number; } while ( x < p_anc + i_data_count ) { if ( ((*x & 0x4) && p_audio->i_pair == 2) || (!(*x & 0x4) && p_audio->i_pair == 1) ) { uint32_t i_tmp = (uint32_t)((x[0] & 0x1f1) >> 3) | ((x[1] & 0x1ff) << 6) | ((x[2] & 0x1f) << 15); int32_t i_sample; if ( x[2] & 0x10 ) i_sample = i_tmp | 0xfff00000; else i_sample = i_tmp; if ( x[0] & 0x2 ) { if ( p_audio->i_right_samples < p_audio->i_max_samples ) p_audio->p_buffer[2 * p_audio->i_right_samples + 1] = (i_sample + 8) / 16; p_audio->i_right_samples++; } else { if ( p_audio->i_left_samples < p_audio->i_max_samples ) p_audio->p_buffer[2 * p_audio->i_left_samples] = (i_sample + 8) / 16; p_audio->i_left_samples++; } } x += 3; } } } } static void HandleAudioConfig( demux_t *p_demux, const uint16_t *p_anc, uint8_t i_data_count, uint8_t i_group ) { demux_sys_t *p_sys = p_demux->p_sys; if ( i_data_count != 18 ) { msg_Warn( p_demux, "malformed audio config for group %u", i_group ); return; } for ( int i = 0; i < MAX_AUDIOS; i++ ) { sdi_audio_t *p_audio = &p_sys->p_audios[i]; if ( p_audio->i_group == i_group && p_audio->p_es == NULL ) { unsigned int i_rate; if ( p_audio->i_pair == 2 ) { i_rate = (p_anc[2] & 0xe0) >> 5; if ( p_anc[7] & 0x1 ) { uint32_t i_tmp = ((p_anc[7] & 0x1fe) >> 1) | ((p_anc[8] & 0x1ff) << 8) | ((p_anc[9] & 0x1ff) << 17); if ( p_anc[9] & 0x80 ) p_audio->i_delay = i_tmp | 0xfc000000; else p_audio->i_delay = i_tmp; } if ( p_anc[13] & 0x1 ) msg_Warn( p_demux, "asymmetric audio is not supported" ); } else { i_rate = (p_anc[2] & 0xe) >> 1; if ( p_anc[4] & 0x1 ) { uint32_t i_tmp = ((p_anc[4] & 0x1fe) >> 1) | ((p_anc[5] & 0x1ff) << 8) | ((p_anc[6] & 0x1ff) << 17); if ( p_anc[6] & 0x80 ) p_audio->i_delay = i_tmp | 0xfc000000; else p_audio->i_delay = i_tmp; } if ( p_anc[10] & 0x1 ) msg_Warn( p_demux, "asymmetric audio is not supported" ); } switch ( i_rate ) { case 0: p_audio->i_rate = 48000; break; case 1: p_audio->i_rate = 44100; break; case 2: p_audio->i_rate = 32000; break; default: msg_Warn( p_demux, "unknown rate for audio %u/%u (%u)", i_group, p_sys->p_audios[i].i_pair, i_rate ); continue; } if( InitAudio( p_demux, p_audio ) != VLC_SUCCESS ) return; } } } /* * Ancillary packet structure: * byte 0: Ancillary Data Flag (0) * byte 1: Ancillary Data Flag (0x3ff) * byte 2: Ancillary Data Flag (0x3ff) * byte 3: Data ID (2 high order bits = parity) * byte 4: Data Block Number 1-255 or 0=unknown (if DID < 0x80) * or Secondary Data ID (if DID >= 0x80) * byte 5: Data Count (10 bits) * byte 6+DC: Checksum */ static void HandleAncillary( demux_t *p_demux, const uint16_t *p_anc, unsigned int i_size ) { uint8_t i_data_count; if ( i_size < 7 || p_anc[0] != 0x0 || p_anc[1] != 0x3ff || p_anc[2] != 0x3ff ) return; i_data_count = p_anc[5] & 0xff; if ( i_size - 6 < i_data_count ) { msg_Warn( p_demux, "malformed ancillary packet (size %u > %u)", i_data_count, i_size - 6 ); return; } switch ( p_anc[3] ) /* Data ID */ { case 0x2ff: HandleAudioData( p_demux, p_anc + 6, i_data_count, 1, p_anc[4] & 0xff ); break; case 0x1fd: HandleAudioData( p_demux, p_anc + 6, i_data_count, 2, p_anc[4] & 0xff ); break; case 0x1fb: HandleAudioData( p_demux, p_anc + 6, i_data_count, 3, p_anc[4] & 0xff ); break; case 0x2f9: HandleAudioData( p_demux, p_anc + 6, i_data_count, 4, p_anc[4] & 0xff ); break; case 0x1ef: HandleAudioConfig( p_demux, p_anc + 6, i_data_count, 1 ); break; case 0x2ee: HandleAudioConfig( p_demux, p_anc + 6, i_data_count, 2 ); break; case 0x2ed: HandleAudioConfig( p_demux, p_anc + 6, i_data_count, 3 ); break; case 0x1ec: HandleAudioConfig( p_demux, p_anc + 6, i_data_count, 4 ); break; /* Extended data packets, same order */ case 0x1fe: case 0x2fc: case 0x2fa: case 0x1f8: default: break; case 0x88: /* non-conforming ANC packet */ p_anc += 7; i_size -= 7; while ( i_size >= 7 && (p_anc[0] != 0x0 || p_anc[1] != 0x3ff || p_anc[2] != 0x3ff) ) { p_anc++; i_size--; } if ( i_size >= 7 ) HandleAncillary( p_demux, p_anc, i_size ); return; } return HandleAncillary( p_demux, p_anc + i_data_count + 7, i_size - i_data_count - 7 ); } static int HandleSDBuffer( demux_t *p_demux, uint8_t *p_buffer, unsigned int i_buffer_size ) { demux_sys_t *p_sys = p_demux->p_sys; const uint8_t *p_parser = p_buffer; const uint8_t *p_end = p_parser + i_buffer_size; const uint8_t *p_line; if ( p_sys->i_state != STATE_SYNC && p_sys->i_last_state_change < mdate() - RESYNC_TIMEOUT ) { p_sys->i_state = STATE_NOSYNC; p_sys->i_last_state_change = mdate(); return VLC_EGENERIC; } switch ( p_sys->i_state ) { case STATE_NOSYNC: default: p_parser = FindReferenceCode( FIELD_2_VBLANK_SAV, p_parser, p_end ); if ( p_parser == NULL ) break; p_sys->i_state = STATE_STARTSYNC; p_sys->i_last_state_change = mdate(); case STATE_STARTSYNC: p_parser = FindReferenceCode( FIELD_1_VBLANK_EAV, p_parser, p_end ); if ( p_parser == NULL ) break; p_sys->i_anc_size = 0; p_sys->i_state = STATE_ANCSYNC; p_sys->i_last_state_change = mdate(); case STATE_ANCSYNC: p_parser = CountReference( &p_sys->i_anc_size, FIELD_1_VBLANK_SAV, p_parser, p_end ); if ( p_parser == NULL ) break; p_sys->i_active_size = 0; p_sys->i_state = STATE_LINESYNC; p_sys->i_last_state_change = mdate(); case STATE_LINESYNC: p_parser = CountReference( &p_sys->i_active_size, FIELD_1_VBLANK_EAV, p_parser, p_end ); if ( p_parser == NULL ) break; p_sys->i_picture_size = p_sys->i_anc_size + p_sys->i_active_size; p_sys->i_state = STATE_ACTIVESYNC; p_sys->i_last_state_change = mdate(); case STATE_ACTIVESYNC: p_parser = CountReference( &p_sys->i_picture_size, FIELD_1_ACTIVE_EAV, p_parser, p_end ); if ( p_parser == NULL ) break; p_sys->i_line_offset = p_sys->i_picture_size / (p_sys->i_anc_size + p_sys->i_active_size); p_sys->i_state = STATE_VBLANKSYNC; p_sys->i_last_state_change = mdate(); case STATE_VBLANKSYNC: p_parser = CountReference( &p_sys->i_picture_size, FIELD_2_ACTIVE_EAV, p_parser, p_end ); if ( p_parser == NULL ) break; p_sys->i_state = STATE_PICSYNC; p_sys->i_last_state_change = mdate(); case STATE_PICSYNC: p_parser = CountReference( &p_sys->i_picture_size, FIELD_1_VBLANK_EAV, p_parser, p_end ); if ( p_parser == NULL ) break; if ( p_sys->i_picture_size % (p_sys->i_anc_size + p_sys->i_active_size) ) { msg_Warn( p_demux, "wrong picture size (anc=%d active=%d total=%d offset=%d), syncing", p_sys->i_anc_size, p_sys->i_active_size, p_sys->i_picture_size, p_sys->i_line_offset + 1 ); p_sys->i_state = STATE_NOSYNC; p_sys->i_last_state_change = mdate(); break; } p_sys->i_nb_lines = p_sys->i_picture_size / (p_sys->i_anc_size + p_sys->i_active_size); InitVideo( p_demux ); msg_Dbg( p_demux, "acquired sync, anc=%d active=%d lines=%d offset=%d", p_sys->i_anc_size, p_sys->i_active_size, p_sys->i_nb_lines, p_sys->i_line_offset + 1 ); p_sys->i_state = STATE_SYNC; if( StartDecode( p_demux ) != VLC_SUCCESS ) { StopDecode( p_demux ); return VLC_ENOMEM; } p_sys->i_current_line = 0; p_sys->p_line_buffer = malloc( p_sys->i_anc_size + p_sys->i_active_size ); if( !p_sys->p_line_buffer ) { StopDecode( p_demux ); return VLC_ENOMEM; } p_sys->i_line_buffer = 0; case STATE_SYNC: while ( (p_line = GetLine( p_demux, &p_parser, p_end )) != NULL ) { bool b_field = p_sys->b_hd ? false : (p_sys->i_current_line >= p_sys->i_nb_lines / 2); unsigned int i_field_height = p_sys->b_hd ? p_sys->i_height : p_sys->i_height / 2; unsigned int i_field_line = b_field ? p_sys->i_current_line - (p_sys->i_nb_lines + 1) / 2 : p_sys->i_current_line; bool b_vbi = i_field_line < p_sys->i_line_offset || i_field_line >= p_sys->i_line_offset + i_field_height; unsigned int anc = p_sys->i_anc_size; if ( p_line[0] != 0xff || p_line[1] != 0x3 || p_line[2] != 0x0 || p_line[3] != 0x0 || p_line[anc+0] != 0xff || p_line[anc+1] != 0x3 || p_line[anc+2] != 0x0 || p_line[anc+3] != 0x0 || (!b_field && b_vbi && (p_line[4] != FIELD_1_VBLANK_EAV || p_line[anc+4] != FIELD_1_VBLANK_SAV)) || (!b_field && !b_vbi && (p_line[4] != FIELD_1_ACTIVE_EAV || p_line[anc+4] != FIELD_1_ACTIVE_SAV)) || (b_field && b_vbi && (p_line[4] != FIELD_2_VBLANK_EAV || p_line[anc+4] != FIELD_2_VBLANK_SAV)) || (b_field && !b_vbi && (p_line[4] != FIELD_2_ACTIVE_EAV || p_line[anc+4] != FIELD_2_ACTIVE_SAV)) ) { msg_Warn( p_demux, "lost sync line:%u SAV:%x EAV:%x", p_sys->i_current_line + 1, p_line[4], p_line[anc+4] ); StopDecode( p_demux ); p_sys->i_state = STATE_NOSYNC; p_sys->i_last_state_change = mdate(); break; } if ( HasAncillary( p_line + 5 ) ) { /* HANC */ unsigned int i_anc_words = (p_sys->i_anc_size - 5) * 4 / 5; uint16_t p_anc[i_anc_words]; UnpackAnc( p_line + 5, p_sys->i_anc_size - 5, p_anc ); HandleAncillary( p_demux, p_anc, i_anc_words ); } if ( !b_vbi ) { unsigned int i_active_field_line = i_field_line - p_sys->i_line_offset; unsigned int i_active_line = b_field + i_active_field_line * 2; if ( !(i_active_field_line % 2) && !b_field ) Unpack01( p_line + anc + 5, p_sys->i_active_size - 5, p_sys->p_y + p_sys->i_width * i_active_line, p_sys->p_u + (p_sys->i_width / 2) * (i_active_line / 2), p_sys->p_v + (p_sys->i_width / 2) * (i_active_line / 2) ); else if ( !(i_active_field_line % 2) ) Unpack01( p_line + anc + 5, p_sys->i_active_size - 5, p_sys->p_y + p_sys->i_width * i_active_line, p_sys->p_u + (p_sys->i_width / 2) * (i_active_line / 2 + 1), p_sys->p_v + (p_sys->i_width / 2) * (i_active_line / 2 + 1) ); else if ( !b_field ) Unpack2( p_line + anc + 5, p_sys->i_active_size - 5, p_sys->p_y + p_sys->i_width * i_active_line, p_sys->p_u + (p_sys->i_width / 2) * (i_active_line / 2 - 1), p_sys->p_v + (p_sys->i_width / 2) * (i_active_line / 2 - 1) ); else Unpack3( p_line + anc + 5, p_sys->i_active_size - 5, p_sys->p_y + p_sys->i_width * i_active_line, p_sys->p_u + (p_sys->i_width / 2) * (i_active_line / 2), p_sys->p_v + (p_sys->i_width / 2) * (i_active_line / 2) ); if ( p_sys->b_vbi && p_sys->i_height == 576 && p_sys->i_current_line == p_sys->i_line_offset ) { /* Line 23 is half VBI, half active */ UnpackVBI( p_line + anc + 5, p_sys->i_active_size - 5, p_sys->p_wss_buffer ); } } else if ( p_sys->b_vbi && p_sys->i_telx_count && i_field_line >= p_sys->i_telx_line && i_field_line < p_sys->i_telx_line + p_sys->i_telx_count ) { UnpackVBI( p_line + anc + 5, p_sys->i_active_size - 5, &p_sys->p_telx_buffer[(i_field_line - p_sys->i_telx_line + b_field * p_sys->i_telx_count) * p_sys->i_width * 2] ); } else if ( b_vbi && HasAncillary( p_line + anc + 5 ) ) { /* VANC */ unsigned int i_anc_words = (p_sys->i_active_size - 5) * 4 / 5; uint16_t p_anc[i_anc_words]; UnpackAnc( p_line + 5, p_sys->i_active_size - 5, p_anc ); HandleAncillary( p_demux, p_anc, i_anc_words ); } p_sys->i_current_line++; if ( p_sys->i_current_line == p_sys->i_nb_lines ) { p_sys->i_current_line %= p_sys->i_nb_lines; if( DecodeFrame( p_demux ) != VLC_SUCCESS ) return VLC_EGENERIC; } } break; } return VLC_SUCCESS; } /***************************************************************************** * Low-level device stuff *****************************************************************************/ #define MAXLEN 256 static int ReadULSysfs( const char *psz_fmt, unsigned int i_link ) { char psz_file[MAXLEN]; unsigned int i_data; snprintf( psz_file, sizeof(psz_file), psz_fmt, i_link ); FILE *stream = vlc_fopen( psz_file, "rt" ); if( stream == NULL ) return -1; int ret = fscanf( stream, "%u", &i_data ); fclose( stream ); return (ret == 1 && i_data <= INT_MAX) ? (int)i_data : -1; } static int WriteULSysfs( const char *psz_fmt, unsigned int i_link, unsigned int i_buf ) { char psz_file[MAXLEN]; snprintf( psz_file, sizeof(psz_file), psz_fmt, i_link ); FILE *stream = vlc_fopen( psz_file, "wt" ); if( stream == NULL ) return -1; int ret = fprintf( stream, "%u\n", i_buf ); fclose( stream ); return ret; } static int InitCapture( demux_t *p_demux ) { demux_sys_t *p_sys = p_demux->p_sys; const int i_page_size = getpagesize(); unsigned int i_bufmemsize; int i_ret; char psz_dev[MAXLEN]; /* 10-bit mode or nothing */ if ( WriteULSysfs( SDI_MODE_FILE, p_sys->i_link, SDI_CTL_MODE_10BIT ) < 0 ) { msg_Err( p_demux, "couldn't write file " SDI_MODE_FILE, p_sys->i_link ); return VLC_EGENERIC; } if ( (i_ret = ReadULSysfs( SDI_BUFFERS_FILE, p_sys->i_link )) < 0 ) { msg_Err( p_demux, "couldn't read file " SDI_BUFFERS_FILE, p_sys->i_link ); return VLC_EGENERIC; } p_sys->i_buffers = i_ret; p_sys->i_current_buffer = 0; if ( (i_ret = ReadULSysfs( SDI_BUFSIZE_FILE, p_sys->i_link )) < 0 ) { msg_Err( p_demux, "couldn't read file " SDI_BUFSIZE_FILE, p_sys->i_link ); return VLC_EGENERIC; } p_sys->i_buffer_size = i_ret; if ( p_sys->i_buffer_size % 20 ) { msg_Err( p_demux, "buffer size must be a multiple of 20" ); return VLC_EGENERIC; } snprintf( psz_dev, sizeof(psz_dev) - 1, SDI_DEVICE, p_sys->i_link ); if ( (p_sys->i_fd = vlc_open( psz_dev, O_RDONLY ) ) < 0 ) { msg_Err( p_demux, "couldn't open device %s", psz_dev ); return VLC_EGENERIC; } i_bufmemsize = ((p_sys->i_buffer_size + i_page_size - 1) / i_page_size) * i_page_size; p_sys->pp_buffers = vlc_alloc( p_sys->i_buffers, sizeof(uint8_t *) ); if( !p_sys->pp_buffers ) return VLC_ENOMEM; for ( unsigned int i = 0; i < p_sys->i_buffers; i++ ) { if ( (p_sys->pp_buffers[i] = mmap( NULL, p_sys->i_buffer_size, PROT_READ, MAP_SHARED, p_sys->i_fd, i * i_bufmemsize )) == MAP_FAILED ) { msg_Err( p_demux, "couldn't mmap(%d): %s", i, vlc_strerror_c(errno) ); free( p_sys->pp_buffers ); return VLC_EGENERIC; } } return VLC_SUCCESS; } static void CloseCapture( demux_t *p_demux ) { demux_sys_t *p_sys = p_demux->p_sys; StopDecode( p_demux ); for ( unsigned int i = 0; i < p_sys->i_buffers; i++ ) munmap( p_sys->pp_buffers[i], p_sys->i_buffer_size ); vlc_close( p_sys->i_fd ); free( p_sys->pp_buffers ); } static int Capture( demux_t *p_demux ) { demux_sys_t *p_sys = p_demux->p_sys; struct pollfd pfd; pfd.fd = p_sys->i_fd; pfd.events = POLLIN | POLLPRI; if ( poll( &pfd, 1, READ_TIMEOUT ) < 0 ) { msg_Warn( p_demux, "couldn't poll(): %s", vlc_strerror_c(errno) ); return VLC_EGENERIC; } if ( pfd.revents & POLLPRI ) { unsigned int i_val; if ( ioctl( p_sys->i_fd, SDI_IOC_RXGETEVENTS, &i_val ) < 0 ) msg_Warn( p_demux, "couldn't SDI_IOC_RXGETEVENTS: %s", vlc_strerror_c(errno) ); else { if ( i_val & SDI_EVENT_RX_BUFFER ) msg_Warn( p_demux, "driver receive buffer queue overrun" ); if ( i_val & SDI_EVENT_RX_FIFO ) msg_Warn( p_demux, "onboard receive FIFO overrun"); if ( i_val & SDI_EVENT_RX_CARRIER ) msg_Warn( p_demux, "carrier status change"); } p_sys->i_next_date += CLOCK_GAP; } if ( pfd.revents & POLLIN ) { int i_ret; if ( ioctl( p_sys->i_fd, SDI_IOC_DQBUF, p_sys->i_current_buffer ) < 0 ) { msg_Warn( p_demux, "couldn't SDI_IOC_DQBUF: %s", vlc_strerror_c(errno) ); return VLC_EGENERIC; } i_ret = HandleSDBuffer( p_demux, p_sys->pp_buffers[p_sys->i_current_buffer], p_sys->i_buffer_size ); if ( ioctl( p_sys->i_fd, SDI_IOC_QBUF, p_sys->i_current_buffer ) < 0 ) { msg_Warn( p_demux, "couldn't SDI_IOC_QBUF: %s", vlc_strerror_c(errno) ); return VLC_EGENERIC; } if ( i_ret == VLC_SUCCESS ) { p_sys->i_current_buffer++; p_sys->i_current_buffer %= p_sys->i_buffers; } else { /* Reference codes do not start on a multiple of 5. This sometimes * happen. We really don't want to allow this. */ msg_Warn( p_demux, "resetting board" ); CloseCapture( p_demux ); InitCapture( p_demux ); } } return VLC_SUCCESS; }