Commit a24fd472 authored by Jean-Paul Saman's avatar Jean-Paul Saman

dvbinfo: implement monitor mode

In monitor mode dvbinfo runs as Unix daemon and the logging goes to syslog instead of stderr.
- added -m (--monitor) commandline switch to run in the background
- added -s (--summary) commandline switch to produce bitrate summary files
- added --summary-file commandline switch to give a summary-file name (default: stdout)
- added --summary-period commandline switch to write every <n> ms a new summary file
- implemented a logging callback function for routing the dvbpsi log messages to dvbinfo application
parent c576e208
......@@ -25,7 +25,9 @@
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdarg.h>
#include <string.h>
#include <limits.h>
#if defined(HAVE_INTTYPES_H)
# include <inttypes.h>
......@@ -39,6 +41,8 @@
#include <unistd.h>
#include <fcntl.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
......@@ -60,6 +64,11 @@
# include "tcp.h"
#endif
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
static const int i_summary_mode[] = { SUM_BANDWIDTH, SUM_TABLE, SUM_PACKET, SUM_WIRE };
static const char *psz_summary_mode[] = { "bandwidth", "table", "packet", "wire" };
/*****************************************************************************
*
*****************************************************************************/
......@@ -70,7 +79,7 @@ typedef struct dvbinfo_capture_s
size_t size; /* prefered capture size */
const params_t *params;
params_t *params;
bool b_alive;
} dvbinfo_capture_t;
......@@ -80,40 +89,106 @@ typedef struct dvbinfo_capture_s
static void usage(void)
{
#ifdef HAVE_SYS_SOCKET_H
printf("Usage: dvbinfo [-h] [-d <debug>] [-f| [[-u|-t] -i <ipaddress:port>] -o <outputfile>\n");
printf("Usage: dvbinfo [-h] [-d <debug>] [-f|-m| [[-u|-t] -i <ipaddress:port>] -o <outputfile>\n");
printf(" [-s [bandwidth|table|packet] --summary-file <file> --summary-period <ms>]\n");
#else
printf("Usage: dvbinfo [-h] [-d <debug>] [-f|\n");
#endif
printf(" -d : debug level (default:none, error, warn, debug)\n");
printf(" -f : filename\n");
printf("\n");
printf(" -d | --debug : debug level (default:none, error, warn, debug)\n");
printf(" -h | --help : help information\n");
printf("\nInputs: \n");
printf(" -f | --file : filename\n");
#ifdef HAVE_SYS_SOCKET_H
printf(" -i : hostname or ipaddress\n");
printf(" -u : udp network transport\n");
printf(" -t : tcp network transport\n");
printf(" -o : output to filename\n");
printf(" -i | --ipadddress : hostname or ipaddress\n");
printf(" -t | --tcp : tcp network transport\n");
printf(" -u | --udp : udp network transport\n");
printf("\nOutputs: \n");
printf(" -o | --output : output incoming data to filename\n");
printf("\nStatistics: \n");
printf(" -m | --monitor : monitor mode (run as unix daemon)\n");
printf(" -s | --summary=[<type>]:write summary for one of the modes (default: bandwidth):\n");
printf(" bandwidth = bandwidth per elementary stream\n");
printf(" table = tables and descriptors\n");
printf(" packet = decode packets and print structs\n");
// printf(" wire = print arrival time per packet (wireshark like)\n");
printf(" --summary-file : file to write summary information to (default: stdout)\n");
printf(" --summary-period : refresh summary file every n milliseconds (default: 1000ms)\n");
#endif
printf(" -h : help information\n");
exit(EXIT_FAILURE);
}
/* */
/* Logging */
static int log_level[] = { LOG_ERR, LOG_WARNING, LOG_INFO, LOG_DEBUG };
static void log_remote(const int level, const char *format, ...)
{
va_list ap;
char *msg = NULL;
va_start(ap, format);
int err = vasprintf(&msg, format, ap);
va_end(ap);
if (err != 1)
{
syslog(log_level[level], "%s", msg);
free(msg);
}
}
static const char *psz_level[] = { "ERROR", "WARNING", "INFO", "DEBUG" };
static void log_local(const int level, const char *format, ...)
{
va_list ap;
int err = 0;
char *msg = NULL;
va_start(ap, format);
#if defined(_GNU_SOURCE)
err = vasprintf(&msg, format, ap);
#else
msg = calloc(1, 1024);
if (msg)
err = vsnprintf(msg, 1024, format, ap);
#endif
va_end(ap);
if (err != 1)
{
fprintf(stderr, "%s: %s", psz_level[level], msg);
free(msg);
}
}
static void libdvbpsi_log(void *data, const int level, const char *format, ...)
{
va_list ap;
va_start(ap, format);
params_t *param = (params_t *)data;
if (param)
param->pf_log(level, format, ap);
va_end(ap);
}
/* Parameters */
static params_t *params_init(void)
{
params_t *param;
param = (params_t *) calloc(1, sizeof(params_t));
if (param == NULL)
{
fprintf(stderr, "out of memory\n");
exit(EXIT_FAILURE);
}
param->fd_in = param->fd_out = -1;
param->input = NULL;
param->output = NULL;
param->debug = 0;
/* statistics */
param->b_summary = false;
param->summary.mode = SUM_BANDWIDTH;
param->summary.file = NULL;
param->summary.fd = stdout;
param->summary.period = 1000;
/* functions */
param->pf_read = NULL;
param->pf_write = NULL;
param->pf_log = log_local;
return param;
}
......@@ -121,6 +196,7 @@ static void params_free(params_t *param)
{
free(param->input);
free(param->output);
free(param->summary.file);
}
/* */
......@@ -220,19 +296,30 @@ static void *dvbinfo_capture(void *data)
return NULL;
}
static void dvbinfo_process(dvbinfo_capture_t *capture)
static int dvbinfo_process(dvbinfo_capture_t *capture)
{
int err = -1;
bool b_error = false;
const params_t *param = capture->params;
params_t *param = capture->params;
buffer_t *buffer = NULL;
ts_stream_t *stream = libdvbpsi_init(param->debug);
if (!stream)
char *psz_temp = NULL;
mtime_t deadline = 0;
if (param->b_summary)
{
fprintf(stderr, "error: out of memory\n");
exit(EXIT_FAILURE);
if (asprintf(&psz_temp, "%s.part", param->summary.file) < 0)
{
param->pf_log(DVBINFO_LOG_ERROR, "Could not create temporary summary file %s\n",
param->summary.file);
return err;
}
deadline = mdate() + param->summary.period;
}
ts_stream_t *stream = libdvbpsi_init(param->debug, &libdvbpsi_log, (void *)param);
if (!stream)
goto out;
while (!b_error)
{
/* Wait till fifo has emptied */
......@@ -254,12 +341,12 @@ static void dvbinfo_process(dvbinfo_capture_t *capture)
size_t size = param->pf_write(param->fd_out, buffer->p_data, buffer->i_size);
if (size < 0) /* error writing */
{
fprintf(stderr, "error (%d) writting to %s", errno, param->output);
param->pf_log(DVBINFO_LOG_ERROR, "error (%d) writting to %s", errno, param->output);
break;
}
else if (size < buffer->i_size) /* short writting disk full? */
{
fprintf(stderr, "error writting to %s (disk full?)", param->output);
param->pf_log(DVBINFO_LOG_ERROR, "error writting to %s (disk full?)", param->output);
break;
}
}
......@@ -267,17 +354,44 @@ static void dvbinfo_process(dvbinfo_capture_t *capture)
if (!libdvbpsi_process(stream, buffer->p_data, buffer->i_size, buffer->i_date))
b_error = true;
/* summary statistics */
if (param->b_summary)
{
if (mdate() >= deadline)
{
FILE *fd = fopen(psz_temp, "w+");
if (fd)
{
libdvbpsi_summary(fd, stream, param->summary.mode);
fflush(fd);
fclose(fd);
unlink(param->summary.file);
rename(psz_temp, param->summary.file);
}
else
{
param->pf_log(DVBINFO_LOG_ERROR, "failed opening summary file (disabling summary logging)\n");
param->b_summary = false;
}
deadline = mdate() + param->summary.period;
}
}
/* reuse buffer */
fifo_push(capture->empty, buffer);
buffer = NULL;
}
libdvbpsi_exit(stream);
err = 0;
out:
if (b_error)
fprintf(stderr, "error while processing\n" );
param->pf_log(DVBINFO_LOG_ERROR, "error while processing\n" );
buffer_free(buffer);
if (buffer) buffer_free(buffer);
free(psz_temp);
return err;
}
/*
......@@ -286,35 +400,44 @@ static void dvbinfo_process(dvbinfo_capture_t *capture)
int main(int argc, char **pp_argv)
{
dvbinfo_capture_t capture;
params_t *parm = NULL;
params_t *param = NULL;
bool b_monitor = false;
char c;
printf("dvbinfo: Copyright (C) 2011 M2X BV\n");
printf("dvbinfo: Copyright (C) 2011-2012 M2X BV\n");
printf("License: LGPL v2.1\n");
if (argc == 1)
usage();
parm = params_init();
capture.params = parm;
param = params_init();
capture.params = param;
capture.fifo = fifo_new();
capture.empty = fifo_new();
static const struct option long_options[] =
{
{ "debug", required_argument, NULL, 'd' },
{ "help", no_argument, NULL, 'h' },
/* - inputs - */
{ "file", required_argument, NULL, 'f' },
#ifdef HAVE_SYS_SOCKET_H
{ "ipaddress", required_argument, NULL, 'i' },
{ "tcp", no_argument, NULL, 't' },
{ "udp", no_argument, NULL, 'u' },
/* - outputs - */
{ "output", required_argument, NULL, 'o' },
/* - daemon - */
{ "monitor", no_argument, NULL, 'm' },
/* - statistics - */
{ "summary", required_argument, NULL, 's' },
{ "summary-file", required_argument, NULL, 'j' },
{ "summary-period", required_argument, NULL, 'p' },
#endif
{ "help", no_argument, NULL, 'h' },
{ 0, 0, 0, 0 }
{ NULL, 0, NULL, 0 }
};
#ifdef HAVE_SYS_SOCKET_H
while ((c = getopt_long(argc, pp_argv, "d:f:i:ho:tu", long_options, NULL)) != -1)
while ((c = getopt_long(argc, pp_argv, "d:f:i:ho:ms:tu", long_options, NULL)) != -1)
#else
while ((c = getopt_long(argc, pp_argv, "d:f:h", long_options, NULL)) != -1)
#endif
......@@ -324,26 +447,27 @@ int main(int argc, char **pp_argv)
case 'd':
if (optarg)
{
parm->debug = 0;
param->debug = 0;
if (strncmp(optarg, "error", 5) == 0)
parm->debug = 1;
param->debug = 1;
else if (strncmp(optarg, "warn", 4) == 0)
parm->debug = 2;
param->debug = 2;
else if (strncmp(optarg, "debug", 5) == 0)
parm->debug = 3;
param->debug = 3;
}
break;
case 'f':
if (optarg)
{
if (asprintf(&parm->input, "%s", optarg) < 0)
if (asprintf(&param->input, "%s", optarg) < 0)
{
fprintf(stderr, "error: out of memory\n");
params_free(param);
usage();
}
/* */
parm->pf_read = read;
param->pf_read = read;
}
break;
......@@ -355,87 +479,173 @@ int main(int argc, char **pp_argv)
if (psz_tmp)
{
size_t len = strlen(psz_tmp);
parm->port = strtol(&optarg[len+1], NULL, 0);
parm->input = strdup(psz_tmp);
param->port = strtol(&optarg[len+1], NULL, 0);
param->input = strdup(psz_tmp);
}
else
{
params_free(param);
usage();
}
else usage();
}
break;
case 'm':
b_monitor = true;
param->pf_log = log_remote;
break;
case 'o':
if (optarg)
{
if (asprintf(&parm->output, "%s", optarg) < 0)
if (asprintf(&param->output, "%s", optarg) < 0)
{
fprintf(stderr, "error: out of memory\n");
params_free(param);
usage();
}
/* */
parm->pf_write = write;
param->pf_write = write;
}
break;
case 't':
parm->b_tcp = true;
parm->pf_read = tcp_read;
param->b_tcp = true;
param->pf_read = tcp_read;
break;
case 'u':
parm->b_udp = true;
parm->pf_read = udp_read;
param->b_udp = true;
param->pf_read = udp_read;
break;
/* - Statistics */
case 's':
{
param->b_summary = true;
ssize_t size = ARRAY_SIZE(psz_summary_mode);
for (unsigned int i = 0; i < size; i++)
{
printf("summary mode %s\n", psz_summary_mode[i]);
if (strncmp(optarg, psz_summary_mode[i], strlen(psz_summary_mode[i])) == 0)
{
param->summary.mode = i_summary_mode[i];
break;
}
}
break;
}
case 'j':
if (optarg)
{
if (asprintf(&param->summary.file, "%s", optarg) < 0)
{
params_free(param);
usage();
}
}
break;
case 'p':
{
char *end = NULL;
param->summary.period = strtoll(optarg, &end, 10);
if (((errno == ERANGE) &&
((param->summary.period == LLONG_MIN) ||
(param->summary.period == LLONG_MAX))) ||
((errno != 0) && (param->summary.period == 0)))
{
fprintf(stderr, "Option --summary-period has invalid content %s\n", optarg);
params_free(param);
usage();
}
}
break;
#endif
case ':':
fprintf(stderr, "Option %c is missing arguments\n", c);
params_free(param);
exit(EXIT_FAILURE);
break;
case '?':
fprintf(stderr, "Unknown option %c found\n", c);
params_free(param);
exit(EXIT_FAILURE);
break;
case 'h':
default:
params_free(param);
usage();
break;
}
};
if (parm->input == NULL)
#ifdef HAVE_SYS_SOCKET_H
if (b_monitor)
{
fprintf(stderr, "No source given\n");
params_free(parm);
openlog("dvbinfo", LOG_PID, LOG_DAEMON);
if (daemon(1,0) < 0)
{
param->pf_log(DVBINFO_LOG_ERROR, "Failed to start in background\n");
params_free(param);
closelog();
usage(); /* exits application */
}
param->pf_log(DVBINFO_LOG_INFO, "dvbinfo: Copyright (C) 2011-2012 M2X BV\n");
param->pf_log(DVBINFO_LOG_ERROR, "License: LGPL v2.1\n");
}
#endif
if (param->input == NULL)
{
param->pf_log(DVBINFO_LOG_ERROR, "No source given\n");
params_free(param);
#ifdef HAVE_SYS_SOCKET_H
if (parm->b_udp || parm->b_tcp)
if (b_monitor)
closelog();
#endif
params_free(param);
usage(); /* exits application */
}
#ifdef HAVE_SYS_SOCKET_H
if (param->b_udp || param->b_tcp)
{
capture.size = 7*188;
printf("Listen: host=%s port=%d\n", parm->input, parm->port);
param->pf_log(DVBINFO_LOG_INFO, "Listen: host=%s port=%d\n", param->input, param->port);
}
else
#endif
{
capture.size = 188;
printf("Examining: %s\n", parm->input);
param->pf_log(DVBINFO_LOG_INFO, "Examining: %s\n", param->input);
}
/* */
dvbinfo_open(parm);
/* Capture thread */
dvbinfo_open(param);
pthread_t handle;
capture.b_alive = true;
if (pthread_create(&handle, NULL, dvbinfo_capture, (void *)&capture) < 0)
{
fprintf(stderr, "failed creating thread\n");
dvbinfo_close(parm);
param->pf_log(DVBINFO_LOG_ERROR, "failed creating thread\n");
dvbinfo_close(param);
#ifdef HAVE_SYS_SOCKET_H
if (b_monitor)
closelog();
#endif
params_free(param);
exit(EXIT_FAILURE);
}
dvbinfo_process(&capture);
int err = dvbinfo_process(&capture);
capture.b_alive = false; /* stop thread */
if (pthread_join(handle, NULL) < 0)
fprintf(stderr, "error joining capture thread\n");
dvbinfo_close(parm);
param->pf_log(DVBINFO_LOG_ERROR, "error joining capture thread\n");
dvbinfo_close(param);
/* cleanup */
params_free(parm);
params_free(param);
fifo_wake((&capture)->fifo);
fifo_wake((&capture)->empty);
......@@ -443,5 +653,12 @@ int main(int argc, char **pp_argv)
fifo_free((&capture)->fifo);
fifo_free((&capture)->empty);
#ifdef HAVE_SYS_SOCKET_H
if (b_monitor)
closelog();
#endif
if (err < 0)
exit(EXIT_FAILURE);
else
exit(EXIT_SUCCESS);
}
......@@ -23,6 +23,15 @@
#ifndef DVBINFO_H_
#define DVBINFO_H_
/* defined loglevels for use within dvbinfo are as follows,
* they match to this order of syslog priority levels.
* { LOG_ERR, LOG_WARNING, LOG_INFO, LOG_DEBUG };
*/
#define DVBINFO_LOG_ERROR 0
#define DVBINFO_LOG_WARN 1
#define DVBINFO_LOG_INFO 2
#define DVBINFO_LOG_DEBUG 3
typedef struct params_s
{
/* parameters */
......@@ -39,10 +48,21 @@ typedef struct params_s
int debug;
bool b_verbose;
/* statistics */
bool b_summary; /* write summary */
struct summary_s {
int mode; /* one of: i_summary_mode */
int64_t period; /* summary period in ms */
char *file; /* summary file name */
FILE *fd; /* summary file descriptor */
} summary;
/* read data from file of socket */
ssize_t (*pf_read)(int fd, void *buf, size_t count);
ssize_t (*pf_write)(int fd, const void *buf, size_t count);
/* logging function */
void (*pf_log)(const int level, const char *format, ...);
} params_t;
#endif
......@@ -227,6 +227,10 @@ struct ts_stream_t
uint64_t i_packets;
uint64_t i_null_packets;
uint64_t i_lost_bytes;
/* logging */
ts_stream_log_cb pf_log;
void *cb_data;
};
/*****************************************************************************
......@@ -255,6 +259,7 @@ mtime_t mdate(void)
if (gettimeofday(&tv, NULL) < 0)
{
fprintf(stderr, "gettimeofday() error: %s\n", strerror(errno));
abort();
}
return (tv.tv_sec * (mtime_t)1000) + (tv.tv_usec / (mtime_t)1000);
......@@ -268,142 +273,103 @@ mtime_t mdate(void)
*****************************************************************************/
static void dvbpsi_message(dvbpsi_t *p_dvbpsi, const dvbpsi_msg_level_t level, const char* msg)
{
/* See dvbinfo.h for the definition of these log levels.*/
int code = 0;
const char *psz_level;
switch(level)
{
case DVBPSI_MSG_ERROR: fprintf(stderr, "Error: "); break;
case DVBPSI_MSG_WARN: fprintf(stderr, "Warning: "); break;
case DVBPSI_MSG_DEBUG: fprintf(stderr, "Debug: "); break;
case DVBPSI_MSG_ERROR: code = 0; psz_level = "Error: "; break;
case DVBPSI_MSG_WARN: code = 1; psz_level = "Warning: "; break;
case DVBPSI_MSG_DEBUG: code = 3; psz_level = "Debug: "; break;
default: /* do nothing */
return;
}
fprintf(stderr, "%s\n", msg);
}
/*****************************************************************************
* Dump TS packet as hex
*****************************************************************************/
static void ts_hexdump(const uint8_t * const data, const uint32_t length)
{
uint32_t i;
printf("\t");
for (i=0; i < length; i++)
ts_stream_t *stream = (ts_stream_t *)p_dvbpsi->p_sys;
if (stream && stream->pf_log)
{
if ((i%8) == 0) printf(" ");
if ((i%16) == 0) printf("\n\t %.4x: ", i);
printf("%.2x ", data[i]);
stream->pf_log(stream->cb_data, code, msg);
}
}
static void summary(ts_stream_t *stream)
{
uint64_t i_packets = 0;
mtime_t i_first_pcr = 0, i_last_pcr = 0;
mtime_t start = 0, end = 0;
printf("\n\t---------------------------------------------------------\n");
/* Find PCR PID and get pcr timestamps */
for (int i_pid = 0; i_pid < 8192; i_pid++)
else
{
if (stream->pid[i_pid].b_pcr)
char *reply = NULL;
if (asprintf(&reply,"%s%s\n", psz_level, msg) > 0)
{
start = stream->pid[i_pid].i_first_pcr;
end = stream->pid[i_pid].i_last_pcr;
if (stream->pid[i_pid].b_discontinuity_indicator)
{
printf("\tPCR discontinuity was signalled for PID: %4d (0x%4x)\n",
i_pid, i_pid);
fprintf(stderr, "%s", reply);
free(reply);
}
}
}
for (int i_pid = 0; i_pid < 8192; i_pid++)
{
if (stream->pid[i_pid].b_seen)
{
printf("\tFound PID: %4d (0x%4x), DRM: %s,", i_pid, i_pid,
(stream->pid[i_pid].i_transport_scrambling_control != 0x00) ? "yes" : " no" );
}
double bitrate = 0;
if ((end - start) > 0)
/*****************************************************************************
* Dump TS packet as hex
*****************************************************************************/
static void ts_hexdump(FILE *fd, const uint8_t * const data, const uint32_t length)
{
uint32_t i;
fprintf(fd, "\t");
for (i=0; i < length; i++)
{
bitrate = (double) (stream->pid[i_pid].i_packets * 188 * 8) /
((double)(end - start)/1000.0);
}
printf(" bitrate %0.4f kbit/s,", bitrate);
printf(" seen %"PRId64" packets",
stream->pid[i_pid].i_packets);
printf("\n");
i_packets += stream->pid[i_pid].i_packets;
if (i_first_pcr == 0)
i_first_pcr = start;
else
i_first_pcr = (i_first_pcr < start) ? i_first_pcr : start;
i_last_pcr = (i_last_pcr > end) ? i_last_pcr : end;
}
if ((i%8) == 0) fprintf(fd, " ");
if ((i%16) == 0) fprintf(fd, "\n\t %.4x: ", i);
fprintf(fd, "%.2x ", data[i]);
}
printf("\n\tNumber of packets: %"PRId64", stuffing %"PRId64" packets, lost %"PRId64" bytes, bitrate: %0.4f kbit/s\n",
i_packets, stream->i_null_packets, stream->i_lost_bytes,
(double)(((i_packets*188) + stream->i_lost_bytes) * 8)/((double)(i_last_pcr - i_first_pcr)/1000.0));
printf("\tPCR first: %"PRId64", last: %"PRId64", duration: %"PRId64"\n",
i_first_pcr, i_last_pcr, (mtime_t)(i_last_pcr - i_first_pcr));
printf("\n\t---------------------------------------------------------\n");
}
static void ts_header_dump(ts_pid_t *ts)
static void ts_header_dump(FILE *fd, ts_pid_t *ts)
{
printf("\tPID 0x%x seen %s\n",
fprintf(fd, "\n\tPID 0x%x seen %s\n",
ts->i_pid, ts->b_seen ? "yes" : "no");
printf("\tContinuity counter: %d\n", ts->i_cc);
printf("\tTransport Error indicator: %s\n",
fprintf(fd, "\tContinuity counter: %d\n", ts->i_cc);
fprintf(fd, "\tTransport Error indicator: %s\n",
ts->b_transport_error_indicator ? "yes" : "no");
printf("\tPayload unit start indicator: %s\n",
fprintf(fd, "\tPayload unit start indicator: %s\n",
ts->b_payload_unit_start_indicator ? "yes" : "no");
printf("\tScrambling control: %s\n",
fprintf(fd, "\tScrambling control: %s\n",
(ts->i_transport_scrambling_control != 0x0) ? "yes" : "no");
if (ts->i_transport_scrambling_control > 0x0)
printf("\tScrambling control word: 0x%x\n", ts->i_transport_scrambling_control);
printf("\tAdaptation field control: %s\n",
fprintf(fd, "\tScrambling control word: 0x%x\n", ts->i_transport_scrambling_control);
fprintf(fd, "\tAdaptation field control: %s\n",
ts->b_adaptation_field ? "yes" : "no");
if (ts->b_adaptation_field)
{
printf("\tDiscontinuity indicator: %s\n",
fprintf(fd, "\tDiscontinuity indicator: %s\n",
ts->b_discontinuity_indicator ? "yes" : "no");
printf("\tRandom access indicator: %s\n",
fprintf(fd, "\tRandom access indicator: %s\n",
ts->b_random_access_indicator ? "yes" : "no");
printf("\tElementary stream priority indicator: %s\n",
fprintf(fd, "\tElementary stream priority indicator: %s\n",
ts->b_elementary_stream_priority_indicator ? "yes" : "no");
printf("\tTransport private data: %s\n",
fprintf(fd, "\tTransport private data: %s\n",
ts->b_transport_private_data ? "yes" : "no");
if (ts->b_transport_private_data )
printf("\tTransport private data length: %d\n",
fprintf(fd, "\tTransport private data length: %d\n",
ts->i_transport_private_data_length);
printf("\tSplicing point: %s\n",
fprintf(fd, "\tSplicing point: %s\n",
ts->b_splicing_point ? "yes" : "no");
if (ts->b_splicing_point)
printf("\tSplice countdown: %d (0x%x)\n",
fprintf(fd, "\tSplice countdown: %d (0x%x)\n",
ts->i_splice_countdown, ts->i_splice_countdown);
printf("\tOriginal PCR: %s\n", ts->b_opcr ? "yes" : "no");
printf("\tPCR PID: %s\n", ts->b_pcr ? "yes" : "no");
fprintf(fd, "\tOriginal PCR: %s\n", ts->b_opcr ? "yes" : "no");
fprintf(fd, "\tPCR PID: %s\n", ts->b_pcr ? "yes" : "no");
if (ts->b_pcr)
printf("\tPCR: %"PRId64"\n", ts->i_pcr);
fprintf(fd, "\tPCR: %"PRId64"\n", ts->i_pcr);
/* adaptation field extension */
if (ts->b_adaptation_field_extension &&
ts->i_adaptation_field_extension_length > 0)
{
printf("\tadaptation field extension, length: %d\n",
fprintf(fd, "\tadaptation field extension, length: %d\n",
ts->i_adaptation_field_extension_length);
printf("\tlegal time window (ltw): %s\n", ts->b_ltw ? "yes" : "no");
printf("\tltw valid: %s\n", ts->b_ltw_valid ? "yes" : "no");
fprintf(fd, "\tlegal time window (ltw): %s\n", ts->b_ltw ? "yes" : "no");
fprintf(fd, "\tltw valid: %s\n", ts->b_ltw_valid ? "yes" : "no");
if (ts->b_ltw)
printf("\tlegal time window offset: %d\n", ts->i_ltw_offset);
printf("\tpiecewise rate: %s\n", ts->b_piecewise_rate ? "yes" : "no");
fprintf(fd, "\tlegal time window offset: %d\n", ts->i_ltw_offset);
fprintf(fd, "\tpiecewise rate: %s\n", ts->b_piecewise_rate ? "yes" : "no");
if (ts->b_piecewise_rate)
printf("\tpiecewise rate: %d\n", ts->i_piecewise_rate);
printf("\tseamless splice: %s\n",
fprintf(fd, "\tpiecewise rate: %d\n", ts->i_piecewise_rate);
fprintf(fd, "\tseamless splice: %s\n",
ts->b_seamless_splice ? "yes" : "no");
if (ts->b_seamless_splice)
{
......@@ -432,10 +398,128 @@ static void ts_header_dump(ts_pid_t *ts)
descr = "Reserved/User-defined";
break;
}
printf("\tsplice type 0x%x (%s)\n", ts->i_splice_type, descr);
fprintf(fd, "\tsplice type 0x%x (%s)\n", ts->i_splice_type, descr);
}
}
}
}
static void ts_dump_packet_details(FILE *fd, ts_stream_t *stream, const uint8_t *data, const uint16_t i_pid)
{
fprintf(fd, "\n\t---------------------------------------------------------\n");
fprintf(fd, "\tTS Packet number %"PRId64", ES number %"PRId64", pid %d (0x%x)\n",
stream->i_packets, stream->pid[i_pid].i_packets, i_pid, i_pid);
#if defined(HAVE_SYS_TIME_H)
fprintf(fd, "\tReceived time: %"PRId64" ms\n", stream->pid[i_pid].i_received);
#endif
ts_header_dump(fd, &stream->pid[i_pid]);
ts_hexdump(fd, data, 188);
fprintf(fd, "\n\t---------------------------------------------------------\n");
}
/*****************************************************************************
* Summary: Bandwidth, Packet, Table
*****************************************************************************/
static void summary(FILE *fd, ts_stream_t *stream)
{
uint64_t i_packets = 0;
mtime_t i_first_pcr = 0, i_last_pcr = 0;
mtime_t start = 0, end = 0;
fprintf(fd, "\n---------------------------------------------------------\n");
fprintf(fd, "\nSummary: Bandwidth\n");
/* Find PCR PID and get pcr timestamps */
for (int i_pid = 0; i_pid < 8192; i_pid++)
{
if (stream->pid[i_pid].b_pcr)
{
start = stream->pid[i_pid].i_first_pcr;
end = stream->pid[i_pid].i_last_pcr;
if (stream->pid[i_pid].b_discontinuity_indicator)
{
fprintf(fd, "PCR discontinuity was signalled for PID: %4d (0x%4x)\n",
i_pid, i_pid);
}
}
}
for (int i_pid = 0; i_pid < 8192; i_pid++)
{
if (stream->pid[i_pid].b_seen)
{
fprintf(fd, "Found PID: %4d (0x%4x), DRM: %s,", i_pid, i_pid,
(stream->pid[i_pid].i_transport_scrambling_control != 0x00) ? "yes" : " no" );
double bitrate = 0;
if ((end - start) > 0)
{
bitrate = (double) (stream->pid[i_pid].i_packets * 188 * 8) /
((double)(end - start)/1000.0);
}
fprintf(fd, " bitrate %0.4f kbit/s,", bitrate);
fprintf(fd, " seen %"PRId64" packets",
stream->pid[i_pid].i_packets);
fprintf(fd, "\n");
i_packets += stream->pid[i_pid].i_packets;
if (i_first_pcr == 0)
i_first_pcr = start;
else
i_first_pcr = (i_first_pcr < start) ? i_first_pcr : start;
i_last_pcr = (i_last_pcr > end) ? i_last_pcr : end;
}
}
double total_bitrate = (double)(((i_packets*188) + stream->i_lost_bytes) * 8)/((double)(i_last_pcr - i_first_pcr)/1000.0);
fprintf(fd, "\nTotal bitrate %0.4f kbits/s\n", total_bitrate);
fprintf(fd, "Number of packets: %"PRId64", stuffing %"PRId64" packets, lost %"PRId64" bytes\n",
i_packets, stream->i_null_packets, stream->i_lost_bytes);
fprintf(fd, "PCR first: %"PRId64", last: %"PRId64", duration: %"PRId64"\n",
i_first_pcr, i_last_pcr, (mtime_t)(i_last_pcr - i_first_pcr));
fprintf(fd, "\n---------------------------------------------------------\n");
}
static void summary_table(FILE *fd, ts_stream_t *stream)
{
fprintf(fd, "\n---------------------------------------------------------\n");
fprintf(fd, "\nSummary: Table\n");
fprintf(fd, "\nTable: PAT\n");
if (stream->pat.handle)
ts_header_dump(fd, stream->pat.pid);
fprintf(fd, "\nTable: PMT\n");
if (stream->pmt.handle)
ts_header_dump(fd, stream->pmt.pid_pmt);
fprintf(fd, "\nTable: CAT\n");
if (stream->cat.handle)
ts_header_dump(fd, stream->cat.pid );
fprintf(fd, "\nTable: SDT\n");
if (stream->sdt.handle)
ts_header_dump(fd, stream->sdt.pid);
fprintf(fd, "\nTable: EIT\n");
if (stream->eit.handle)
ts_header_dump(fd, stream->eit.pid);
fprintf(fd, "\nTable: TDT\n");
if (stream->tdt.handle)
ts_header_dump(fd, stream->tdt.pid);
fprintf(fd, "\n---------------------------------------------------------\n");
}
static void summary_packet(FILE *fd, ts_stream_t *stream)
{
fprintf(fd, "\n---------------------------------------------------------\n");
fprintf(fd, "\nSummary: Packet\n");
/* Find PCR PID and get pcr timestamps */
for (int i_pid = 0; i_pid < 8192; i_pid++)
{
ts_pid_t *ts = &stream->pid[i_pid];
ts_header_dump(fd, ts);
}
fprintf(fd, "\n---------------------------------------------------------\n");
}
/*****************************************************************************
......@@ -1201,12 +1285,18 @@ static void handle_CAT(void *p_data, dvbpsi_cat_t *p_cat)
/*****************************************************************************
* Public API
*****************************************************************************/
ts_stream_t *libdvbpsi_init(int debug)
ts_stream_t *libdvbpsi_init(int debug, ts_stream_log_cb pf_log, void *cb_data)
{
ts_stream_t *stream = (ts_stream_t *)calloc(1, sizeof(ts_stream_t));
if (stream == NULL)
return NULL;
if (pf_log)
{
stream->pf_log = pf_log;
stream->cb_data = cb_data;
}
/* print PSI tables debug anyway, unless no debug is wanted at all */
switch (debug)
{
......@@ -1314,8 +1404,7 @@ error:
void libdvbpsi_exit(ts_stream_t *stream)
{
printf("\nSummary:\n");
summary(stream);
summary(stdout, stream);
if (dvbpsi_HasDecoder(stream->pat.handle))
dvbpsi_DetachPAT(stream->pat.handle);
......@@ -1376,7 +1465,8 @@ bool libdvbpsi_process(ts_stream_t *stream, uint8_t *buf, ssize_t length, mtime_
{
stream->i_lost_bytes += i_lost;
i += i_lost;
fprintf(stderr, "%"PRId64": lost %"PRId64" bytes out of %"PRId64" in buffer\n",
stream->pf_log(stream->cb_data, 0,
"dvbinfo: %"PRId64": lost %"PRId64" bytes out of %"PRId64" in buffer\n",
date, (int64_t) i_lost, (int64_t)length);
if (i >= length)
return true;
......@@ -1399,7 +1489,8 @@ bool libdvbpsi_process(ts_stream_t *stream, uint8_t *buf, ssize_t length, mtime_
stream->pid[i_pid].i_received = date;
if (stream->level < DVBPSI_MSG_DEBUG)
fprintf(stderr, "%"PRId64" packet %"PRId64" pid %d (0x%x) cc %d\n",
stream->pf_log(stream->cb_data, 0,
"dvbinfo: %"PRId64" packet %"PRId64" pid %d (0x%x) cc %d\n",
date, stream->i_packets, i_pid, i_pid, i_cc);
if (i_pid == 0x0) /* PAT */
......@@ -1492,10 +1583,12 @@ bool libdvbpsi_process(ts_stream_t *stream, uint8_t *buf, ssize_t length, mtime_
stream->pid[i_pid].i_first_pcr = i_pcr;
if (i_pcr < stream->pid[i_pid].i_last_pcr)
{
fprintf(stderr, "dvbinfo: Warning wrapping PCR");
if (b_discontinuity_seen)
fprintf(stderr, " on discontinuity");
fprintf(stderr, "\n");
stream->pf_log(stream->cb_data, 2,
"dvbinfo: Warning wrapping PCR on discontinuity\n");
else
stream->pf_log(stream->cb_data, 2,
"dvbinfo: Warning wrapping PCR\n");
}
stream->pid[i_pid].i_prev_pcr = i_prev_pcr;
stream->pid[i_pid].i_last_pcr = i_pcr;
......@@ -1503,7 +1596,8 @@ bool libdvbpsi_process(ts_stream_t *stream, uint8_t *buf, ssize_t length, mtime_
if (stream->pid[i_pid].b_discontinuity_indicator)
{
/* cc discontinuity is expected */
fprintf(stderr, "dvbinfo: Server signalled the continuity counter discontinuity\n");
stream->pf_log(stream->cb_data, 2,
"dvbinfo: Server signalled the continuity counter discontinuity\n");
/* Discontinuity has been handled */
b_discontinuity_seen = false;
......@@ -1569,8 +1663,9 @@ bool libdvbpsi_process(ts_stream_t *stream, uint8_t *buf, ssize_t length, mtime_
if (b_discontinuity_seen)
{
fprintf(stderr, "dvbinfo: Continuity counter discontinuity (pid %d 0x%x found %d expected %d)\n",
i_pid, i_pid, stream->pid[i_pid].i_cc, i_old_cc+1 );
stream->pf_log(stream->cb_data, 2,
"dvbinfo: Continuity counter discontinuity (pid %d 0x%x found %d expected %d)\n",
i_pid, i_pid, stream->pid[i_pid].i_cc, i_old_cc+1);
/* Discontinuity has been handled */
b_discontinuity_seen = false;
......@@ -1579,22 +1674,31 @@ bool libdvbpsi_process(ts_stream_t *stream, uint8_t *buf, ssize_t length, mtime_
dump_packet:
if (stream->level >= DVBPSI_MSG_DEBUG)
{
printf("\n\t---------------------------------------------------------\n");
printf("\tTS Packet number %"PRId64", ES number %"PRId64", pid %d (0x%x)\n",
stream->i_packets, stream->pid[i_pid].i_packets, i_pid, i_pid);
#if defined(HAVE_SYS_TIME_H)
printf("\tReceived time: %"PRId64" ms\n", stream->pid[i_pid].i_received);
#endif
ts_header_dump(&stream->pid[i_pid]);
ts_hexdump(&buf[i],188);
printf("\n\t---------------------------------------------------------\n");
ts_dump_packet_details(stdout, stream, &buf[i], i_pid);
}
}
return true;
}
void libdvbpsi_summary(ts_stream_t *stream)
void libdvbpsi_summary(FILE *fd, ts_stream_t *stream, const int summary_mode)
{
summary(stream);
switch(summary_mode)
{
case SUM_TABLE:
summary_table(fd, stream);
break;
case SUM_PACKET:
summary_packet(fd, stream);
break;
#if 0
case SUM_WIRE:
summary_wire(fd, stream);
break;
#endif
case SUM_BANDWIDTH:
default:
summary(fd, stream);
break;
}
}
......@@ -23,16 +23,24 @@
#ifndef DVBINFO_DVBPSI_H_
#define DVBINFO_DVBPSI_H_
/* Date and time */
typedef int64_t mtime_t;
mtime_t mdate(void);
/* Summary */
#define SUM_BANDWIDTH 0
#define SUM_TABLE 1
#define SUM_PACKET 2
#define SUM_WIRE 3
/* MPEG-TS PSI decoders */
typedef struct ts_stream_t ts_stream_t;
typedef void (* ts_stream_log_cb)(void *data, const int level, const char *msg, ...);
/* */
ts_stream_t *libdvbpsi_init(int debug);
ts_stream_t *libdvbpsi_init(int debug, ts_stream_log_cb pf_log, void *cb_data);
bool libdvbpsi_process(ts_stream_t *stream, uint8_t *buf, ssize_t length, mtime_t date);
void libdvbpsi_summary(ts_stream_t *stream);
void libdvbpsi_summary(FILE *fd, ts_stream_t *stream, const int summary_mode);
void libdvbpsi_exit(ts_stream_t *stream);
#endif
......@@ -97,6 +97,12 @@ struct dvbpsi_s
/* Messages callback */
dvbpsi_message_cb pf_message; /*!< Log message callback */
enum dvbpsi_msg_level i_msg_level; /*!< Log level */
/* private data pointer for use by caller, not by libdvbpsi itself ! */
void *p_sys; /*!< pointer to private data
from caller. Do not use
from inside libdvbpsi. It
will crash any application. */
};
/*****************************************************************************
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment