|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import sys
|
|
import struct
|
|
import binascii
|
|
|
|
type_strings = []
|
|
|
|
|
|
def decode_ts_packet(packet):
|
|
fields = {}
|
|
|
|
|
|
|
|
word = struct.unpack('!I', packet[0:4])[0]
|
|
|
|
|
|
sync = (word & 0xff000000) >> 24
|
|
if sync != 0x47:
|
|
print('Bad packet sync byte (desync?):', [packet])
|
|
raise SystemExit
|
|
|
|
fields['transport_error_indicator'] = (word & 0x800000) != 0
|
|
|
|
|
|
|
|
|
|
fields['payload_unit_start_indicator'] = (word & 0x400000) != 0
|
|
|
|
|
|
fields['transport_priority'] = (word & 0x200000) != 0
|
|
|
|
|
|
fields['pid'] = (word & 0x1fff00) >> 8
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fields['scrambling_control'] = (word & 0xc0) >> 6
|
|
|
|
|
|
|
|
|
|
|
|
fields['adaptation_field_control'] = (word & 0x30) >> 4
|
|
|
|
if fields['adaptation_field_control'] == 1:
|
|
has_adapt = False
|
|
has_payload = True
|
|
elif fields['adaptation_field_control'] == 2:
|
|
has_adapt = True
|
|
has_payload = False
|
|
elif fields['adaptation_field_control'] == 3:
|
|
has_adapt = True
|
|
has_payload = True
|
|
else:
|
|
|
|
|
|
|
|
|
|
has_adapt = False
|
|
has_payload = True
|
|
fields['corrupted_adaption_control_field'] = True
|
|
|
|
|
|
|
|
fields['cont_counter'] = word & 0xf
|
|
|
|
payload_start = 5
|
|
if has_adapt:
|
|
adapt_length = struct.unpack('b', bytes([packet[5]]))[0]
|
|
if 6 + adapt_length > len(packet):
|
|
print("adaptation field beyond length of packet", [packet], adapt_length, len(packet))
|
|
raise SystemExit
|
|
|
|
fields['adapt'] = packet[6:6 + adapt_length]
|
|
payload_start = 6 + adapt_length
|
|
|
|
|
|
if has_payload:
|
|
fields['payload'] = packet[payload_start:]
|
|
else:
|
|
|
|
extra = packet[payload_start:]
|
|
if len(extra) != 0:
|
|
fields['corrupt_payload'] = extra
|
|
|
|
return fields
|
|
|
|
|
|
def ascii_dump(s):
|
|
s2 = ''
|
|
printable = 0
|
|
for c in s:
|
|
if ord(' ') <= c <= ord('~'):
|
|
s2 += str(c)
|
|
printable += 1
|
|
else:
|
|
s2 += '.'
|
|
return s2, printable
|
|
|
|
|
|
def decode_pat(payload):
|
|
t = binascii.b2a_hex(payload)
|
|
if t not in type_strings:
|
|
type_strings.append(t)
|
|
|
|
print('PAT', binascii.b2a_hex(payload))
|
|
|
|
|
|
section_length = (payload[1] & 0xf << 8) | payload[2]
|
|
print(section_length)
|
|
|
|
|
|
program_count = (section_length - 5) / 4 - 1
|
|
|
|
program_map_pids = {}
|
|
|
|
print("PAT:")
|
|
for i in range(0, int(program_count)):
|
|
at = 8 + (i * 4)
|
|
program_number = struct.unpack("!H", payload[at:at + 2])[0]
|
|
if at + 2 > len(payload):
|
|
break
|
|
program_map_pid = struct.unpack("!H", payload[at + 2:at + 2 + 2])[0]
|
|
|
|
|
|
reserved = (program_map_pid & 0xe000) >> 13
|
|
program_map_pid &= 0x1fff
|
|
|
|
print(i, "%.4x %.4x" % (program_number, program_map_pid))
|
|
program_map_pids[program_map_pid] = program_number
|
|
i += 1
|
|
return program_map_pids
|
|
|
|
|
|
DESC_NAMES = {
|
|
|
|
0: 'Reserved',
|
|
1: 'Reserved',
|
|
2: 'video_stream_descriptor',
|
|
3: 'audio_stream_descriptor',
|
|
4: 'hierarchy_descriptor:',
|
|
5: 'registration_descriptor',
|
|
6: 'data_stream_alignment_descriptor',
|
|
7: 'target_background_grid_descriptor',
|
|
8: 'video_window_descriptor',
|
|
9: 'CA_descriptor',
|
|
10: 'ISO_639_language_descriptor',
|
|
11: 'system_clock_descriptor',
|
|
12: 'multiplex_buffer_utilization_descriptor',
|
|
13: 'copyright_descriptor',
|
|
14: 'maximum_bitrate_descriptor',
|
|
15: 'private_data_indicator_descriptor',
|
|
16: 'smoothing_buffer_descriptor',
|
|
17: 'STD_descriptor',
|
|
18: 'IBP_descriptor:',
|
|
27: 'MPEG-4_video_descriptor',
|
|
28: 'MPEG-4_audio_descriptor',
|
|
29: 'IOD_descriptor',
|
|
30: 'SL_descriptor',
|
|
31: 'FMC_descriptor',
|
|
32: 'External_ES_ID_descriptor:',
|
|
33: 'MuxCode_descriptor',
|
|
34: 'FmxBufferSize_descriptor:',
|
|
35: 'MultiplexBuffer_descriptor',
|
|
36: 'FlexMuxTiming_descriptor',
|
|
|
|
129: 'AC-3 Descriptor',
|
|
20: 'association_tag descriptor',
|
|
173: 'ATSC Private Information descriptor',
|
|
134: 'Caption Service Descriptor',
|
|
163: 'Component name descriptor',
|
|
135: 'Content Advisory Descriptor',
|
|
198: 'Content Identifier descriptor',
|
|
164: 'Data Service descriptor',
|
|
166: 'Download descriptor',
|
|
169: 'DCC Arriving request descriptor',
|
|
168: 'DCC Departing request descriptor',
|
|
178: 'Enhanced Signaling descriptor',
|
|
160: 'Extended Channel name descriptor',
|
|
167: 'Multiprotocol Encapsulation descriptor',
|
|
165: 'pid_count descriptor',
|
|
171: 'Genre descriptor',
|
|
170: 'Redistribution Control descriptor',
|
|
161: 'Service location descriptor',
|
|
128: 'Stuffing Descriptor',
|
|
162: 'Time-shifted service descriptor',
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def decode_descriptors(data):
|
|
at = 0
|
|
descriptors = {}
|
|
|
|
while True:
|
|
if at + 2 > len(data):
|
|
break
|
|
|
|
tag = data[at]
|
|
at += 1
|
|
length = data[at]
|
|
at += 1
|
|
value = data[at:at + length]
|
|
at += length
|
|
|
|
name = DESC_NAMES.get(tag)
|
|
if name is None:
|
|
name = 'unknown-%d' % (tag,)
|
|
print("descriptor %d (%s) %s" % (tag, name, [value]))
|
|
descriptors[name] = value
|
|
|
|
return descriptors
|
|
|
|
|
|
|
|
def decode_pmt(pid, program, payload):
|
|
t = binascii.b2a_hex(payload)
|
|
if t not in type_strings:
|
|
type_strings.append(t)
|
|
print('PMT for Program %d at PID %.4x: %s' % (pid, program, [payload]))
|
|
|
|
pcr_pid = struct.unpack("!H", payload[8:10])[0]
|
|
reserved = (pcr_pid & 0xe000) >> 13
|
|
pcr_pid &= 0x1fff
|
|
|
|
|
|
|
|
|
|
desc1 = payload[12:]
|
|
descriptors = decode_descriptors(desc1)
|
|
|
|
|
|
|
|
def decode_psip(payload):
|
|
print('Found PSIP full', [payload])
|
|
|
|
table_id = ord(payload[0])
|
|
|
|
|
|
|
|
|
|
|
|
if table_id == 0xc7:
|
|
print('PSIP MGT', [payload])
|
|
decode_mgt(payload)
|
|
|
|
elif table_id == 0xc8:
|
|
print('PSIP VCT', payload.encode('hex'), '\n', 'PSIP VCT', [payload])
|
|
|
|
|
|
decode_vct(payload)
|
|
|
|
elif table_id == 0xcb:
|
|
print('PSIP EIT', [payload])
|
|
|
|
elif table_id == 0xcc:
|
|
print('PSIP ETT', [payload])
|
|
|
|
elif table_id == 0xcd:
|
|
print('PSIP STT', [payload])
|
|
|
|
elif table_id == 0xca:
|
|
print('PSIP RRT', [payload])
|
|
|
|
elif table_id == 0xd3:
|
|
print('PSIP DCCT', [payload])
|
|
else:
|
|
print('PSIP unknown table id %.2x' % (table_id,))
|
|
|
|
|
|
def decode_mgt_table_type(x):
|
|
|
|
|
|
|
|
if x == 0x0000:
|
|
return 'Terrestrial VCT with current_next_indicator=1'
|
|
elif x == 0x0001:
|
|
return 'Terrestrial VCT with current_next_indicator=0'
|
|
elif x == 0x0002:
|
|
return 'Cable VCT with current_next_indicator=1'
|
|
elif x == 0x0003:
|
|
return 'Cable VCT with current_next_indicator=0'
|
|
elif x == 0x004:
|
|
return 'Channel ETT'
|
|
elif x == 0x005:
|
|
return 'DCCSCT'
|
|
|
|
|
|
elif 0x0100 <= x <= 0x017f:
|
|
return 'EIT-%d' % (x - 0x0100)
|
|
|
|
|
|
elif 0x0200 <= x <= 0x027f:
|
|
return 'Event ETT-%d' % (x - 0x0200)
|
|
|
|
|
|
elif 0x0301 <= x <= 0x03ff:
|
|
return 'RRT with rating_region-%d' % (x - 0x0301)
|
|
elif 0x0400 <= x <= 0x0fff:
|
|
return 'User private-%.4x' % (x,)
|
|
|
|
|
|
elif 0x1400 <= x <= 0x14ff:
|
|
return 'DCCT with dcc_id-%d' % (x - 0x1400)
|
|
|
|
|
|
else:
|
|
return 'Reserved for future ATSC use-%.4x' % (x,)
|
|
|
|
|
|
psip_table_pids = {}
|
|
|
|
|
|
|
|
def decode_mgt(payload):
|
|
t = binascii.b2a_hex(payload)
|
|
if t not in type_strings:
|
|
type_strings.append(t)
|
|
print('PSIP MGT', payload.encode('hex'))
|
|
assert payload[0] == '\xc7', 'decodeMGT not MGT'
|
|
|
|
section_length = struct.unpack('!H', payload[1:3])[0]
|
|
section_length &= 0xfff
|
|
print('PSIP MGT section_length', section_length)
|
|
|
|
table_id_extension = struct.unpack('!H', payload[3:5])[0]
|
|
print('PSIP MGT table_id_extension', table_id_extension)
|
|
|
|
|
|
version_etc = ord(payload[5])
|
|
print('PSIP MGT version_etc %.2x' % (version_etc,))
|
|
|
|
|
|
section_number = ord(payload[6])
|
|
assert section_number == 0, 'decodeMGT section_number != 0'
|
|
|
|
last_section_number = ord(payload[7])
|
|
assert last_section_number == 0, 'decodeMGT last_section_number != 0'
|
|
|
|
protocol_version = ord(payload[8])
|
|
print('PSIP MGT protocol_version', protocol_version)
|
|
|
|
|
|
tables_defined = struct.unpack('!H', payload[9:11])[0]
|
|
print('PSIP MGT tables_defined', tables_defined)
|
|
|
|
at = 11
|
|
tables = []
|
|
for i in range(0, tables_defined):
|
|
if at > len(payload):
|
|
|
|
break
|
|
|
|
table_type = struct.unpack('!H', payload[at:at + 2])[0]
|
|
table_type_name = decode_mgt_table_type(table_type)
|
|
print('PSIP MGT %i table_type %.4x %s' % (i, table_type, table_type_name))
|
|
at += 2
|
|
|
|
|
|
|
|
|
|
table_type_pid = struct.unpack('!H', payload[at:at + 2])[0]
|
|
table_type_pid &= 0x1fff
|
|
print('PSIP MGT %i table_type_pid %.4x' % (i, table_type_pid))
|
|
at += 2
|
|
|
|
|
|
reserved_version = ord(payload[at])
|
|
print('PSIP MGT %i reserved_version %.2x' % (i, reserved_version))
|
|
at += 1
|
|
|
|
|
|
if at + 4 > len(payload):
|
|
break
|
|
number_bytes = struct.unpack('!I', payload[at:at + 4])[0]
|
|
print('PSIP MGT %i number_bytes %d' % (i, number_bytes))
|
|
at += 4
|
|
|
|
table_type_descriptors_length = struct.unpack('!H', payload[at:at + 2])[0]
|
|
table_type_descriptors_length &= 0xfff
|
|
print('PSIP MGT %i table_type_descriptors_length %d' % (i, table_type_descriptors_length))
|
|
at += 2
|
|
|
|
|
|
descriptors_data = payload[at:at + table_type_descriptors_length]
|
|
at += table_type_descriptors_length
|
|
|
|
|
|
psip_table_pids[table_type_pid] = table_type_name
|
|
|
|
return tables
|
|
|
|
|
|
|
|
def decode_vct(payload):
|
|
t = binascii.b2a_hex(payload)
|
|
if t not in type_strings:
|
|
type_strings.append(t)
|
|
num_channels_in_section = ord(payload[9:10])
|
|
print('VCT number of channels:', num_channels_in_section)
|
|
short_name = payload[11:11 + 13]
|
|
short_name = short_name.replace('\0', '')
|
|
print('VCT channel short_name:', short_name)
|
|
|
|
chinfo = struct.unpack("!I", payload[24:24 + 4])[0]
|
|
print("VCT chinfo %.4x" % (chinfo,))
|
|
reserved = (chinfo & 0xf0000000) >> 28
|
|
assert reserved == 0xf, '%x != 0xf in chinfo' % (reserved,)
|
|
|
|
major_channel_number = (chinfo & 0x0cff0000) >> 18
|
|
minor_channel_number = (chinfo & 0x000cff00) >> 8
|
|
print("VCT virtual channel %d-%d" % (major_channel_number, minor_channel_number))
|
|
|
|
modulation_mode = chinfo & 0xff
|
|
if modulation_mode == 4:
|
|
modulation_mode_name = '8VSB'
|
|
else:
|
|
modulation_mode_name = None
|
|
print("VCT modulation mode", modulation_mode, modulation_mode_name)
|
|
|
|
carrier_frequency = struct.unpack("!I", payload[28:28 + 4])[0]
|
|
print("VCT carrier frequency", carrier_frequency)
|
|
|
|
|
|
channel_tsid = struct.unpack("!H", payload[32:32 + 2])[0]
|
|
print("VCT channel TSID", channel_tsid)
|
|
|
|
program_number = struct.unpack("!H", payload[34:34 + 2])[0]
|
|
print("VCT program number", program_number)
|
|
|
|
|
|
flags = struct.unpack("!H", payload[34:34 + 2])[0]
|
|
print("VCT misc flags %.4x" % (flags,))
|
|
service_type = flags & 0x3f
|
|
|
|
|
|
|
|
|
|
|
|
service_types = {
|
|
1: 'NTSC analog',
|
|
2: 'ATSC full digital',
|
|
3: 'ATSC audio and data',
|
|
4: 'ATSC data'
|
|
}
|
|
|
|
print("VCT service type", service_type, service_types.get(service_type))
|
|
|
|
source_id = struct.unpack("!H", payload[36:36 + 2])[0]
|
|
|
|
|
|
print("VCT source id", source_id, ("%.4x" % source_id))
|
|
|
|
desc_len = struct.unpack("!H", payload[38:38 + 2])[0]
|
|
|
|
desc_len &= 0x3ff
|
|
print("VCT descriptors length", desc_len)
|
|
|
|
print("VCT descriptors", decode_descriptors(payload[40:40 + desc_len]))
|
|
|
|
|
|
|
|
|
|
|
|
def decode_eit(payload, table_type_name):
|
|
t = binascii.b2a_hex(payload)
|
|
if t not in type_strings:
|
|
type_strings.append(t)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print('EIT', table_type_name, ascii_dump(payload))
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) > 1:
|
|
f = open(sys.argv[1], 'rb')
|
|
else:
|
|
f = sys.stdin
|
|
|
|
packet_count = 0
|
|
|
|
program_map_pids = None
|
|
accumulated_1ffb_payload = None
|
|
|
|
while True:
|
|
|
|
|
|
packet = f.read(188)
|
|
if len(packet) == 0:
|
|
break
|
|
|
|
if len(packet) != 188:
|
|
print("%d != %d" % (len(packet), 188))
|
|
print("Incomplete packet:", [packet])
|
|
raise SystemExit
|
|
|
|
fields = decode_ts_packet(packet)
|
|
|
|
if fields['transport_error_indicator']:
|
|
|
|
|
|
continue
|
|
|
|
|
|
if fields['pid'] == 0x0000:
|
|
program_map_pids = decode_pat(fields['payload'])
|
|
|
|
|
|
if program_map_pids and fields['pid'] in program_map_pids.keys():
|
|
program = program_map_pids[fields['pid']]
|
|
decode_pmt(fields['pid'], program, fields['payload'])
|
|
print('PAYLOAD')
|
|
print(binascii.b2a_hex(fields['payload']))
|
|
print('PACKET')
|
|
print(binascii.b2a_hex(packet))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if fields['pid'] == 0x1ffb:
|
|
print('Found 1ffb psip', fields)
|
|
if fields['payload_unit_start_indicator']:
|
|
if accumulated_1ffb_payload and len(accumulated_1ffb_payload) != 0:
|
|
|
|
|
|
|
|
def trim_array(a):
|
|
s1 = ''
|
|
started = False
|
|
for elem in a:
|
|
if elem is not None:
|
|
s1 += elem
|
|
started = True
|
|
else:
|
|
if started:
|
|
|
|
|
|
|
|
|
|
pass
|
|
return s1
|
|
|
|
print('Accumulated 1ffb psip', accumulated_1ffb_payload)
|
|
full = trim_array(accumulated_1ffb_payload)
|
|
if full:
|
|
decode_psip(full)
|
|
|
|
accumulated_1ffb_payload = [None] * 16
|
|
|
|
if accumulated_1ffb_payload is not None:
|
|
accumulated_1ffb_payload[fields['cont_counter']] = fields['payload']
|
|
|
|
elif fields['pid'] in psip_table_pids.keys():
|
|
table_type_name = psip_table_pids[fields['pid']]
|
|
print('TABLE TYPE NAME %.4x %s' % (fields['pid'], table_type_name))
|
|
|
|
if table_type_name == 'Channel EIT' or table_type_name.startswith('EIT-'):
|
|
if 'payload' in fields:
|
|
|
|
decode_eit(fields['payload'], table_type_name)
|
|
else:
|
|
|
|
pass
|
|
|
|
if fields.get('payload'):
|
|
s, printable = ascii_dump(fields['payload'])
|
|
|
|
if printable > 100:
|
|
print("PID w/ text: %.4x %s" % (fields['pid'], s))
|
|
|
|
|
|
packet_count += 1
|
|
if packet_count % 100000 == 0:
|
|
print('#####################################################################')
|
|
print('', file=sys.stderr)
|
|
for t in type_strings:
|
|
print(t, file=sys.stderr)
|
|
print('#####################################################################')
|
|
|
|
print("Read %s packets\n\n" % (packet_count,))
|
|
print('#####################################################################')
|
|
for t in type_strings:
|
|
print(t, file=sys.stderr)
|
|
print('#####################################################################\n\n')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
main()
|
|
|