1425 lines
30 KiB
C
1425 lines
30 KiB
C
/*
|
|
* Packet parsing functions
|
|
* Copyright (C) 2007 Andreas Öman
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <pthread.h>
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
|
|
#include "tvheadend.h"
|
|
#include "service.h"
|
|
#include "parsers.h"
|
|
#include "parser_h264.h"
|
|
#include "parser_latm.h"
|
|
#include "bitstream.h"
|
|
#include "packet.h"
|
|
#include "streaming.h"
|
|
|
|
#define PTS_MASK 0x1ffffffffLL
|
|
//#define PTS_MASK 0x7ffffLL
|
|
|
|
#define getu32(b, l) ({ \
|
|
uint32_t x = (b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3]); \
|
|
b+=4; \
|
|
l-=4; \
|
|
x; \
|
|
})
|
|
|
|
#define getu16(b, l) ({ \
|
|
uint16_t x = (b[0] << 8 | b[1]); \
|
|
b+=2; \
|
|
l-=2; \
|
|
x; \
|
|
})
|
|
|
|
#define getu8(b, l) ({ \
|
|
uint8_t x = b[0]; \
|
|
b+=1; \
|
|
l-=1; \
|
|
x; \
|
|
})
|
|
|
|
|
|
static int64_t
|
|
getpts(const uint8_t *p)
|
|
{
|
|
int a = p[0];
|
|
int b = (p[1] << 8) | p[2];
|
|
int c = (p[3] << 8) | p[4];
|
|
|
|
if((a & 1) && (b & 1) && (c & 1)) {
|
|
|
|
return
|
|
((int64_t)((a >> 1) & 0x07) << 30) |
|
|
((int64_t)((b >> 1) ) << 15) |
|
|
((int64_t)((c >> 1) ))
|
|
;
|
|
|
|
} else {
|
|
// Marker bits not present
|
|
return PTS_UNSET;
|
|
}
|
|
}
|
|
|
|
|
|
static int parse_mpeg2video(service_t *t, elementary_stream_t *st, size_t len,
|
|
uint32_t next_startcode, int sc_offset);
|
|
|
|
static int parse_h264(service_t *t, elementary_stream_t *st, size_t len,
|
|
uint32_t next_startcode, int sc_offset);
|
|
|
|
typedef int (packet_parser_t)(service_t *t, elementary_stream_t *st, size_t len,
|
|
uint32_t next_startcode, int sc_offset);
|
|
|
|
typedef void (aparser_t)(service_t *t, elementary_stream_t *st, th_pkt_t *pkt);
|
|
|
|
static void parse_sc(service_t *t, elementary_stream_t *st, const uint8_t *data,
|
|
int len, packet_parser_t *vp);
|
|
|
|
|
|
static void parse_aac(service_t *t, elementary_stream_t *st, const uint8_t *data,
|
|
int len, int start);
|
|
|
|
static void parse_subtitles(service_t *t, elementary_stream_t *st,
|
|
const uint8_t *data, int len, int start);
|
|
|
|
static void parse_teletext(service_t *t, elementary_stream_t *st,
|
|
const uint8_t *data, int len, int start);
|
|
|
|
static int parse_mpa(service_t *t, elementary_stream_t *st, size_t len,
|
|
uint32_t next_startcode, int sc_offset);
|
|
|
|
static int parse_mpa2(service_t *t, elementary_stream_t *st);
|
|
|
|
static int parse_ac3(service_t *t, elementary_stream_t *st, size_t len,
|
|
uint32_t next_startcode, int sc_offset);
|
|
|
|
static int parse_eac3(service_t *t, elementary_stream_t *st, size_t len,
|
|
uint32_t next_startcode, int sc_offset);
|
|
|
|
static int parse_mp4a(service_t *t, elementary_stream_t *st, size_t ilen,
|
|
uint32_t next_startcode, int sc_offset);
|
|
|
|
static void parser_deliver(service_t *t, elementary_stream_t *st, th_pkt_t *pkt,
|
|
int errors);
|
|
|
|
static int parse_pes_header(service_t *t, elementary_stream_t *st,
|
|
const uint8_t *buf, size_t len);
|
|
|
|
/**
|
|
* Parse raw mpeg data
|
|
*/
|
|
void
|
|
parse_mpeg_ts(service_t *t, elementary_stream_t *st, const uint8_t *data,
|
|
int len, int start, int err)
|
|
{
|
|
|
|
if(err)
|
|
sbuf_err(&st->es_buf);
|
|
|
|
switch(st->es_type) {
|
|
case SCT_MPEG2VIDEO:
|
|
parse_sc(t, st, data, len, parse_mpeg2video);
|
|
break;
|
|
|
|
case SCT_H264:
|
|
parse_sc(t, st, data, len, parse_h264);
|
|
break;
|
|
|
|
case SCT_MPEG2AUDIO:
|
|
parse_sc(t, st, data, len, parse_mpa);
|
|
break;
|
|
|
|
case SCT_AC3:
|
|
parse_sc(t, st, data, len, parse_ac3);
|
|
break;
|
|
|
|
case SCT_EAC3:
|
|
parse_sc(t, st, data, len, parse_eac3);
|
|
break;
|
|
|
|
case SCT_MP4A:
|
|
parse_sc(t, st, data, len, parse_mp4a);
|
|
break;
|
|
|
|
case SCT_DVBSUB:
|
|
parse_subtitles(t, st, data, len, start);
|
|
break;
|
|
|
|
case SCT_AAC:
|
|
parse_aac(t, st, data, len, start);
|
|
break;
|
|
|
|
case SCT_TELETEXT:
|
|
parse_teletext(t, st, data, len, start);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Parse program stream, as from V4L2, etc.
|
|
*
|
|
* Note: data does not include startcode and packet length
|
|
*/
|
|
void
|
|
parse_mpeg_ps(service_t *t, elementary_stream_t *st, uint8_t *data, int len)
|
|
{
|
|
int hlen;
|
|
|
|
hlen = parse_pes_header(t, st, data, len);
|
|
#if 0
|
|
int i;
|
|
for(i = 0; i < 16; i++)
|
|
printf("%02x.", data[i]);
|
|
printf(" %d\n", hlen);
|
|
#endif
|
|
data += hlen;
|
|
len -= hlen;
|
|
|
|
if(len < 1)
|
|
return;
|
|
|
|
switch(st->es_type) {
|
|
case SCT_MPEG2AUDIO:
|
|
sbuf_append(&st->es_buf_a, data, len);
|
|
parse_mpa2(t, st);
|
|
break;
|
|
|
|
case SCT_MPEG2VIDEO:
|
|
parse_sc(t, st, data, len, parse_mpeg2video);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse AAC LATM
|
|
*/
|
|
static void
|
|
parse_aac(service_t *t, elementary_stream_t *st, const uint8_t *data,
|
|
int len, int start)
|
|
{
|
|
int l, muxlen, p;
|
|
th_pkt_t *pkt;
|
|
|
|
if(start) {
|
|
/* Payload unit start */
|
|
st->es_parser_state = 1;
|
|
st->es_parser_ptr = 0;
|
|
sbuf_reset(&st->es_buf);
|
|
}
|
|
|
|
if(st->es_parser_state == 0)
|
|
return;
|
|
|
|
sbuf_append(&st->es_buf, data, len);
|
|
|
|
|
|
if(st->es_parser_ptr == 0) {
|
|
int hlen;
|
|
|
|
if(st->es_buf.sb_ptr < 9)
|
|
return;
|
|
|
|
hlen = parse_pes_header(t, st,
|
|
st->es_buf.sb_data + 6, st->es_buf.sb_ptr - 6);
|
|
if(hlen < 0)
|
|
return;
|
|
st->es_parser_ptr += 6 + hlen;
|
|
}
|
|
|
|
|
|
p = st->es_parser_ptr;
|
|
|
|
while((l = st->es_buf.sb_ptr - p) > 3) {
|
|
const uint8_t *d = st->es_buf.sb_data + p;
|
|
if(d[0] == 0x56 && (d[1] & 0xe0) == 0xe0) {
|
|
muxlen = (d[1] & 0x1f) << 8 | d[2];
|
|
|
|
if(l < muxlen + 3)
|
|
break;
|
|
|
|
pkt = parse_latm_audio_mux_element(t, st, d + 3, muxlen);
|
|
|
|
if(pkt != NULL)
|
|
parser_deliver(t, st, pkt, st->es_buf.sb_err);
|
|
|
|
p += muxlen + 3;
|
|
} else {
|
|
p++;
|
|
}
|
|
}
|
|
st->es_parser_ptr = p;
|
|
}
|
|
|
|
|
|
/**
|
|
* Generic video parser
|
|
*
|
|
* We scan for startcodes a'la 0x000001xx and let a specific parser
|
|
* derive further information.
|
|
*/
|
|
static void
|
|
parse_sc(service_t *t, elementary_stream_t *st, const uint8_t *data, int len,
|
|
packet_parser_t *vp)
|
|
{
|
|
uint32_t sc = st->es_startcond;
|
|
int i, r;
|
|
sbuf_alloc(&st->es_buf, len);
|
|
|
|
for(i = 0; i < len; i++) {
|
|
|
|
if(st->es_ssc_intercept == 1) {
|
|
|
|
if(st->es_ssc_ptr < sizeof(st->es_ssc_buf))
|
|
st->es_ssc_buf[st->es_ssc_ptr] = data[i];
|
|
st->es_ssc_ptr++;
|
|
|
|
if(st->es_ssc_ptr < 5)
|
|
continue;
|
|
|
|
uint16_t plen = st->es_ssc_buf[0] << 8 | st->es_ssc_buf[1];
|
|
st->es_incomplete = plen >= 0xffdf;
|
|
|
|
int hlen = st->es_ssc_buf[4];
|
|
|
|
if(st->es_ssc_ptr < hlen + 5)
|
|
continue;
|
|
|
|
parse_pes_header(t, st, st->es_ssc_buf + 2, hlen + 3);
|
|
st->es_ssc_intercept = 0;
|
|
if(st->es_buf.sb_ptr > 2)
|
|
sc = st->es_buf.sb_data[st->es_buf.sb_ptr-3] << 16 |
|
|
st->es_buf.sb_data[st->es_buf.sb_ptr-2] << 8 |
|
|
st->es_buf.sb_data[st->es_buf.sb_ptr-1];
|
|
|
|
continue;
|
|
}
|
|
|
|
st->es_buf.sb_data[st->es_buf.sb_ptr++] = data[i];
|
|
sc = sc << 8 | data[i];
|
|
|
|
if((sc & 0xffffff00) != 0x00000100)
|
|
continue;
|
|
|
|
if(sc == 0x100 && (len-i)>3) {
|
|
uint32_t tempsc = data[i+1] << 16 | data[i+2] << 8 | data[i+3];
|
|
|
|
if(tempsc == 0x1e0)
|
|
continue;
|
|
}
|
|
|
|
r = st->es_buf.sb_ptr - st->es_startcode_offset - 4;
|
|
|
|
if(r > 0 && st->es_startcode != 0) {
|
|
r = vp(t, st, r, sc, st->es_startcode_offset);
|
|
if(r == 3)
|
|
continue;
|
|
if(r == 4) {
|
|
st->es_buf.sb_ptr -= 4;
|
|
st->es_ssc_intercept = 1;
|
|
st->es_ssc_ptr = 0;
|
|
sc = -1;
|
|
continue;
|
|
}
|
|
} else {
|
|
r = 1;
|
|
}
|
|
|
|
assert(st->es_buf.sb_data != NULL);
|
|
|
|
if(r == 2) {
|
|
// Drop packet
|
|
st->es_buf.sb_ptr = st->es_startcode_offset;
|
|
|
|
st->es_buf.sb_data[st->es_buf.sb_ptr++] = sc >> 24;
|
|
st->es_buf.sb_data[st->es_buf.sb_ptr++] = sc >> 16;
|
|
st->es_buf.sb_data[st->es_buf.sb_ptr++] = sc >> 8;
|
|
st->es_buf.sb_data[st->es_buf.sb_ptr++] = sc;
|
|
st->es_startcode = sc;
|
|
|
|
} else {
|
|
if(r == 1) {
|
|
/* Reset packet parser upon length error or if parser
|
|
tells us so */
|
|
sbuf_reset(&st->es_buf);
|
|
st->es_buf.sb_data[st->es_buf.sb_ptr++] = sc >> 24;
|
|
st->es_buf.sb_data[st->es_buf.sb_ptr++] = sc >> 16;
|
|
st->es_buf.sb_data[st->es_buf.sb_ptr++] = sc >> 8;
|
|
st->es_buf.sb_data[st->es_buf.sb_ptr++] = sc;
|
|
}
|
|
st->es_startcode = sc;
|
|
st->es_startcode_offset = st->es_buf.sb_ptr - 4;
|
|
}
|
|
}
|
|
st->es_startcond = sc;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static int
|
|
depacketize(service_t *t, elementary_stream_t *st, size_t len,
|
|
uint32_t next_startcode, int sc_offset)
|
|
{
|
|
const uint8_t *buf = st->es_buf.sb_data + sc_offset;
|
|
uint32_t sc = st->es_startcode;
|
|
int hlen, plen;
|
|
|
|
if((sc != 0x1bd && (sc & ~0x1f) != 0x1c0) || len < 9)
|
|
return 1;
|
|
|
|
plen = (buf[4] << 8) | buf[5];
|
|
|
|
if(plen + 6 > len || next_startcode != sc)
|
|
return 3;
|
|
|
|
if(plen + 6 < len)
|
|
return 1;
|
|
|
|
buf += 6;
|
|
len -= 6;
|
|
|
|
hlen = parse_pes_header(t, st, buf, len);
|
|
|
|
if(hlen < 0)
|
|
return 1;
|
|
|
|
buf += hlen;
|
|
len -= hlen;
|
|
|
|
st->es_buf_a.sb_err = st->es_buf.sb_err;
|
|
|
|
sbuf_append(&st->es_buf_a, buf, len);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
makeapkt(service_t *t, elementary_stream_t *st, const void *buf,
|
|
int len, int64_t dts, int duration, int channels, int sri,
|
|
int errors)
|
|
{
|
|
|
|
th_pkt_t *pkt = pkt_alloc(buf, len, dts, dts);
|
|
|
|
pkt->pkt_commercial = t->s_tt_commercial_advice;
|
|
pkt->pkt_duration = duration;
|
|
pkt->pkt_channels = channels;
|
|
pkt->pkt_sri = sri;
|
|
|
|
parser_deliver(t, st, pkt, errors);
|
|
|
|
st->es_curdts = PTS_UNSET;
|
|
st->es_nextdts = dts + duration;
|
|
}
|
|
|
|
/**
|
|
* Parse AAC MP4A
|
|
*/
|
|
|
|
static const int aac_sample_rates[12] =
|
|
{
|
|
96000,
|
|
88200,
|
|
64000,
|
|
48000,
|
|
44100,
|
|
32000,
|
|
24000,
|
|
22050,
|
|
16000,
|
|
12000,
|
|
11025,
|
|
8000
|
|
};
|
|
|
|
/**
|
|
* Inspect ADTS header
|
|
*/
|
|
static int
|
|
mp4a_valid_frame(const uint8_t *buf)
|
|
{
|
|
return (buf[0] == 0xff) && ((buf[1] & 0xf6) == 0xf0);
|
|
}
|
|
|
|
static int parse_mp4a(service_t *t, elementary_stream_t *st, size_t ilen,
|
|
uint32_t next_startcode, int sc_offset)
|
|
{
|
|
int i, len;
|
|
const uint8_t *buf;
|
|
|
|
if((i = depacketize(t, st, ilen, next_startcode, sc_offset)) != 0)
|
|
return i;
|
|
|
|
again:
|
|
buf = st->es_buf_a.sb_data;
|
|
len = st->es_buf_a.sb_ptr;
|
|
|
|
for(i = 0; i < len - 6; i++) {
|
|
const uint8_t *p = buf + i;
|
|
if(mp4a_valid_frame(p)) {
|
|
|
|
int fsize = ((p[3] & 0x03) << 11) | (p[4] << 3) | ((p[5] & 0xe0) >> 5);
|
|
int sr_index = (p[2] & 0x3c) >> 2;
|
|
int sr = aac_sample_rates[sr_index];
|
|
|
|
|
|
if(sr && fsize) {
|
|
int duration = 90000 * 1024 / sr;
|
|
int64_t dts = st->es_curdts;
|
|
int sri = rate_to_sri(sr);
|
|
|
|
if(dts == PTS_UNSET)
|
|
dts = st->es_nextdts;
|
|
|
|
if(dts != PTS_UNSET && len >= i + fsize + 6 &&
|
|
mp4a_valid_frame(p + fsize)) {
|
|
|
|
int channels = ((p[2] & 0x01) << 2) | ((p[3] & 0xc0) >> 6);
|
|
|
|
makeapkt(t, st, p, fsize, dts, duration, channels, sri,
|
|
st->es_buf_a.sb_err);
|
|
st->es_buf_a.sb_err = 0;
|
|
sbuf_cut(&st->es_buf_a, i + fsize);
|
|
goto again;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
const static int mpa_br[16] = {
|
|
0, 32, 48, 56,
|
|
64, 80, 96, 112,
|
|
128, 160, 192, 224,
|
|
256, 320, 384, 0
|
|
};
|
|
|
|
const static int mpa_sr[4] = {44100, 48000, 32000, 0};
|
|
const static int mpa_sri[4] = {4, 3, 5, 0};
|
|
|
|
static int
|
|
mpa_valid_frame(const uint8_t *buf)
|
|
{
|
|
return buf[0] == 0xff && (buf[1] & 0xfe) == 0xfc;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static int
|
|
parse_mpa2(service_t *t, elementary_stream_t *st)
|
|
{
|
|
int i, len;
|
|
const uint8_t *buf;
|
|
|
|
again:
|
|
buf = st->es_buf_a.sb_data;
|
|
len = st->es_buf_a.sb_ptr;
|
|
|
|
for(i = 0; i < len - 4; i++) {
|
|
if(mpa_valid_frame(buf + i)) {
|
|
int br = mpa_br[ buf[i+2] >> 4 ];
|
|
int sr = mpa_sr[(buf[i+2] >> 2) & 3];
|
|
int pad = (buf[i+2] >> 1) & 1;
|
|
|
|
if(br && sr) {
|
|
int fsize = 144000 * br / sr + pad;
|
|
int duration = 90000 * 1152 / sr;
|
|
int64_t dts = st->es_curdts;
|
|
int channels = (buf[i + 3] & 0xc0) == 0xc0 ? 1 : 2;
|
|
if(dts == PTS_UNSET)
|
|
dts = st->es_nextdts;
|
|
|
|
if(dts != PTS_UNSET &&
|
|
len >= i + fsize + 4 &&
|
|
mpa_valid_frame(buf + i + fsize)) {
|
|
|
|
makeapkt(t, st, buf + i, fsize, dts, duration,
|
|
channels, mpa_sri[(buf[i+2] >> 2) & 3],
|
|
st->es_buf_a.sb_err);
|
|
st->es_buf_a.sb_err = 0;
|
|
sbuf_cut(&st->es_buf_a, i + fsize);
|
|
goto again;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static int
|
|
parse_mpa(service_t *t, elementary_stream_t *st, size_t ilen,
|
|
uint32_t next_startcode, int sc_offset)
|
|
{
|
|
int r;
|
|
|
|
if((r = depacketize(t, st, ilen, next_startcode, sc_offset)) != 0)
|
|
return r;
|
|
return parse_mpa2(t, st);
|
|
}
|
|
|
|
|
|
/**
|
|
* AC3 audio parser
|
|
*/
|
|
const static int ac3_freq_tab[4] = {48000, 44100, 32000, 0};
|
|
|
|
const static uint16_t ac3_frame_size_tab[38][3] = {
|
|
{ 64, 69, 96 },
|
|
{ 64, 70, 96 },
|
|
{ 80, 87, 120 },
|
|
{ 80, 88, 120 },
|
|
{ 96, 104, 144 },
|
|
{ 96, 105, 144 },
|
|
{ 112, 121, 168 },
|
|
{ 112, 122, 168 },
|
|
{ 128, 139, 192 },
|
|
{ 128, 140, 192 },
|
|
{ 160, 174, 240 },
|
|
{ 160, 175, 240 },
|
|
{ 192, 208, 288 },
|
|
{ 192, 209, 288 },
|
|
{ 224, 243, 336 },
|
|
{ 224, 244, 336 },
|
|
{ 256, 278, 384 },
|
|
{ 256, 279, 384 },
|
|
{ 320, 348, 480 },
|
|
{ 320, 349, 480 },
|
|
{ 384, 417, 576 },
|
|
{ 384, 418, 576 },
|
|
{ 448, 487, 672 },
|
|
{ 448, 488, 672 },
|
|
{ 512, 557, 768 },
|
|
{ 512, 558, 768 },
|
|
{ 640, 696, 960 },
|
|
{ 640, 697, 960 },
|
|
{ 768, 835, 1152 },
|
|
{ 768, 836, 1152 },
|
|
{ 896, 975, 1344 },
|
|
{ 896, 976, 1344 },
|
|
{ 1024, 1114, 1536 },
|
|
{ 1024, 1115, 1536 },
|
|
{ 1152, 1253, 1728 },
|
|
{ 1152, 1254, 1728 },
|
|
{ 1280, 1393, 1920 },
|
|
{ 1280, 1394, 1920 },
|
|
};
|
|
|
|
/**
|
|
* Inspect 6 bytes AC3 header
|
|
*/
|
|
static int
|
|
ac3_valid_frame(const uint8_t *buf)
|
|
{
|
|
if(buf[0] != 0x0b || buf[1] != 0x77 || buf[5] >> 3 > 10)
|
|
return 0;
|
|
return (buf[4] & 0xc0) != 0xc0 && (buf[4] & 0x3f) < 0x26;
|
|
}
|
|
|
|
static const char acmodtab[8] = {2,1,2,3,3,4,4,5};
|
|
|
|
|
|
static int
|
|
parse_ac3(service_t *t, elementary_stream_t *st, size_t ilen,
|
|
uint32_t next_startcode, int sc_offset)
|
|
{
|
|
int i, len;
|
|
const uint8_t *buf;
|
|
|
|
if((i = depacketize(t, st, ilen, next_startcode, sc_offset)) != 0)
|
|
return i;
|
|
|
|
again:
|
|
buf = st->es_buf_a.sb_data;
|
|
len = st->es_buf_a.sb_ptr;
|
|
|
|
for(i = 0; i < len - 6; i++) {
|
|
const uint8_t *p = buf + i;
|
|
if(ac3_valid_frame(p)) {
|
|
int bsid = p[5] >> 3;
|
|
int fscod = p[4] >> 6;
|
|
int frmsizcod = p[4] & 0x3f;
|
|
int fsize = ac3_frame_size_tab[frmsizcod][fscod] * 2;
|
|
|
|
bsid -= 8;
|
|
if(bsid < 0)
|
|
bsid = 0;
|
|
int sr = ac3_freq_tab[fscod] >> bsid;
|
|
|
|
if(sr) {
|
|
int duration = 90000 * 1536 / sr;
|
|
int64_t dts = st->es_curdts;
|
|
int sri = rate_to_sri(sr);
|
|
|
|
if(dts == PTS_UNSET)
|
|
dts = st->es_nextdts;
|
|
|
|
if(dts != PTS_UNSET && len >= i + fsize + 6 &&
|
|
ac3_valid_frame(p + fsize)) {
|
|
|
|
bitstream_t bs;
|
|
init_rbits(&bs, p + 5, (fsize - 5) * 8);
|
|
|
|
read_bits(&bs, 5); // bsid
|
|
read_bits(&bs, 3); // bsmod
|
|
int acmod = read_bits(&bs, 3);
|
|
|
|
if((acmod & 0x1) && (acmod != 0x1))
|
|
read_bits(&bs, 2); // cmixlen
|
|
if(acmod & 0x4)
|
|
read_bits(&bs, 2); // surmixlev
|
|
if(acmod == 0x2)
|
|
read_bits(&bs, 2); // dsurmod
|
|
|
|
int lfeon = read_bits(&bs, 1);
|
|
int channels = acmodtab[acmod] + lfeon;
|
|
|
|
makeapkt(t, st, p, fsize, dts, duration, channels, sri,
|
|
st->es_buf_a.sb_err);
|
|
st->es_buf_a.sb_err = 0;
|
|
sbuf_cut(&st->es_buf_a, i + fsize);
|
|
goto again;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* EAC3 audio parser
|
|
*/
|
|
|
|
|
|
static int
|
|
eac3_valid_frame(const uint8_t *buf)
|
|
{
|
|
if(buf[0] != 0x0b || buf[1] != 0x77 || buf[5] >> 3 <= 10)
|
|
return 0;
|
|
return (buf[4] & 0xc0) != 0xc0;
|
|
}
|
|
|
|
static int
|
|
parse_eac3(service_t *t, elementary_stream_t *st, size_t ilen,
|
|
uint32_t next_startcode, int sc_offset)
|
|
{
|
|
int i, len;
|
|
const uint8_t *buf;
|
|
|
|
if((i = depacketize(t, st, ilen, next_startcode, sc_offset)) != 0)
|
|
return i;
|
|
|
|
again:
|
|
buf = st->es_buf_a.sb_data;
|
|
len = st->es_buf_a.sb_ptr;
|
|
|
|
for(i = 0; i < len - 6; i++) {
|
|
const uint8_t *p = buf + i;
|
|
if(eac3_valid_frame(p)) {
|
|
|
|
int fsize = ((((p[2] & 0x7) << 8) + p[3]) + 1) * 2;
|
|
|
|
int sr = p[4] >> 6;
|
|
int rate;
|
|
if(sr == 3) {
|
|
int sr2 = (p[4] >> 4) & 0x3;
|
|
if(sr2 == 3)
|
|
continue;
|
|
rate = ac3_freq_tab[sr2] / 2;
|
|
} else {
|
|
rate = ac3_freq_tab[sr];
|
|
}
|
|
|
|
|
|
int64_t dts = st->es_curdts;
|
|
int sri = rate_to_sri(rate);
|
|
|
|
int acmod = (p[4] >> 1) & 0x7;
|
|
int lfeon = p[4] & 1;
|
|
|
|
int channels = acmodtab[acmod] + lfeon;
|
|
int duration = 90000 * 1536 / rate;
|
|
|
|
if(dts == PTS_UNSET)
|
|
dts = st->es_nextdts;
|
|
|
|
if(dts != PTS_UNSET && len >= i + fsize + 6 &&
|
|
eac3_valid_frame(p + fsize)) {
|
|
makeapkt(t, st, p, fsize, dts, duration, channels, sri,
|
|
st->es_buf_a.sb_err);
|
|
st->es_buf_a.sb_err = 0;
|
|
sbuf_cut(&st->es_buf_a, i + fsize);
|
|
goto again;
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* PES header parser
|
|
*
|
|
* Extract DTS and PTS and update current values in stream
|
|
*/
|
|
static int
|
|
parse_pes_header(service_t *t, elementary_stream_t *st,
|
|
const uint8_t *buf, size_t len)
|
|
{
|
|
int64_t dts, pts, d;
|
|
int hdr, flags, hlen;
|
|
|
|
hdr = getu8(buf, len);
|
|
flags = getu8(buf, len);
|
|
hlen = getu8(buf, len);
|
|
|
|
if(len < hlen || (hdr & 0xc0) != 0x80)
|
|
goto err;
|
|
|
|
if((flags & 0xc0) == 0xc0) {
|
|
if(hlen < 10)
|
|
goto err;
|
|
|
|
pts = getpts(buf);
|
|
dts = getpts(buf + 5);
|
|
|
|
|
|
d = (pts - dts) & PTS_MASK;
|
|
if(d > 180000) // More than two seconds of PTS/DTS delta, probably corrupt
|
|
pts = dts = PTS_UNSET;
|
|
|
|
} else if((flags & 0xc0) == 0x80) {
|
|
if(hlen < 5)
|
|
goto err;
|
|
|
|
dts = pts = getpts(buf);
|
|
} else
|
|
return hlen + 3;
|
|
|
|
if(st->es_buf.sb_err) {
|
|
st->es_curdts = PTS_UNSET;
|
|
st->es_curpts = PTS_UNSET;
|
|
} else {
|
|
st->es_curdts = dts;
|
|
st->es_curpts = pts;
|
|
}
|
|
return hlen + 3;
|
|
|
|
err:
|
|
st->es_curdts = PTS_UNSET;
|
|
st->es_curpts = PTS_UNSET;
|
|
limitedlog(&st->es_loglimit_pes, "TS", service_component_nicename(st),
|
|
"Corrupted PES header");
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* MPEG2VIDEO frame duration table (in 90kHz clock domain)
|
|
*/
|
|
const unsigned int mpeg2video_framedurations[16] = {
|
|
0,
|
|
3753,
|
|
3750,
|
|
3600,
|
|
3003,
|
|
3000,
|
|
1800,
|
|
1501,
|
|
1500,
|
|
};
|
|
|
|
/**
|
|
* Parse mpeg2video picture start
|
|
*/
|
|
static int
|
|
parse_mpeg2video_pic_start(service_t *t, elementary_stream_t *st, int *frametype,
|
|
bitstream_t *bs)
|
|
{
|
|
int v, pct;
|
|
|
|
if(bs->len < 29)
|
|
return 1;
|
|
|
|
skip_bits(bs, 10); /* temporal reference */
|
|
|
|
pct = read_bits(bs, 3);
|
|
if(pct < PKT_I_FRAME || pct > PKT_B_FRAME)
|
|
return 1; /* Illegal picture_coding_type */
|
|
|
|
*frametype = pct;
|
|
|
|
v = read_bits(bs, 16); /* vbv_delay */
|
|
if(v == 0xffff)
|
|
st->es_vbv_delay = -1;
|
|
else
|
|
st->es_vbv_delay = v;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void
|
|
parser_set_stream_vsize(elementary_stream_t *st, int width, int height)
|
|
{
|
|
int need_save = 0;
|
|
|
|
if(st->es_width == 0 && st->es_height == 0) {
|
|
need_save = 1;
|
|
st->es_meta_change = 0;
|
|
|
|
} else if(st->es_width != width || st->es_height != height) {
|
|
|
|
st->es_meta_change++;
|
|
if(st->es_meta_change == 2)
|
|
need_save = 1;
|
|
|
|
} else {
|
|
st->es_meta_change = 0;
|
|
}
|
|
|
|
if(need_save) {
|
|
st->es_width = width;
|
|
st->es_height = height;
|
|
service_request_save(st->es_service, 1);
|
|
}
|
|
}
|
|
|
|
|
|
static const uint8_t mpeg2_aspect[16][2]={
|
|
{0,1},
|
|
{1,1},
|
|
{4,3},
|
|
{16,9},
|
|
{221,100},
|
|
{0,1},
|
|
{0,1},
|
|
{0,1},
|
|
{0,1},
|
|
{0,1},
|
|
{0,1},
|
|
{0,1},
|
|
{0,1},
|
|
{0,1},
|
|
{0,1},
|
|
{0,1},
|
|
};
|
|
|
|
|
|
/**
|
|
* Parse mpeg2video sequence start
|
|
*/
|
|
static int
|
|
parse_mpeg2video_seq_start(service_t *t, elementary_stream_t *st,
|
|
bitstream_t *bs)
|
|
{
|
|
int v, width, height, aspect;
|
|
|
|
if(bs->len < 61)
|
|
return 1;
|
|
|
|
width = read_bits(bs, 12);
|
|
height = read_bits(bs, 12);
|
|
aspect = read_bits(bs, 4);
|
|
|
|
st->es_aspect_num = mpeg2_aspect[aspect][0];
|
|
st->es_aspect_den = mpeg2_aspect[aspect][1];
|
|
|
|
st->es_frame_duration = mpeg2video_framedurations[read_bits(bs, 4)];
|
|
|
|
v = read_bits(bs, 18) * 400;
|
|
skip_bits(bs, 1);
|
|
|
|
v = read_bits(bs, 10) * 16 * 1024 / 8;
|
|
st->es_vbv_size = v;
|
|
|
|
parser_set_stream_vsize(st, width, height);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static int
|
|
drop_trailing_zeroes(const uint8_t *buf, size_t len)
|
|
{
|
|
while(len > 3 && (buf[len - 3] | buf[len - 2] | buf[len - 1]) == 0)
|
|
len--;
|
|
return len;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
parser_global_data_move(elementary_stream_t *st, const uint8_t *data, size_t len)
|
|
{
|
|
int len2 = drop_trailing_zeroes(data, len);
|
|
|
|
st->es_global_data = realloc(st->es_global_data,
|
|
st->es_global_data_len + len2);
|
|
memcpy(st->es_global_data + st->es_global_data_len, data, len2);
|
|
st->es_global_data_len += len2;
|
|
|
|
st->es_buf.sb_ptr -= len;
|
|
}
|
|
|
|
|
|
/**
|
|
* MPEG2VIDEO specific reassembly
|
|
*
|
|
* Process all startcodes (also the system ones)
|
|
*
|
|
* Extract framerate (or duration to be more specific)
|
|
*
|
|
* 'steal' the st->es_buffer and use it as 'pkt' buffer
|
|
*
|
|
*/
|
|
static int
|
|
parse_mpeg2video(service_t *t, elementary_stream_t *st, size_t len,
|
|
uint32_t next_startcode, int sc_offset)
|
|
{
|
|
const uint8_t *buf = st->es_buf.sb_data + sc_offset;
|
|
bitstream_t bs;
|
|
int frametype;
|
|
|
|
if(next_startcode == 0x1e0)
|
|
return 4;
|
|
|
|
init_rbits(&bs, buf + 4, (len - 4) * 8);
|
|
|
|
switch(st->es_startcode) {
|
|
case 0x000001e0 ... 0x000001ef:
|
|
/* System start codes for video */
|
|
if(len < 9)
|
|
return 1;
|
|
|
|
parse_pes_header(t, st, buf + 6, len - 6);
|
|
return 1;
|
|
|
|
case 0x00000100:
|
|
/* Picture start code */
|
|
if(st->es_frame_duration == 0)
|
|
return 1;
|
|
|
|
if(parse_mpeg2video_pic_start(t, st, &frametype, &bs))
|
|
return 1;
|
|
|
|
if(st->es_curpkt != NULL)
|
|
pkt_ref_dec(st->es_curpkt);
|
|
|
|
st->es_curpkt = pkt_alloc(NULL, 0, st->es_curpts, st->es_curdts);
|
|
st->es_curpkt->pkt_frametype = frametype;
|
|
st->es_curpkt->pkt_duration = st->es_frame_duration;
|
|
st->es_curpkt->pkt_commercial = t->s_tt_commercial_advice;
|
|
|
|
/* If we know the frame duration, increase DTS accordingly */
|
|
if(st->es_curdts != PTS_UNSET)
|
|
st->es_curdts += st->es_frame_duration;
|
|
|
|
/* PTS cannot be extrapolated (it's not linear) */
|
|
st->es_curpts = PTS_UNSET;
|
|
|
|
break;
|
|
|
|
case 0x000001b3:
|
|
/* Sequence start code */
|
|
if(!st->es_buf.sb_err) {
|
|
if(parse_mpeg2video_seq_start(t, st, &bs))
|
|
return 1;
|
|
parser_global_data_move(st, buf, len);
|
|
}
|
|
return 2;
|
|
|
|
case 0x000001b5:
|
|
if(len < 5)
|
|
return 1;
|
|
switch(buf[4] >> 4) {
|
|
case 0x1:
|
|
// Sequence Extension
|
|
if(!st->es_buf.sb_err)
|
|
parser_global_data_move(st, buf, len);
|
|
return 2;
|
|
case 0x2:
|
|
// Sequence Display Extension
|
|
if(!st->es_buf.sb_err)
|
|
parser_global_data_move(st, buf, len);
|
|
return 2;
|
|
}
|
|
break;
|
|
|
|
case 0x00000101 ... 0x000001af:
|
|
/* Slices */
|
|
|
|
if(next_startcode == 0x100 || next_startcode > 0x1af) {
|
|
/* Last picture slice (because next not a slice) */
|
|
th_pkt_t *pkt = st->es_curpkt;
|
|
if(pkt == NULL) {
|
|
/* no packet, may've been discarded by sanity checks here */
|
|
return 1;
|
|
}
|
|
|
|
if(st->es_global_data) {
|
|
pkt->pkt_header = pktbuf_make(st->es_global_data,
|
|
st->es_global_data_len);
|
|
st->es_global_data = NULL;
|
|
st->es_global_data_len = 0;
|
|
}
|
|
|
|
pkt->pkt_payload = pktbuf_make(st->es_buf.sb_data,
|
|
st->es_buf.sb_ptr - 4);
|
|
pkt->pkt_duration = st->es_frame_duration;
|
|
|
|
parser_deliver(t, st, pkt, st->es_buf.sb_err);
|
|
st->es_curpkt = NULL;
|
|
|
|
st->es_buf.sb_data = malloc(st->es_buf.sb_size);
|
|
if(st->es_buf.sb_data == NULL) {
|
|
fprintf(stderr, "Unable to allocate %d bytes\n",
|
|
st->es_buf.sb_size);
|
|
abort();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
break;
|
|
|
|
case 0x000001b8:
|
|
// GOP header
|
|
if(!st->es_buf.sb_err)
|
|
parser_global_data_move(st, buf, len);
|
|
return 2;
|
|
|
|
case 0x000001b2:
|
|
// User data
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* H.264 parser
|
|
*/
|
|
static int
|
|
parse_h264(service_t *t, elementary_stream_t *st, size_t len,
|
|
uint32_t next_startcode, int sc_offset)
|
|
{
|
|
const uint8_t *buf = st->es_buf.sb_data + sc_offset;
|
|
uint32_t sc = st->es_startcode;
|
|
int64_t d;
|
|
int l2, pkttype, duration, isfield;
|
|
bitstream_t bs;
|
|
int ret = 0;
|
|
|
|
if(sc >= 0x000001e0 && sc <= 0x000001ef) {
|
|
/* System start codes for video */
|
|
if(len >= 9){
|
|
uint16_t plen = buf[4] << 8 | buf[5];
|
|
if(plen >= 0xffe9) st->es_incomplete =1;
|
|
parse_pes_header(t, st, buf + 6, len - 6);
|
|
}
|
|
if(st->es_prevdts != PTS_UNSET && st->es_curdts != PTS_UNSET) {
|
|
d = (st->es_curdts - st->es_prevdts) & 0x1ffffffffLL;
|
|
|
|
if(d < 90000)
|
|
st->es_frame_duration = d;
|
|
}
|
|
st->es_prevdts = st->es_curdts;
|
|
return 1;
|
|
}
|
|
|
|
if(sc == 0x10c) {
|
|
// Padding
|
|
|
|
st->es_buf.sb_ptr -= len;
|
|
ret = 2;
|
|
|
|
} else {
|
|
|
|
switch(sc & 0x1f) {
|
|
|
|
case 7:
|
|
if(!st->es_buf.sb_err) {
|
|
void *f = h264_nal_deescape(&bs, buf + 3, len - 3);
|
|
h264_decode_seq_parameter_set(st, &bs);
|
|
free(f);
|
|
parser_global_data_move(st, buf, len);
|
|
}
|
|
ret = 2;
|
|
break;
|
|
|
|
case 8:
|
|
if(!st->es_buf.sb_err) {
|
|
void *f = h264_nal_deescape(&bs, buf + 3, len - 3);
|
|
h264_decode_pic_parameter_set(st, &bs);
|
|
free(f);
|
|
parser_global_data_move(st, buf, len);
|
|
}
|
|
ret = 2;
|
|
break;
|
|
|
|
case 5: /* IDR+SLICE */
|
|
case 1:
|
|
if(st->es_curpkt != NULL || st->es_frame_duration == 0)
|
|
break;
|
|
|
|
l2 = len - 3 > 64 ? 64 : len - 3;
|
|
void *f = h264_nal_deescape(&bs, buf + 3, l2);
|
|
/* we just want the first stuff */
|
|
|
|
if(h264_decode_slice_header(st, &bs, &pkttype, &duration, &isfield)) {
|
|
free(f);
|
|
return 1;
|
|
}
|
|
free(f);
|
|
|
|
st->es_curpkt = pkt_alloc(NULL, 0, st->es_curpts, st->es_curdts);
|
|
st->es_curpkt->pkt_frametype = pkttype;
|
|
st->es_curpkt->pkt_field = isfield;
|
|
st->es_curpkt->pkt_duration = duration ?: st->es_frame_duration;
|
|
st->es_curpkt->pkt_commercial = t->s_tt_commercial_advice;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(next_startcode >= 0x000001e0 && next_startcode <= 0x000001ef) {
|
|
/* Complete frame */
|
|
if (st->es_incomplete) return 4;
|
|
th_pkt_t *pkt = st->es_curpkt;
|
|
|
|
if(pkt != NULL) {
|
|
|
|
if(st->es_global_data) {
|
|
pkt->pkt_header = pktbuf_make(st->es_global_data,
|
|
st->es_global_data_len);
|
|
st->es_global_data = NULL;
|
|
st->es_global_data_len = 0;
|
|
}
|
|
|
|
pkt->pkt_payload = pktbuf_make(st->es_buf.sb_data,
|
|
st->es_buf.sb_ptr - 4);
|
|
parser_deliver(t, st, pkt, st->es_buf.sb_err);
|
|
|
|
st->es_curpkt = NULL;
|
|
st->es_buf.sb_data = malloc(st->es_buf.sb_size);
|
|
|
|
st->es_curdts = PTS_UNSET;
|
|
st->es_curpts = PTS_UNSET;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* http://broadcasting.ru/pdf-standard-specifications/subtitling/dvb-sub/en300743.v1.2.1.pdf
|
|
*/
|
|
static void
|
|
parse_subtitles(service_t *t, elementary_stream_t *st, const uint8_t *data,
|
|
int len, int start)
|
|
{
|
|
th_pkt_t *pkt;
|
|
int psize, hlen;
|
|
const uint8_t *buf;
|
|
const uint8_t *d;
|
|
if(start) {
|
|
/* Payload unit start */
|
|
st->es_parser_state = 1;
|
|
sbuf_reset(&st->es_buf);
|
|
}
|
|
|
|
if(st->es_parser_state == 0)
|
|
return;
|
|
|
|
sbuf_append(&st->es_buf, data, len);
|
|
|
|
if(st->es_buf.sb_ptr < 6)
|
|
return;
|
|
d = st->es_buf.sb_data;
|
|
uint32_t startcode = (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3];
|
|
|
|
if(startcode == 0x1be) {
|
|
st->es_parser_state = 0;
|
|
return;
|
|
}
|
|
|
|
psize = d[4] << 8 | d[5];
|
|
|
|
if(st->es_buf.sb_ptr != psize + 6)
|
|
return;
|
|
|
|
st->es_parser_state = 0;
|
|
|
|
hlen = parse_pes_header(t, st, d + 6, st->es_buf.sb_ptr - 6);
|
|
if(hlen < 0)
|
|
return;
|
|
|
|
psize -= hlen;
|
|
buf = d + 6 + hlen;
|
|
|
|
if(psize < 2 || buf[0] != 0x20 || buf[1] != 0x00)
|
|
return;
|
|
|
|
psize -= 2;
|
|
buf += 2;
|
|
|
|
if(psize >= 6) {
|
|
|
|
// end_of_PES_data_field_marker
|
|
if(buf[psize - 1] == 0xff) {
|
|
pkt = pkt_alloc(buf, psize - 1, st->es_curpts, st->es_curdts);
|
|
pkt->pkt_commercial = t->s_tt_commercial_advice;
|
|
parser_deliver(t, st, pkt, st->es_buf.sb_err);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Teletext parser
|
|
*/
|
|
static void
|
|
parse_teletext(service_t *t, elementary_stream_t *st, const uint8_t *data,
|
|
int len, int start)
|
|
{
|
|
th_pkt_t *pkt;
|
|
int psize, hlen;
|
|
const uint8_t *buf;
|
|
const uint8_t *d;
|
|
if(start) {
|
|
st->es_parser_state = 1;
|
|
st->es_parser_ptr = 0;
|
|
sbuf_reset(&st->es_buf);
|
|
}
|
|
|
|
if(st->es_parser_state == 0)
|
|
return;
|
|
|
|
sbuf_append(&st->es_buf, data, len);
|
|
|
|
if(st->es_buf.sb_ptr < 6)
|
|
return;
|
|
d = st->es_buf.sb_data;
|
|
|
|
psize = d[4] << 8 | d[5];
|
|
|
|
if(st->es_buf.sb_ptr != psize + 6)
|
|
return;
|
|
|
|
st->es_parser_state = 0;
|
|
|
|
hlen = parse_pes_header(t, st, d + 6, st->es_buf.sb_ptr - 6);
|
|
if(hlen < 0)
|
|
return;
|
|
|
|
psize -= hlen;
|
|
buf = d + 6 + hlen;
|
|
|
|
if(psize >= 46) {
|
|
|
|
pkt = pkt_alloc(buf, psize, st->es_curpts, st->es_curdts);
|
|
pkt->pkt_commercial = t->s_tt_commercial_advice;
|
|
parser_deliver(t, st, pkt, st->es_buf.sb_err);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void
|
|
parser_deliver(service_t *t, elementary_stream_t *st, th_pkt_t *pkt, int error)
|
|
{
|
|
pkt->pkt_err = error;
|
|
|
|
if(SCT_ISAUDIO(st->es_type) && pkt->pkt_pts != PTS_UNSET &&
|
|
(t->s_current_pts == PTS_UNSET ||
|
|
pkt->pkt_pts > t->s_current_pts ||
|
|
pkt->pkt_pts < t->s_current_pts - 180000))
|
|
t->s_current_pts = pkt->pkt_pts;
|
|
|
|
#if 0
|
|
printf("PARSE: %-12s %d %10"PRId64" %10"PRId64" %10d %10d\n",
|
|
streaming_component_type2txt(st->es_type),
|
|
pkt->pkt_frametype,
|
|
pkt->pkt_dts,
|
|
pkt->pkt_pts,
|
|
pkt->pkt_duration,
|
|
pktbuf_len(pkt->pkt_payload));
|
|
#endif
|
|
|
|
pkt->pkt_aspect_num = st->es_aspect_num;
|
|
pkt->pkt_aspect_den = st->es_aspect_den;
|
|
|
|
// avgstat_add(&st->es_rate, pkt->pkt_payloadlen, dispatch_clock);
|
|
|
|
/**
|
|
* Input is ok
|
|
*/
|
|
service_set_streaming_status_flags(t, TSS_PACKETS);
|
|
|
|
/* Forward packet */
|
|
pkt->pkt_componentindex = st->es_index;
|
|
|
|
streaming_message_t *sm = streaming_msg_create_pkt(pkt);
|
|
|
|
streaming_pad_deliver(&t->s_streaming_pad, sm);
|
|
streaming_msg_free(sm);
|
|
|
|
/* Decrease our own reference to the packet */
|
|
pkt_ref_dec(pkt);
|
|
|
|
}
|