The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 */

/*=============================================================================================*/
// USES
/*=============================================================================================*/

#include <features.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/ioctl.h>

#include "dvb_lib.h"
#include "dvb_tune.h"
#include "dvb_stream.h"
#include "dvb_debug.h"
#include "dvb_error.h"

#include "ts_parse.h"

// Added for EIT decoding
#include "tables/parse_si_eit.h"


/*=============================================================================================*/
// CONSTANTS
/*=============================================================================================*/

//#define TEST_NO_EIT
#define TIMESTAMP_PRINT


// DVR buffer read timeout
#define TIMEOUT_SECS	3

// Max delay before now/next service should have been seen
// One or other is usually available after 7 secs; both after 20 secs
#define GET_EIT_DELAY	10
#define EIT_NEXT_DELAY	30


/*=============================================================================================*/
// MACROS
/*=============================================================================================*/

#ifdef TIMESTAMP_PRINT
#define dvbstream_fprintf				fprintf_timestamp
#define dvbstream_dbg_prt(LVL, ARGS)	\
		if (tsreader->debug >= LVL)	{ printf_timestamp ARGS ; fflush(stdout) ; }
#else
#define dvbstream_fprintf				fprintf
#define dvbstream_dbg_prt(LVL, ARGS)	tsparse_dbg_prt(LVL, ARGS)
#endif


/*=============================================================================================*/
// STRUCTS
/*=============================================================================================*/

struct Timeslip_data {
	struct multiplex_pid_struct 	*pid_list ;
	unsigned 						num_entries ;
};


/*=============================================================================================*/
// FUNCTIONS
/*=============================================================================================*/

#ifdef PROFILE_STREAM

#undef TS_BUFFSIZE
#define TS_BUFFSIZE 188

#define BINS_TIME			10
#define clear_bins(bins)	memset(bins, 0, (TS_BUFFSIZE+1)*sizeof(unsigned))
#define inc_bin(bins, bin)	if ((bin>=0) && (bin <= TS_BUFFSIZE)) { ++bins[bin]; }

/* ----------------------------------------------------------------------- */
void show_bins(unsigned *bins)
{
unsigned bin ;

	printf("Read histogram: ") ;
	for (bin=0; bin <= TS_BUFFSIZE; ++bin)
	{
		if (bins[bin])
		{
			printf("%d=%d, ", bin, bins[bin]) ;
		}
	}
	printf("\n") ;
}
#endif

/* ----------------------------------------------------------------------- */
// Wait for data ready or timeout
int
input_timeout (int filedes, unsigned int seconds)
{
   fd_set set;
   struct timeval timeout;

   /* Initialize the file descriptor set. */
   FD_ZERO (&set);
   FD_SET (filedes, &set);

   /* Initialize the timeout data structure. */
   timeout.tv_sec = seconds;
   timeout.tv_usec = 0;

   /* select returns 0 if timeout, 1 if input available, -1 if error. */
   return TEMP_FAILURE_RETRY (select (FD_SETSIZE,
                                      &set, NULL, NULL,
                                      &timeout));
}




