Add DVB subtitle support. Ticket #39
This commit is contained in:
parent
b0399bdeea
commit
3ee73a5b81
8 changed files with 147 additions and 14 deletions
4
debian/changelog
vendored
4
debian/changelog
vendored
|
@ -1,3 +1,7 @@
|
|||
hts-tvheadend (2.7) hts; urgency=low
|
||||
|
||||
* Added support for DVB subtitles. Currently only forwarded over HTSP
|
||||
|
||||
hts-tvheadend (2.6) hts; urgency=low
|
||||
|
||||
* Tvheadend's DVB service probe will now make the channel join tags based
|
||||
|
|
|
@ -1418,6 +1418,12 @@ htsp_subscription_start(htsp_subscription_t *hs, const streaming_start_t *ss)
|
|||
htsmsg_add_str(c, "type", streaming_component_type2txt(ssc->ssc_type));
|
||||
if(ssc->ssc_lang[0])
|
||||
htsmsg_add_str(c, "language", ssc->ssc_lang);
|
||||
|
||||
if(ssc->ssc_type == SCT_SUBTITLES) {
|
||||
htsmsg_add_u32(c, "composition_id", ssc->ssc_composition_id);
|
||||
htsmsg_add_u32(c, "ancillary_id", ssc->ssc_ancillary_id);
|
||||
}
|
||||
|
||||
htsmsg_add_msg(streams, NULL, c);
|
||||
}
|
||||
|
||||
|
|
|
@ -92,6 +92,9 @@ static void parse_audio(th_transport_t *t, th_stream_t *st, uint8_t *data,
|
|||
static void parse_aac(th_transport_t *t, th_stream_t *st, uint8_t *data,
|
||||
int len, int start);
|
||||
|
||||
static void parse_subtitles(th_transport_t *t, th_stream_t *st, uint8_t *data,
|
||||
int len, int start);
|
||||
|
||||
static void parse_mpegaudio(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt);
|
||||
|
||||
static void parse_ac3(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt);
|
||||
|
@ -99,7 +102,8 @@ static void parse_ac3(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt);
|
|||
static void parse_compute_pts(th_transport_t *t, th_stream_t *st,
|
||||
th_pkt_t *pkt);
|
||||
|
||||
static void parser_deliver(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt);
|
||||
static void parser_deliver(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt,
|
||||
int checkts);
|
||||
|
||||
static int parse_pes_header(th_transport_t *t, th_stream_t *st, uint8_t *buf,
|
||||
size_t len);
|
||||
|
@ -130,6 +134,10 @@ parse_mpeg_ts(th_transport_t *t, th_stream_t *st, uint8_t *data,
|
|||
case SCT_AC3:
|
||||
parse_audio(t, st, data, len, start, parse_ac3);
|
||||
break;
|
||||
|
||||
case SCT_SUBTITLES:
|
||||
parse_subtitles(t, st, data, len, start);
|
||||
break;
|
||||
|
||||
case SCT_AAC:
|
||||
if(0) // not yet
|
||||
|
@ -441,7 +449,7 @@ parse_mpegaudio(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt)
|
|||
pkt->pkt_duration = duration;
|
||||
st->st_nextdts = pkt->pkt_dts + duration;
|
||||
|
||||
parser_deliver(t, st, pkt);
|
||||
parser_deliver(t, st, pkt, 1);
|
||||
}
|
||||
|
||||
|
||||
|
@ -517,7 +525,7 @@ parse_ac3(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt)
|
|||
|
||||
pkt->pkt_duration = duration;
|
||||
st->st_nextdts = pkt->pkt_dts + duration;
|
||||
parser_deliver(t, st, pkt);
|
||||
parser_deliver(t, st, pkt, 1);
|
||||
}
|
||||
|
||||
|
||||
|
@ -833,7 +841,7 @@ parse_h264(th_transport_t *t, th_stream_t *st, size_t len,
|
|||
|
||||
st->st_curpkt->pkt_payload = st->st_buffer;
|
||||
st->st_curpkt->pkt_payloadlen = st->st_buffer_ptr;
|
||||
parser_deliver(t, st, st->st_curpkt);
|
||||
parser_deliver(t, st, st->st_curpkt, 1);
|
||||
|
||||
st->st_curpkt = NULL;
|
||||
st->st_buffer = malloc(st->st_buffer_size);
|
||||
|
@ -842,6 +850,68 @@ parse_h264(th_transport_t *t, th_stream_t *st, size_t len,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* http://broadcasting.ru/pdf-standard-specifications/subtitling/dvb-sub/en300743.v1.2.1.pdf
|
||||
*/
|
||||
static void
|
||||
parse_subtitles(th_transport_t *t, th_stream_t *st, uint8_t *data,
|
||||
int len, int start)
|
||||
{
|
||||
th_pkt_t *pkt;
|
||||
int psize, hlen;
|
||||
uint8_t *buf;
|
||||
|
||||
if(start) {
|
||||
/* Payload unit start */
|
||||
st->st_parser_state = 1;
|
||||
st->st_buffer_ptr = 0;
|
||||
}
|
||||
|
||||
if(st->st_parser_state == 0)
|
||||
return;
|
||||
|
||||
if(st->st_buffer == NULL) {
|
||||
st->st_buffer_size = 4000;
|
||||
st->st_buffer = malloc(st->st_buffer_size);
|
||||
}
|
||||
|
||||
if(st->st_buffer_ptr + len >= st->st_buffer_size) {
|
||||
st->st_buffer_size += len * 4;
|
||||
st->st_buffer = realloc(st->st_buffer, st->st_buffer_size);
|
||||
}
|
||||
|
||||
memcpy(st->st_buffer + st->st_buffer_ptr, data, len);
|
||||
st->st_buffer_ptr += len;
|
||||
|
||||
if(st->st_buffer_ptr < 6)
|
||||
return;
|
||||
|
||||
psize = st->st_buffer[4] << 8 | st->st_buffer[5];
|
||||
|
||||
if(st->st_buffer_ptr != psize + 6)
|
||||
return;
|
||||
|
||||
st->st_parser_state = 0;
|
||||
|
||||
hlen = parse_pes_header(t, st, st->st_buffer + 6, st->st_buffer_ptr - 6);
|
||||
if(hlen < 0)
|
||||
return;
|
||||
|
||||
psize -= 6 + hlen;
|
||||
buf = st->st_buffer + 6 + hlen;
|
||||
|
||||
if(psize < 2 || buf[0] != 0x20 || buf[1] != 0x00)
|
||||
return;
|
||||
|
||||
psize -= 2;
|
||||
buf += 2;
|
||||
|
||||
if(psize >= 6) {
|
||||
pkt = pkt_alloc(buf, psize, st->st_curpts, st->st_curdts);
|
||||
pkt->pkt_commercial = t->tht_tt_commercial_advice;
|
||||
parser_deliver(t, st, pkt, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
@ -860,7 +930,7 @@ parse_compute_pts(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt)
|
|||
|
||||
/* PTS known and no other packets in queue, deliver at once */
|
||||
if(validpts && pkt->pkt_duration)
|
||||
return parser_deliver(t, st, pkt);
|
||||
return parser_deliver(t, st, pkt, 1);
|
||||
|
||||
|
||||
/* Reference count is transfered to queue */
|
||||
|
@ -910,7 +980,7 @@ parse_compute_pts(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt)
|
|||
if(pkt->pkt_duration == 0) {
|
||||
parser_compute_duration(t, st, pr);
|
||||
} else {
|
||||
parser_deliver(t, st, pkt);
|
||||
parser_deliver(t, st, pkt, 1);
|
||||
free(pr);
|
||||
}
|
||||
}
|
||||
|
@ -938,7 +1008,7 @@ parser_compute_duration(th_transport_t *t, th_stream_t *st, th_pktref_t *pr)
|
|||
pkt_ref_dec(pr->pr_pkt);
|
||||
} else {
|
||||
pr->pr_pkt->pkt_duration = d;
|
||||
parser_deliver(t, st, pr->pr_pkt);
|
||||
parser_deliver(t, st, pr->pr_pkt, 1);
|
||||
}
|
||||
free(pr);
|
||||
}
|
||||
|
@ -949,13 +1019,13 @@ parser_compute_duration(th_transport_t *t, th_stream_t *st, th_pktref_t *pr)
|
|||
* De-wrap and normalize PTS/DTS to 1MHz clock domain
|
||||
*/
|
||||
static void
|
||||
parser_deliver(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt)
|
||||
parser_deliver(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt,
|
||||
int checkts)
|
||||
{
|
||||
int64_t dts, pts, ptsoff, d;
|
||||
|
||||
assert(pkt->pkt_dts != AV_NOPTS_VALUE);
|
||||
assert(pkt->pkt_pts != AV_NOPTS_VALUE);
|
||||
assert(pkt->pkt_duration > 10);
|
||||
|
||||
if(t->tht_dts_start == AV_NOPTS_VALUE) {
|
||||
pkt_ref_dec(pkt);
|
||||
|
@ -977,7 +1047,7 @@ parser_deliver(th_transport_t *t, th_stream_t *st, th_pkt_t *pkt)
|
|||
pkt_ref_dec(pkt);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
} else if(checkts) {
|
||||
d = dts + st->st_dts_epoch - st->st_last_dts;
|
||||
|
||||
if(d < 0 || d > 90000) {
|
||||
|
|
44
src/psi.c
44
src/psi.c
|
@ -277,6 +277,8 @@ psi_parse_pmt(th_transport_t *t, const uint8_t *ptr, int len, int chksvcid,
|
|||
int frameduration;
|
||||
int update = 0;
|
||||
int had_components;
|
||||
int composition_id;
|
||||
int ancillary_id;
|
||||
|
||||
if(len < 9)
|
||||
return -1;
|
||||
|
@ -339,8 +341,12 @@ psi_parse_pmt(th_transport_t *t, const uint8_t *ptr, int len, int chksvcid,
|
|||
|
||||
ptr += 5;
|
||||
len -= 5;
|
||||
|
||||
frameduration = 0;
|
||||
hts_stream_type = 0;
|
||||
memset(lang, 0, 4);
|
||||
composition_id = -1;
|
||||
ancillary_id = -1;
|
||||
|
||||
switch(estype) {
|
||||
case 0x01:
|
||||
|
@ -362,8 +368,6 @@ psi_parse_pmt(th_transport_t *t, const uint8_t *ptr, int len, int chksvcid,
|
|||
break;
|
||||
}
|
||||
|
||||
memset(lang, 0, 4);
|
||||
|
||||
while(dllen > 1) {
|
||||
dtag = ptr[0];
|
||||
dlen = ptr[1];
|
||||
|
@ -406,6 +410,16 @@ psi_parse_pmt(th_transport_t *t, const uint8_t *ptr, int len, int chksvcid,
|
|||
hts_stream_type = SCT_AAC;
|
||||
break;
|
||||
|
||||
case DVB_DESC_SUBTITLE:
|
||||
if(dlen < 8)
|
||||
break;
|
||||
|
||||
memcpy(lang, ptr, 3);
|
||||
composition_id = ptr[4] << 8 | ptr[5];
|
||||
ancillary_id = ptr[6] << 8 | ptr[7];
|
||||
hts_stream_type = SCT_SUBTITLES;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -432,6 +446,16 @@ psi_parse_pmt(th_transport_t *t, const uint8_t *ptr, int len, int chksvcid,
|
|||
st->st_frame_duration = frameduration;
|
||||
update = 1;
|
||||
}
|
||||
|
||||
if(composition_id != -1 && st->st_composition_id != composition_id) {
|
||||
st->st_composition_id = composition_id;
|
||||
update = 1;
|
||||
}
|
||||
|
||||
if(ancillary_id != -1 && st->st_ancillary_id != ancillary_id) {
|
||||
st->st_ancillary_id = ancillary_id;
|
||||
update = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -445,6 +469,9 @@ psi_parse_pmt(th_transport_t *t, const uint8_t *ptr, int len, int chksvcid,
|
|||
}
|
||||
|
||||
if(update) {
|
||||
tvhlog(LOG_DEBUG, "PSI", "Transport %s (%s) updated due to PMT change",
|
||||
t->tht_svcname, t->tht_identifier);
|
||||
|
||||
transport_request_save(t);
|
||||
if(t->tht_status == TRANSPORT_RUNNING)
|
||||
transport_restart(t, had_components);
|
||||
|
@ -721,6 +748,11 @@ psi_save_transport_settings(htsmsg_t *m, th_transport_t *t)
|
|||
htsmsg_add_u32(sub, "caproviderid", st->st_providerid);
|
||||
}
|
||||
|
||||
if(st->st_type == SCT_SUBTITLES) {
|
||||
htsmsg_add_u32(sub, "compositionid", st->st_composition_id);
|
||||
htsmsg_add_u32(sub, "ancillartyid", st->st_ancillary_id);
|
||||
}
|
||||
|
||||
if(st->st_frame_duration)
|
||||
htsmsg_add_u32(sub, "frameduration", st->st_frame_duration);
|
||||
|
||||
|
@ -783,5 +815,13 @@ psi_load_transport_settings(htsmsg_t *m, th_transport_t *t)
|
|||
}
|
||||
|
||||
htsmsg_get_u32(c, "caproviderid", &st->st_providerid);
|
||||
|
||||
if(type == SCT_SUBTITLES) {
|
||||
if(!htsmsg_get_u32(c, "compositionid", &u32))
|
||||
st->st_composition_id = u32;
|
||||
|
||||
if(!htsmsg_get_u32(c, "ancillartyid", &u32))
|
||||
st->st_ancillary_id = u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,8 @@ typedef struct streaming_start_component {
|
|||
int ssc_index;
|
||||
int ssc_type;
|
||||
char ssc_lang[4];
|
||||
|
||||
uint16_t ssc_composition_id;
|
||||
uint16_t ssc_ancillary_id;
|
||||
} streaming_start_component_t;
|
||||
|
||||
|
||||
|
|
|
@ -731,6 +731,8 @@ transport_build_stream_start(th_transport_t *t)
|
|||
ssc->ssc_index = st->st_index;
|
||||
ssc->ssc_type = st->st_type;
|
||||
memcpy(ssc->ssc_lang, st->st_lang, 4);
|
||||
ssc->ssc_composition_id = st->st_composition_id;
|
||||
ssc->ssc_ancillary_id = st->st_ancillary_id;
|
||||
}
|
||||
|
||||
t->tht_setsourceinfo(t, &ss->ss_si);
|
||||
|
|
|
@ -256,6 +256,9 @@ typedef struct th_stream {
|
|||
int st_index;
|
||||
|
||||
char st_lang[4]; /* ISO 639 3-letter language code */
|
||||
uint16_t st_composition_id;
|
||||
uint16_t st_ancillary_id;
|
||||
|
||||
|
||||
uint16_t st_pid;
|
||||
uint8_t st_cc; /* Last CC */
|
||||
|
@ -285,6 +288,7 @@ typedef struct th_stream {
|
|||
int st_buffer_ptr;
|
||||
int st_buffer_size;
|
||||
int st_buffer_errors; /* Errors accumulated for this packet */
|
||||
|
||||
uint32_t st_startcond;
|
||||
uint32_t st_startcode;
|
||||
uint32_t st_startcode_offset;
|
||||
|
|
|
@ -1379,7 +1379,7 @@ extjs_servicedetails(http_connection_t *hc,
|
|||
htsmsg_t *out, *streams, *c;
|
||||
th_transport_t *t;
|
||||
th_stream_t *st;
|
||||
char buf[20];
|
||||
char buf[40];
|
||||
|
||||
pthread_mutex_lock(&global_lock);
|
||||
|
||||
|
@ -1412,6 +1412,12 @@ extjs_servicedetails(http_connection_t *hc,
|
|||
htsmsg_add_str(c, "details", st->st_lang);
|
||||
break;
|
||||
|
||||
case SCT_SUBTITLES:
|
||||
snprintf(buf, sizeof(buf), "%s (%04x %04x)",
|
||||
st->st_lang, st->st_composition_id, st->st_ancillary_id);
|
||||
htsmsg_add_str(c, "details", buf);
|
||||
break;
|
||||
|
||||
case SCT_MPEG2VIDEO:
|
||||
case SCT_H264:
|
||||
buf[0] = 0;
|
||||
|
|
Loading…
Add table
Reference in a new issue