* If the Program Stream Information changes during a subscription,
react and send a subscriptionStop + subscriptionStart. This happens on SVT (in sweden) when the transmission switches from local to nationwide broadcast (AC3 audio is only present in nationwide broadcast) Ticket #78
This commit is contained in:
parent
f2538a4c46
commit
b37177433c
6 changed files with 262 additions and 137 deletions
7
debian/changelog
vendored
7
debian/changelog
vendored
|
@ -46,6 +46,13 @@ hts-tvheadend (2.3) hts; urgency=low
|
|||
* Remove configuration and settings (/home/hts/.hts/tvheadend) on a
|
||||
deb package purge operation. Ticket #73
|
||||
|
||||
* If the Program Stream Information changes during a subscription,
|
||||
react and send a subscriptionStop + subscriptionStart.
|
||||
This happens on SVT (in sweden) when the transmission switches
|
||||
from local to nationwide broadcast (AC3 audio is only present
|
||||
in nationwide broadcast)
|
||||
Ticket #78
|
||||
|
||||
hts-tvheadend (2.2) hts; urgency=low
|
||||
|
||||
* Set $HOME so forked processes (XMLTV) will have correct environment
|
||||
|
|
|
@ -45,8 +45,53 @@
|
|||
#include "dvb_support.h"
|
||||
#include "notify.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
dvb_transport_open_demuxers(th_dvb_adapter_t *tda, th_transport_t *t)
|
||||
{
|
||||
struct dmx_pes_filter_params dmx_param;
|
||||
int fd;
|
||||
th_stream_t *st;
|
||||
|
||||
/*
|
||||
LIST_FOREACH(st, &t->tht_components, st_link) {
|
||||
if(st->st_demuxer_fd != -1)
|
||||
continue;
|
||||
|
||||
fd = open(tda->tda_demux_path, O_RDWR);
|
||||
st->st_cc_valid = 0;
|
||||
|
||||
if(fd == -1) {
|
||||
st->st_demuxer_fd = -1;
|
||||
tvhlog(LOG_ERR, "dvb",
|
||||
"\"%s\" unable to open demuxer \"%s\" for pid %d -- %s",
|
||||
t->tht_name, tda->tda_demux_path, st->st_pid, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
memset(&dmx_param, 0, sizeof(dmx_param));
|
||||
dmx_param.pid = st->st_pid;
|
||||
dmx_param.input = DMX_IN_FRONTEND;
|
||||
dmx_param.output = DMX_OUT_TS_TAP;
|
||||
dmx_param.pes_type = DMX_PES_OTHER;
|
||||
dmx_param.flags = DMX_IMMEDIATE_START;
|
||||
|
||||
if(ioctl(fd, DMX_SET_PES_FILTER, &dmx_param)) {
|
||||
tvhlog(LOG_ERR, "dvb",
|
||||
"\"%s\" unable to configure demuxer \"%s\" for pid %d -- %s",
|
||||
t->tht_name, tda->tda_demux_path, st->st_pid, strerror(errno));
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
|
||||
st->st_demuxer_fd = fd;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Switch the adapter (which is implicitly tied to our transport)
|
||||
* to receive the given transport.
|
||||
*
|
||||
|
@ -57,9 +102,7 @@ static int
|
|||
dvb_transport_start(th_transport_t *t, unsigned int weight, int status,
|
||||
int force_start)
|
||||
{
|
||||
struct dmx_pes_filter_params dmx_param;
|
||||
th_stream_t *st;
|
||||
int w, fd, pid;
|
||||
int w;
|
||||
th_dvb_adapter_t *tda = t->tht_dvb_mux_instance->tdmi_adapter;
|
||||
th_dvb_mux_instance_t *tdmi = tda->tda_mux_current;
|
||||
|
||||
|
@ -81,50 +124,16 @@ dvb_transport_start(th_transport_t *t, unsigned int weight, int status,
|
|||
|
||||
dvb_adapter_clean(tda);
|
||||
}
|
||||
tdmi = t->tht_dvb_mux_instance;
|
||||
|
||||
LIST_FOREACH(st, &t->tht_components, st_link) {
|
||||
fd = open(tda->tda_demux_path, O_RDWR);
|
||||
|
||||
pid = st->st_pid;
|
||||
st->st_cc_valid = 0;
|
||||
|
||||
if(fd == -1) {
|
||||
st->st_demuxer_fd = -1;
|
||||
tvhlog(LOG_ERR, "dvb",
|
||||
"\"%s\" unable to open demuxer \"%s\" for pid %d -- %s",
|
||||
t->tht_name, tda->tda_demux_path, pid, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
memset(&dmx_param, 0, sizeof(dmx_param));
|
||||
dmx_param.pid = pid;
|
||||
dmx_param.input = DMX_IN_FRONTEND;
|
||||
dmx_param.output = DMX_OUT_TS_TAP;
|
||||
dmx_param.pes_type = DMX_PES_OTHER;
|
||||
dmx_param.flags = DMX_IMMEDIATE_START;
|
||||
|
||||
if(ioctl(fd, DMX_SET_PES_FILTER, &dmx_param)) {
|
||||
tvhlog(LOG_ERR, "dvb",
|
||||
"\"%s\" unable to configure demuxer \"%s\" for pid %d -- %s",
|
||||
t->tht_name, tda->tda_demux_path, pid, strerror(errno));
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
|
||||
st->st_demuxer_fd = fd;
|
||||
}
|
||||
dvb_transport_open_demuxers(tda, t);
|
||||
|
||||
pthread_mutex_lock(&tda->tda_delivery_mutex);
|
||||
|
||||
LIST_INSERT_HEAD(&tda->tda_transports, t, tht_active_link);
|
||||
t->tht_status = status;
|
||||
|
||||
|
||||
dvb_fe_tune(tdmi, "Transport start");
|
||||
dvb_fe_tune(t->tht_dvb_mux_instance, "Transport start");
|
||||
|
||||
pthread_mutex_unlock(&tda->tda_delivery_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -145,13 +154,28 @@ dvb_transport_stop(th_transport_t *t)
|
|||
pthread_mutex_unlock(&tda->tda_delivery_mutex);
|
||||
|
||||
LIST_FOREACH(st, &t->tht_components, st_link) {
|
||||
close(st->st_demuxer_fd);
|
||||
st->st_demuxer_fd = -1;
|
||||
if(st->st_demuxer_fd != -1) {
|
||||
close(st->st_demuxer_fd);
|
||||
st->st_demuxer_fd = -1;
|
||||
}
|
||||
}
|
||||
t->tht_status = TRANSPORT_IDLE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
dvb_transport_refresh(th_transport_t *t)
|
||||
{
|
||||
th_dvb_adapter_t *tda = t->tht_dvb_mux_instance->tdmi_adapter;
|
||||
|
||||
lock_assert(&global_lock);
|
||||
dvb_transport_open_demuxers(tda, t);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Load config for the given mux
|
||||
|
@ -330,6 +354,7 @@ dvb_transport_find(th_dvb_mux_instance_t *tdmi, uint16_t sid, int pmt_pid,
|
|||
t->tht_pmt_pid = pmt_pid;
|
||||
|
||||
t->tht_start_feed = dvb_transport_start;
|
||||
t->tht_refresh_feed = dvb_transport_refresh;
|
||||
t->tht_stop_feed = dvb_transport_stop;
|
||||
t->tht_config_change = dvb_transport_save;
|
||||
t->tht_sourcename = dvb_transport_sourcename;
|
||||
|
|
39
src/psi.c
39
src/psi.c
|
@ -181,6 +181,8 @@ psi_desc_ca(th_transport_t *t, uint8_t *ptr)
|
|||
r = 1;
|
||||
}
|
||||
|
||||
st->st_delete_me = 0;
|
||||
|
||||
if(st->st_caid != caid) {
|
||||
st->st_caid = caid;
|
||||
r = 1;
|
||||
|
@ -200,10 +202,10 @@ psi_parse_pmt(th_transport_t *t, uint8_t *ptr, int len, int chksvcid)
|
|||
uint8_t dtag, dlen;
|
||||
uint16_t sid;
|
||||
streaming_component_type_t hts_stream_type;
|
||||
th_stream_t *st;
|
||||
th_stream_t *st, *next;
|
||||
char lang[4];
|
||||
int frameduration;
|
||||
int need_save = 0;
|
||||
int update = 0;
|
||||
|
||||
if(len < 9)
|
||||
return -1;
|
||||
|
@ -225,12 +227,16 @@ psi_parse_pmt(th_transport_t *t, uint8_t *ptr, int len, int chksvcid)
|
|||
|
||||
if(t->tht_pcr_pid != pcr_pid) {
|
||||
t->tht_pcr_pid = pcr_pid;
|
||||
need_save = 1;
|
||||
update = 1;
|
||||
}
|
||||
|
||||
ptr += 9;
|
||||
len -= 9;
|
||||
|
||||
/* Mark all streams for deletion */
|
||||
LIST_FOREACH(st, &t->tht_components, st_link)
|
||||
st->st_delete_me = 1;
|
||||
|
||||
while(dllen > 1) {
|
||||
dtag = ptr[0];
|
||||
dlen = ptr[1];
|
||||
|
@ -241,7 +247,7 @@ psi_parse_pmt(th_transport_t *t, uint8_t *ptr, int len, int chksvcid)
|
|||
|
||||
switch(dtag) {
|
||||
case DVB_DESC_CA:
|
||||
need_save |= psi_desc_ca(t, ptr);
|
||||
update |= psi_desc_ca(t, ptr);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -294,7 +300,7 @@ psi_parse_pmt(th_transport_t *t, uint8_t *ptr, int len, int chksvcid)
|
|||
|
||||
switch(dtag) {
|
||||
case DVB_DESC_CA:
|
||||
need_save |= psi_desc_ca(t, ptr);
|
||||
update |= psi_desc_ca(t, ptr);
|
||||
break;
|
||||
|
||||
case DVB_DESC_VIDEO_STREAM:
|
||||
|
@ -329,27 +335,40 @@ psi_parse_pmt(th_transport_t *t, uint8_t *ptr, int len, int chksvcid)
|
|||
if(hts_stream_type != 0) {
|
||||
|
||||
if((st = transport_stream_find(t, pid)) == NULL) {
|
||||
need_save = 1;
|
||||
update = 1;
|
||||
st = transport_stream_create(t, pid, hts_stream_type);
|
||||
}
|
||||
|
||||
st->st_delete_me = 0;
|
||||
|
||||
st->st_tb = (AVRational){1, 90000};
|
||||
|
||||
if(memcmp(st->st_lang, lang, 4)) {
|
||||
need_save = 1;
|
||||
update = 1;
|
||||
memcpy(st->st_lang, lang, 4);
|
||||
}
|
||||
|
||||
if(st->st_frame_duration == 0 && frameduration != 0) {
|
||||
st->st_frame_duration = frameduration;
|
||||
need_save = 1;
|
||||
update = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(need_save)
|
||||
t->tht_config_change(t);
|
||||
/* Scan again to see if any streams should be deleted */
|
||||
for(st = LIST_FIRST(&t->tht_components); st != NULL; st = next) {
|
||||
next = LIST_NEXT(st, st_link);
|
||||
if(st->st_delete_me) {
|
||||
transport_stream_destroy(t, st);
|
||||
update = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(update) {
|
||||
t->tht_config_change(t);
|
||||
if(t->tht_status == TRANSPORT_RUNNING)
|
||||
transport_restart(t);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
234
src/transports.c
234
src/transports.c
|
@ -53,6 +53,121 @@ static struct th_transport_list transporthash[TRANSPORT_HASH_WIDTH];
|
|||
static void transport_data_timeout(void *aux);
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
stream_init(th_stream_t *st)
|
||||
{
|
||||
AVCodec *c;
|
||||
enum CodecID id;
|
||||
|
||||
st->st_startcond = 0xffffffff;
|
||||
st->st_curdts = AV_NOPTS_VALUE;
|
||||
st->st_curpts = AV_NOPTS_VALUE;
|
||||
st->st_prevdts = AV_NOPTS_VALUE;
|
||||
|
||||
st->st_last_dts = AV_NOPTS_VALUE;
|
||||
st->st_dts_epoch = 0;
|
||||
|
||||
st->st_pcr_real_last = AV_NOPTS_VALUE;
|
||||
st->st_pcr_last = AV_NOPTS_VALUE;
|
||||
st->st_pcr_drift = 0;
|
||||
st->st_pcr_recovery_fails = 0;
|
||||
/* Open ffmpeg context and parser */
|
||||
|
||||
switch(st->st_type) {
|
||||
case SCT_MPEG2VIDEO: id = CODEC_ID_MPEG2VIDEO; break;
|
||||
case SCT_MPEG2AUDIO: id = CODEC_ID_MP3; break;
|
||||
case SCT_H264: id = CODEC_ID_H264; break;
|
||||
case SCT_AC3: id = CODEC_ID_AC3; break;
|
||||
default: id = CODEC_ID_NONE; break;
|
||||
}
|
||||
|
||||
assert(st->st_ctx == NULL);
|
||||
assert(st->st_parser == NULL);
|
||||
|
||||
|
||||
if(id == CODEC_ID_NONE)
|
||||
return;
|
||||
c = avcodec_find_decoder(id);
|
||||
if(c != NULL) {
|
||||
st->st_ctx = avcodec_alloc_context();
|
||||
pthread_mutex_lock(&ffmpeg_lock);
|
||||
avcodec_open(st->st_ctx, c);
|
||||
pthread_mutex_unlock(&ffmpeg_lock);
|
||||
st->st_parser = av_parser_init(id);
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
stream_clean(th_stream_t *st)
|
||||
{
|
||||
if(st->st_demuxer_fd != -1) {
|
||||
// XXX: Should be in DVB-code perhaps
|
||||
close(st->st_demuxer_fd);
|
||||
st->st_demuxer_fd = -1;
|
||||
}
|
||||
|
||||
if(st->st_parser != NULL)
|
||||
av_parser_close(st->st_parser);
|
||||
|
||||
if(st->st_ctx != NULL) {
|
||||
pthread_mutex_lock(&ffmpeg_lock);
|
||||
avcodec_close(st->st_ctx);
|
||||
pthread_mutex_unlock(&ffmpeg_lock);
|
||||
}
|
||||
|
||||
av_free(st->st_ctx);
|
||||
|
||||
st->st_parser = NULL;
|
||||
st->st_ctx = NULL;
|
||||
|
||||
free(st->st_priv);
|
||||
st->st_priv = NULL;
|
||||
|
||||
/* Clear reassembly buffer */
|
||||
|
||||
free(st->st_buffer);
|
||||
st->st_buffer = NULL;
|
||||
st->st_buffer_size = 0;
|
||||
st->st_buffer_ptr = 0;
|
||||
st->st_startcode = 0;
|
||||
|
||||
if(st->st_curpkt != NULL) {
|
||||
pkt_ref_dec(st->st_curpkt);
|
||||
st->st_curpkt = NULL;
|
||||
}
|
||||
|
||||
/* Clear PTS queue */
|
||||
|
||||
pktref_clear_queue(&st->st_ptsq);
|
||||
st->st_ptsq_len = 0;
|
||||
|
||||
/* Clear durationq */
|
||||
|
||||
pktref_clear_queue(&st->st_durationq);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
transport_stream_destroy(th_transport_t *t, th_stream_t *st)
|
||||
{
|
||||
if(t->tht_status == TRANSPORT_RUNNING)
|
||||
stream_clean(st);
|
||||
LIST_REMOVE(st, st_link);
|
||||
free(st);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transport lock must be held
|
||||
*/
|
||||
|
@ -79,47 +194,8 @@ transport_stop(th_transport_t *t)
|
|||
/**
|
||||
* Clean up each stream
|
||||
*/
|
||||
LIST_FOREACH(st, &t->tht_components, st_link) {
|
||||
|
||||
if(st->st_parser != NULL)
|
||||
av_parser_close(st->st_parser);
|
||||
|
||||
if(st->st_ctx != NULL) {
|
||||
pthread_mutex_lock(&ffmpeg_lock);
|
||||
avcodec_close(st->st_ctx);
|
||||
pthread_mutex_unlock(&ffmpeg_lock);
|
||||
}
|
||||
|
||||
av_free(st->st_ctx);
|
||||
|
||||
st->st_parser = NULL;
|
||||
st->st_ctx = NULL;
|
||||
|
||||
free(st->st_priv);
|
||||
st->st_priv = NULL;
|
||||
|
||||
/* Clear reassembly buffer */
|
||||
|
||||
free(st->st_buffer);
|
||||
st->st_buffer = NULL;
|
||||
st->st_buffer_size = 0;
|
||||
st->st_buffer_ptr = 0;
|
||||
st->st_startcode = 0;
|
||||
|
||||
if(st->st_curpkt != NULL) {
|
||||
pkt_ref_dec(st->st_curpkt);
|
||||
st->st_curpkt = NULL;
|
||||
}
|
||||
|
||||
/* Clear PTS queue */
|
||||
|
||||
pktref_clear_queue(&st->st_ptsq);
|
||||
st->st_ptsq_len = 0;
|
||||
|
||||
/* Clear durationq */
|
||||
|
||||
pktref_clear_queue(&st->st_durationq);
|
||||
}
|
||||
LIST_FOREACH(st, &t->tht_components, st_link)
|
||||
stream_clean(st);
|
||||
|
||||
pthread_mutex_unlock(&t->tht_stream_mutex);
|
||||
}
|
||||
|
@ -157,8 +233,6 @@ int
|
|||
transport_start(th_transport_t *t, unsigned int weight, int force_start)
|
||||
{
|
||||
th_stream_t *st;
|
||||
AVCodec *c;
|
||||
enum CodecID id;
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
|
@ -173,46 +247,8 @@ transport_start(th_transport_t *t, unsigned int weight, int force_start)
|
|||
/**
|
||||
* Initialize stream
|
||||
*/
|
||||
LIST_FOREACH(st, &t->tht_components, st_link) {
|
||||
st->st_startcond = 0xffffffff;
|
||||
st->st_curdts = AV_NOPTS_VALUE;
|
||||
st->st_curpts = AV_NOPTS_VALUE;
|
||||
st->st_prevdts = AV_NOPTS_VALUE;
|
||||
|
||||
st->st_last_dts = AV_NOPTS_VALUE;
|
||||
st->st_dts_epoch = 0;
|
||||
|
||||
st->st_pcr_real_last = AV_NOPTS_VALUE;
|
||||
st->st_pcr_last = AV_NOPTS_VALUE;
|
||||
st->st_pcr_drift = 0;
|
||||
st->st_pcr_recovery_fails = 0;
|
||||
/* Open ffmpeg context and parser */
|
||||
|
||||
switch(st->st_type) {
|
||||
case SCT_MPEG2VIDEO: id = CODEC_ID_MPEG2VIDEO; break;
|
||||
case SCT_MPEG2AUDIO: id = CODEC_ID_MP3; break;
|
||||
case SCT_H264: id = CODEC_ID_H264; break;
|
||||
case SCT_AC3: id = CODEC_ID_AC3; break;
|
||||
default: id = CODEC_ID_NONE; break;
|
||||
}
|
||||
|
||||
assert(st->st_ctx == NULL);
|
||||
assert(st->st_parser == NULL);
|
||||
|
||||
|
||||
if(id != CODEC_ID_NONE) {
|
||||
c = avcodec_find_decoder(id);
|
||||
if(c != NULL) {
|
||||
st->st_ctx = avcodec_alloc_context();
|
||||
pthread_mutex_lock(&ffmpeg_lock);
|
||||
avcodec_open(st->st_ctx, c);
|
||||
pthread_mutex_unlock(&ffmpeg_lock);
|
||||
st->st_parser = av_parser_init(id);
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
LIST_FOREACH(st, &t->tht_components, st_link)
|
||||
stream_init(st);
|
||||
|
||||
cwc_transport_start(t);
|
||||
|
||||
|
@ -478,10 +514,12 @@ transport_stream_create(th_transport_t *t, int pid,
|
|||
{
|
||||
th_stream_t *st;
|
||||
int i = 0;
|
||||
|
||||
int idx = 0;
|
||||
lock_assert(&t->tht_stream_mutex);
|
||||
|
||||
LIST_FOREACH(st, &t->tht_components, st_link) {
|
||||
if(st->st_index > idx)
|
||||
idx = st->st_index;
|
||||
i++;
|
||||
if(pid != -1 && st->st_pid == pid)
|
||||
return st;
|
||||
|
@ -492,7 +530,7 @@ transport_stream_create(th_transport_t *t, int pid,
|
|||
t->tht_identifier, streaming_component_type2txt(type), pid);
|
||||
|
||||
st = calloc(1, sizeof(th_stream_t));
|
||||
st->st_index = i;
|
||||
st->st_index = idx + 1;
|
||||
st->st_type = type;
|
||||
LIST_INSERT_HEAD(&t->tht_components, st, st_link);
|
||||
|
||||
|
@ -505,6 +543,9 @@ transport_stream_create(th_transport_t *t, int pid,
|
|||
avgstat_init(&st->st_rate, 10);
|
||||
avgstat_init(&st->st_cc_errors, 10);
|
||||
|
||||
if(t->tht_status == TRANSPORT_RUNNING)
|
||||
stream_init(st);
|
||||
|
||||
return st;
|
||||
}
|
||||
|
||||
|
@ -627,6 +668,29 @@ transport_set_feed_status(th_transport_t *t, transport_feed_status_t newstatus)
|
|||
newstatus));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restart output on a transport.
|
||||
* Happens if the stream composition changes.
|
||||
* (i.e. an AC3 stream disappears, etc)
|
||||
*/
|
||||
void
|
||||
transport_restart(th_transport_t *t)
|
||||
{
|
||||
streaming_message_t *sm;
|
||||
lock_assert(&t->tht_stream_mutex);
|
||||
|
||||
sm = streaming_msg_create_msg(SMT_STOP, htsmsg_create_map());
|
||||
streaming_pad_deliver(&t->tht_streaming_pad, sm);
|
||||
|
||||
t->tht_refresh_feed(t);
|
||||
|
||||
sm = streaming_msg_create_msg(SMT_START,
|
||||
transport_build_stream_start_msg(t));
|
||||
streaming_pad_deliver(&t->tht_streaming_pad, sm);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a message containing info about all components
|
||||
*
|
||||
|
|
|
@ -78,4 +78,8 @@ htsmsg_t *transport_build_stream_start_msg(th_transport_t *t);
|
|||
|
||||
void transport_set_enable(th_transport_t *t, int enabled);
|
||||
|
||||
void transport_restart(th_transport_t *t);
|
||||
|
||||
void transport_stream_destroy(th_transport_t *t, th_stream_t *st);
|
||||
|
||||
#endif /* TRANSPORTS_H */
|
||||
|
|
|
@ -312,6 +312,10 @@ typedef struct th_stream {
|
|||
int st_vbv_size; /* Video buffer size (in bytes) */
|
||||
int st_vbv_delay; /* -1 if CBR */
|
||||
|
||||
/* */
|
||||
|
||||
int st_delete_me; /* Temporary flag for deleting streams */
|
||||
|
||||
} th_stream_t;
|
||||
|
||||
|
||||
|
@ -438,6 +442,8 @@ typedef struct th_transport {
|
|||
int (*tht_start_feed)(struct th_transport *t, unsigned int weight,
|
||||
int status, int force_start);
|
||||
|
||||
void (*tht_refresh_feed)(struct th_transport *t);
|
||||
|
||||
void (*tht_stop_feed)(struct th_transport *t);
|
||||
|
||||
void (*tht_config_change)(struct th_transport *t);
|
||||
|
|
Loading…
Add table
Reference in a new issue