/* ----------------------------------------------------------------------- */
int getbuff(struct dvb_state *h, char *buffer, int *count)
{
int rc ;
int status ;
int data_ready ;
fe_status_t  fe_status  = 0;
int read_count = *count ;


	status = 0 ;

	// wait for data (or time out)
	data_ready = input_timeout(h->dvro, TIMEOUT_SECS) ;
	if (data_ready != 1)
	{
		if (dvb_debug >= 10) fprintf(stderr,"reading %d bytes\n", *count);
		if (data_ready < 0)
		{
			//perror("read");
			RETURN_DVB_ERROR(ERR_SELECT);
		}
		else
		{
			//fprintf_timestamp(stderr,"timed out\n");
			RETURN_DVB_ERROR(ERR_TIMEOUT);
		}
#ifdef PROFILE_STREAM
perror("data ready error : ");
#endif
	}

	// got to here so data is available
	rc = read(h->dvro, buffer, read_count);

	// return actual read amount
	if (rc > 0)
	{
		*count = rc ;
	}
	else
	{
#ifdef PROFILE_STREAM
perror("read error : ");
#endif
		if (errno == EOVERFLOW)
		{
			// ignore overflow error
			status = ERR_OVERFLOW ;
			rc = 1 ;
		}
		else
		{
			// some problem - show frontend status
			if (-1 != ioctl(h->fdro, FE_READ_STATUS, &fe_status))
			{
				if (dvb_debug) fprintf_timestamp(stderr, ">>> tuning status == 0x%04x\n", fe_status) ;
			}
		}
	}

if (dvb_debug >= 10) fprintf(stderr, "getbuff(): request=%d read=%d\n", *count, rc) ;

	switch (rc) {
	case -1:
		//fprintf_timestamp(stderr,"reading %d bytes\n", count);
		//perror("read");
		RETURN_DVB_ERROR(ERR_READ);
	case 0:
		//fprintf_timestamp(stderr,"EOF\n");
		RETURN_DVB_ERROR(ERR_EOF);

	default:
		break;
	}
	return(status) ;
}

/* ----------------------------------------------------------------------- */
int write_stream(struct dvb_state *h, char *filename, int sec)
{
time_t start, end, now, prev;
char buffer[TS_BUFFSIZE];
int file;
int count;
int rc, nwrite;
unsigned done ;

    if (sec <= 0)
    {
		//fprintf(stderr, "Invalid duration (%d)\n", sec);
    	RETURN_DVB_ERROR(ERR_DURATION);
    }

    if (-1 == h->dvro)
    {
		//fprintf(stderr,"dvr device not open\n");
		RETURN_DVB_ERROR(ERR_DVR_OPEN);
    }

    file = open(filename, O_WRONLY | O_TRUNC | O_CREAT | O_LARGEFILE, 0666);
    if (-1 == file) {
		//fprintf(stderr,"open %s: %s\n",filename,strerror(errno));
		RETURN_DVB_ERROR(ERR_FILE);
    }

    count = 0;
    start = time(NULL);
    end = sec + time(NULL);
	for (done=0; !done;)
	{
		rc = read(h->dvro, buffer, sizeof(buffer));
		switch (rc) {
		case -1:
			//perror("read");
			RETURN_DVB_ERROR(ERR_READ);
		case 0:
			//fprintf(stderr,"EOF\n");
			RETURN_DVB_ERROR(ERR_EOF);
		default:
			nwrite = write(file, buffer, rc);
			count += rc;
			break;
		}
		now = time(NULL);

		if (dvb_debug)
		{
			if (prev != now)
			{
				fprintf(stderr, "%d / %d : %d bytes\n", (int)(now-start), (int)(end-start), rc) ;
				prev = now ;
			}
		}

		if (now >= end)
		{
			++done ;
			break;
		}
	}
    
    close(file);

    return 0;
}



//=======================================================================================================================
// Using TS parser to add functionality
//=======================================================================================================================

//---------------------------------------------------------------------------------------------------------------------------
static void eit_handler(struct TS_reader *tsreader, struct TS_state *tsstate, struct Section *section, void *user_data)
{
struct Timeslip_data *timeslip_data = (struct Timeslip_data *)user_data ;
struct Section_event_information *eit = (struct Section_event_information *)section ;
unsigned pid_index ;

	dvbstream_dbg_prt(3, ("Called eit_handler with : 0x%02x TSID %d\n", eit->table_id, eit->transport_stream_id)) ;

	// expect there to be only one currently running & one pending program per channel (service_id)

	// update all of the pids that have a matching event_id
	for (pid_index=0; (pid_index < timeslip_data->num_entries); ++pid_index)
	{
		// check that we specified an event to find
		if (timeslip_data->pid_list[pid_index].event_id >= 0)
		{
			dvbstream_dbg_prt(3, ("EV: check service id %d : list id = %d\n",
				eit->service_id,
				timeslip_data->pid_list[pid_index].pnr)) ;

			// match program number
			if (timeslip_data->pid_list[pid_index].pnr == eit->service_id)
			{
				// find the event
				struct list_head  *item, *safe;
				struct EIT_entry  *eit_entry;

				list_for_each_safe(item,safe,&eit->eit_array) {
					eit_entry = list_entry(item, struct EIT_entry, next);

					if (dvb_debug >= 1)
						dvbstream_fprintf(stderr, "EV: check event id %d, running = %d: list event id = %d\n",
							eit_entry->event_id,
							eit_entry->running_status,
							timeslip_data->pid_list[pid_index].event_id
							) ;

					if (eit_entry->event_id == timeslip_data->pid_list[pid_index].event_id)
					{
						timeslip_data->pid_list[pid_index].running_status = eit_entry->running_status ;

						dvbstream_dbg_prt(3, ("EVENT + PID %d : pnr %d event %d - running = %d\n",
							tsstate->pidinfo.pid,
							eit->service_id,
							eit_entry->event_id,
							eit_entry->running_status
							)) ;
					}
					else
					{
						// if another service is running, then this one can't be
						if (eit_entry->running_status == RUNNING_STATUS_RUNNING)
						{
							if (timeslip_data->pid_list[pid_index].running_status == RUNNING_STATUS_RUNNING)
							{
								timeslip_data->pid_list[pid_index].running_status = RUNNING_STATUS_COMPLETE ;
							}
						}

					}

					// NOTE: Due to the asynchronous arrival of the now & next events (which are independent), can't assume
					// that the event id's will cycle correctly. For example, where we have a set of programs A -> B -> C
					// the now/next event ids can present as:
					//
					//	now		next
					//	--------------
					//	undef	undef		start
					//	undef	B			B next event arrives
					//	A		B			A now event arrives
					//	A		C			C next event arrives
					//	B		C			B now event arrives
					//
					//	rather than the easier to handle:
					//
					//	now		next
					//	--------------
					//	undef	undef
					//	A		undef
					//	A		B
					//	B		C
					//
					//	so we can't assume anything about the pair of indicators
					//
					//

					// track the current running/pending events
					if (eit_entry->running_status == RUNNING_STATUS_RUNNING)
					{
						timeslip_data->pid_list[pid_index].running_event_id = eit_entry->event_id ;
						timeslip_data->pid_list[pid_index].got_eit = 1 ;
					}
					else if (eit_entry->running_status == RUNNING_STATUS_PENDING)
					{
						timeslip_data->pid_list[pid_index].pending_event_id = eit_entry->event_id ;
						timeslip_data->pid_list[pid_index].got_eit = 1 ;
					}

				};
			}
		}
	}
}


/* ----------------------------------------------------------------------- */
int write_stream_demux(struct dvb_state *h, struct multiplex_pid_struct *pid_list, unsigned num_entries)
{
time_t now, prev, end_time;
char buffer[TS_BUFFSIZE];
char *bptr ;
int status, final_status;
int rc, wrc;
unsigned ts_pid, ts_err ;
unsigned pid_index ;
int running ;
unsigned running_timeslip ;
int buffer_len ;
int bytes_read ;
char debugstr[1024] ;

struct TS_reader *tsreader ;
struct Timeslip_data timeslip_data ;
struct Section_decode_flags flags ;

	// Initialise the TS parser
	running_timeslip = 0 ;
	tsreader = tsreader_new_nofile() ;
	tsreader_data_start(tsreader) ;

	timeslip_data.num_entries = num_entries ;
	timeslip_data.pid_list = pid_list ;
	tsreader->user_data = &timeslip_data ;

#ifdef TEST_NO_EIT

    dvbstream_fprintf(stderr, "**TEST MODE: TESTING NO EIT DATA**\n") ;

#endif


if (dvb_debug >= 4)
	tsreader->debug = dvb_debug ;


#ifdef PROFILE_STREAM
unsigned read_bins[TS_BUFFSIZE+1] ;
time_t bins_time ;
#endif

    if (-1 == h->dvro)
    {
    	if (dvb_debug >= 2)
    		dvbstream_fprintf(stderr,"dvr device not open\n");

		RETURN_DVB_ERROR(ERR_DVR_OPEN);
    }

    // make access to demux non-blocking
    setNonblocking(h->dvro) ;

    // sticky error
    final_status = 0 ;

#ifdef PROFILE_STREAM
    clear_bins(read_bins) ;
    bins_time = time(NULL) + BINS_TIME ;
#endif

	// find end time
    end_time = 0 ;
	for (pid_index=0; pid_index < num_entries; ++pid_index)
	{
		if (end_time < pid_list[pid_index].file_info->end)
		{
			end_time = pid_list[pid_index].file_info->end;
		}

		// check timeslip
		if (pid_list[pid_index].max_timeslip == 0)
		{
			// cancel timeslip
			pid_list[pid_index].event_id = -1 ;
			pid_list[pid_index].timeslip_start = 0 ;
			pid_list[pid_index].timeslip_end = 0 ;

		}
		if (pid_list[pid_index].event_id >= 0)
		{
			running_timeslip = 1 ;
		}

		// Display settings
		if (dvb_debug)
		{
			dvbstream_fprintf(stderr, "(%02d) PID %d : pnr=%d event=%d timeslip [start %d, end %d] max=%d\n",
					pid_index,
					pid_list[pid_index].pid,
					pid_list[pid_index].pnr,
					pid_list[pid_index].event_id,
					pid_list[pid_index].timeslip_start,
					pid_list[pid_index].timeslip_end,
					pid_list[pid_index].max_timeslip) ;
		}
	}

	// Only add the overhead of parsing the EITs if something requires timeslip
	if (running_timeslip)
	{
	    // register an interest
	    flags.decode_descriptor = 0 ;
	    int num_added = tsreader_register_section(tsreader,
	    		SECTION_EIT_NOW_ACTUAL, 0xff,
	    		eit_handler, flags) ;

	}


    // main loop
    running = num_entries ;
	buffer_len = 0 ;
	bptr = buffer ;
	prev = time(NULL);
    while (running > 0)
    {
		// start of each packet
		now = time(NULL);

		// check for request for new bytes
		if (buffer_len < TS_PACKET_LEN)
		{
			// next packets
			bytes_read = TS_BUFFSIZE_READ ;
			status = getbuff(h, buffer, &bytes_read) ;

			// special case of buffer overflow - update counts then continue
			if (status == ERR_OVERFLOW)
			{
				// increment counts
				for (pid_index=0; pid_index < num_entries; ++pid_index)
				{
					if (!pid_list[pid_index].done)
					{
						pid_list[pid_index].overflows++;
					}
				}
				status = ERR_NONE ;

				// check for end
				if (now > end_time)
				{
					running = 0 ;

					if (dvb_debug)
							dvbstream_fprintf(stderr, " + + Force stop all due now (%d) > end (%d)\n",
									(int)now,
									(int)end_time
									) ;
					break ;
				}
				continue ;
			}

			if (!final_status) final_status = status ;
			buffer_len = bytes_read ;
			bptr = buffer ;

			if (dvb_debug >= 10)
				dvbstream_fprintf(stderr, "Reload buffer : 0x%02x (bptr @ %p) %d bytes left\n", buffer_len?bptr[0]:0, bptr, buffer_len) ;

#ifdef PROFILE_STREAM
			inc_bin(read_bins, bytes_read) ;
#endif
		}


		if (dvb_debug >= 10)
			dvbstream_fprintf(stderr, "Start of loop : 0x%02x (bptr @ %p) %d bytes left\n", buffer_len?bptr[0]:0, bptr, buffer_len) ;

		// reset to a value that won't ever match
		ts_pid = NULL_PID ;

		// check sync byte
		while ( (bptr[0] != SYNC_BYTE) && (buffer_len > 0) )
		{
			if (dvb_debug >= 10)
				dvbstream_fprintf(stderr, "! Searching for sync : 0x%02x (bptr @ %p) len=%d\n", buffer_len?bptr[0]:0, bptr, buffer_len) ;

			++bptr ;
			--buffer_len ;
		}

		// only process if we have a packet's worth
		if (buffer_len >= TS_PACKET_LEN)
		{
			/* decode header
			#	sync_byte 8 bslbf
			#
			#	transport_error_indicator 1 bslbf
			#	payload_unit_start_indicator 1 bslbf
			#	transport_priority 1 bslbf
			#	PID 13 uimsbf
			#
			#	transport_scrambling_control 2 bslbf
			#	adaptation_field_control 2 bslbf
			#	continuity_counter 4 uimsbf
			#
			#	if(adaptation_field_control = = '10' || adaptation_field_control = = '11'){
			#		adaptation_field()
			#	}
			#	if(adaptation_field_control = = '01' || adaptation_field_control = = '11') {
			#		for (i = 0; i < N; i++){
			#		data_byte 8 bslbf
			#		}
			#	}
			*/
			ts_err = bptr[1] & 0x80 ;
			ts_pid = ((bptr[1] & 0x1f) << 8) | (bptr[2] & 0xff) & MAX_PID ;
			if (dvb_debug >= 10)
			{
				if (prev != now)
				{
					dvbstream_fprintf(stderr, "-> TS PID 0x%x (%u)\n", ts_pid, ts_pid) ;
				}
			}

#ifndef TEST_NO_EIT

			// TS parse
			if (running_timeslip)
			{
				tsreader_data_add(tsreader, buffer, buffer_len) ;
			}
#endif

		}

		// search the pid list for a match (also keep done flags up to date - in case there are no packets for this pid!)
		for (pid_index=0; pid_index < num_entries; ++pid_index)
		{
			// debug display
			if (dvb_debug)
			{
				if (prev != now)
				{
					if (pid_index==0)
					{
						dvbstream_fprintf(stderr, "%d Running / %d Total\n", running, num_entries);
					}

					sprintf(debugstr, "[File %02d] (%02d) + + PID %d : %"PRIu64" pkts (%"PRIu64" errors) : [event %d run %d] : [now %d next %d : got EIT %u] : now=%d, end=%d, file end=%d : ",
							(int)pid_list[pid_index].file_info->file,
							pid_index,
							pid_list[pid_index].pid,
							pid_list[pid_index].pkts,
							pid_list[pid_index].errors,
							pid_list[pid_index].event_id,
							pid_list[pid_index].running_status,
							pid_list[pid_index].running_event_id,
							pid_list[pid_index].pending_event_id,
							pid_list[pid_index].got_eit,
							(int)now,
							(int)end_time,
							(int)pid_list[pid_index].file_info->end
							) ;

					if (pid_list[pid_index].done)
					{
						strcat(debugstr, "complete") ;
					}
					else
					{
						if (now >= pid_list[pid_index].file_info->start)
						{
							if (pid_list[pid_index].started)
							{
								if (now <= pid_list[pid_index].file_info->end)
								{
									sprintf(debugstr, "%s recording (%d secs remaining)",
										debugstr,
										(int)(pid_list[pid_index].file_info->end - now)) ;
								}
								else
								{
									sprintf(debugstr, "%s recording (+%d secs slipped)",
										debugstr,
										(int)(now - pid_list[pid_index].file_info->end)) ;
								}
							}
							else
							{
								sprintf(debugstr, "%s timeslipping start by %d secs ...",
									debugstr,
									(int)(now - pid_list[pid_index].file_info->start)) ;
							}
						}
						else
						{
							sprintf(debugstr, "%s starting in %d secs ...",
								debugstr,
								(int)(pid_list[pid_index].file_info->start - now)) ;
						}
					}

					if (dvb_debug >= 2)
					{
						dvbstream_fprintf(stderr, "%s [buff len=%d]\n", debugstr, buffer_len) ;
					}
					else
					{
						dvbstream_fprintf(stderr, "%s\n", debugstr) ;
					}
				}
			} // dvb_debug

#ifdef PROFILE_STREAM
			if (now >= bins_time)
			{
				show_bins(read_bins) ;
				clear_bins(read_bins) ;
				bins_time = time(NULL) + BINS_TIME ;
			}

//			usleep(10000) ;
#endif


			// skip if done
			if (!pid_list[pid_index].done)
			{
				//----------------------------------------------------------
				// START

				// check start time
				if (!pid_list[pid_index].started && (now >= pid_list[pid_index].file_info->start))
				{
					unsigned started = 0 ;

					// track start timeslip
					pid_list[pid_index].timeslip_start_secs = (unsigned)(now - pid_list[pid_index].file_info->start) ;

					// check for timeslipping start
					if (pid_list[pid_index].timeslip_start)
					{
						// check for timeout
						if (now - pid_list[pid_index].file_info->start >= pid_list[pid_index].max_timeslip)
						{
							// got to start now
							started = 1 ;

							if (dvb_debug)
									dvbstream_fprintf(stderr, " + + PID %d : Force start due to MAX TIMESLIP (%d) timeout\n",
											pid_list[pid_index].pid,
											pid_list[pid_index].max_timeslip
											) ;
						}
						else
						{
							// check status
							if (pid_list[pid_index].running_status >= RUNNING_STATUS_RUNNING)
							{
								started = 1 ;

								if (dvb_debug)
										dvbstream_fprintf(stderr, " + + PID %d : start due EIT now RUNNING\n",
												pid_list[pid_index].pid
												) ;
							}
						}

						// Catch the error case where the now/next service is not running (or program recording
						// has started too early/late or with the wrong event id)
						//
						// Pending event id should be set within 7 secs and it should be the program we're about the record.
						// If we've started recording in the middle of the required program, then the eit handler will have
						// set the running status and we'll automatically start recording
						//
						if (!started && !pid_list[pid_index].got_eit)
						{
							// check for timeout
							if (pid_list[pid_index].timeslip_start_secs >= GET_EIT_DELAY)
							{
								// force a start now since we're unlikely to get the now/next info
								started = 1 ;

								if (dvb_debug)
										dvbstream_fprintf(stderr, " + + PID %d : Force start due to GET_EIT timeout\n",
												pid_list[pid_index].pid
												) ;
							}
						}
						if (!started && (pid_list[pid_index].event_id != pid_list[pid_index].pending_event_id))
						{
							// check for timeout
							if (pid_list[pid_index].timeslip_start_secs >= EIT_NEXT_DELAY)
							{
								// force a start now since we're unlikely to get the now/next info
								started = 1 ;

								if (dvb_debug)
										dvbstream_fprintf(stderr, " + + PID %d : Force start due to EIT NEXT timeout\n",
												pid_list[pid_index].pid
												) ;
							}
						}
					}
					else
					{
						// ok to start
						started = 1 ;

						if (dvb_debug)
								dvbstream_fprintf(stderr, " + + PID %d : start now (no timeslip)\n",
										pid_list[pid_index].pid
										) ;
					}

					// start now?
					pid_list[pid_index].started |= started ;

					// update file end (and maximum end time)
					pid_list[pid_index].file_info->end = (now + pid_list[pid_index].file_info->duration) ;
					if (end_time < pid_list[pid_index].file_info->end)
					{
						end_time = pid_list[pid_index].file_info->end;
					}

				} // !started AND (now >= start)


				//----------------------------------------------------------
				// WRITE

				// matching pid?
				if (ts_pid == pid_list[pid_index].pid)
				{

					// see if we've now started recording
					if (pid_list[pid_index].started)
					{
						// write this packet to the corresponding file
						wrc=write(pid_list[pid_index].file_info->file, bptr, TS_PACKET_LEN);

						// error count
						if (ts_err)
						{
							pid_list[pid_index].errors++;
						}

						// debug
						pid_list[pid_index].pkts++;

						if (dvb_debug >= 10)
							dvbstream_fprintf(stderr, " + + Written PID %u : total %"PRIu64" pkts (%"PRIu64" errors) : ",
									pid_list[pid_index].pid,
									pid_list[pid_index].pkts,
									pid_list[pid_index].errors
									) ;

					} // started

				} // transport stream pid == this pid


				//----------------------------------------------------------
				// END

				// check end time - mark as done if elapsed
				if (now > pid_list[pid_index].file_info->end)
				{
					unsigned done =0 ;

					// check for timeslipping end
					if (pid_list[pid_index].timeslip_end)
					{
						// track end timeslip
						pid_list[pid_index].timeslip_end_secs = (unsigned)(now - pid_list[pid_index].file_info->end) ;

						// check for timeout
						if (now - pid_list[pid_index].file_info->end >= pid_list[pid_index].max_timeslip)
						{
							// got to stop now
							done = 1 ;

							if (dvb_debug)
									dvbstream_fprintf(stderr, " + + PID %d : Force end due to MAX TIMESLIP (%d) timeout\n",
											pid_list[pid_index].pid,
											pid_list[pid_index].max_timeslip
											) ;
						}
						else
						{
							// check status
							if (pid_list[pid_index].running_status > RUNNING_STATUS_RUNNING)
							{
								done = 1 ;

								if (dvb_debug)
										dvbstream_fprintf(stderr, " + + PID %d : end due to EIT running NOT RUNNING\n",
												pid_list[pid_index].pid
												) ;
							}
						}



						// Catch the error case where the now/next service is not running (or program recording
						// has started too early/late or with the wrong event id)
						if (!done &&
							(pid_list[pid_index].event_id != pid_list[pid_index].running_event_id) &&
							(pid_list[pid_index].event_id != pid_list[pid_index].pending_event_id)
						)
						{
							// check for timeout
							if (pid_list[pid_index].timeslip_end_secs >= EIT_NEXT_DELAY)
							{
								// force a stop now since we're unlikely to get the now/next info
								done = 1 ;

								if (dvb_debug)
										dvbstream_fprintf(stderr, " + + PID %d : Force end due to EIT NEXT timeout\n",
												pid_list[pid_index].pid
												) ;

							}
						}

					}
					else
					{
						// ok to stop
						done = 1 ;

						if (dvb_debug)
								dvbstream_fprintf(stderr, " + + PID %d : Force end (no timeslip)\n",
										pid_list[pid_index].pid
										) ;
					}

					// set flag?
					if (done)
					{
						pid_list[pid_index].done = 1 ;
						--running ;
					}
					else
					{
						// adjust max end time while we're time slipping this pid
						if (end_time < now + EIT_NEXT_DELAY)
						{
							// allow enough time to see the timeout
							end_time = now + EIT_NEXT_DELAY + 1;
						}

					}

				} // Timeslipping End : now > end

			} // !done

		} // for each pid

		//----------------------------------------------
		// update buffer
		if (buffer_len >= TS_PACKET_LEN)
		{
			buffer_len -= TS_PACKET_LEN ;
			bptr += TS_PACKET_LEN ;
		}

		prev = now ;

		if (dvb_debug >= 10)
			dvbstream_fprintf(stderr, "End of loop : 0x%02x (bptr @ %p) %d bytes left\n", buffer_len?bptr[0]:0, bptr, buffer_len) ;

    } // while running


	// terminate the TS parser
	tsreader_data_end(tsreader) ;
	tsreader_free(tsreader) ;


    return final_status;
}