├── .gitignore ├── LICENSE ├── README.md ├── create-metadata-ts.py ├── make-stream.sh └── null.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, European Broadcasting Union 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | hbbtv-dvbstream 2 | =============== 3 | 4 | This project contains some scripts to generate a transport stream using FFMpeg and Opencaster which triggers HbbTV portals on a TV. 5 | The stream can be played using Dektec streamXpress. 6 | 7 | It is composed of two files: 8 | 9 | * *make-stream.sh*: Generate the final transport stream. (First, it splits the mpeg files into different transport streams elements for audio and video. Second, it multiplexes the metadata tables, videos and audios streams into a final transport stream file) 10 | 11 | * *create-metadata-ts.py*: Creates stream tables ( PAT, NIT, AIT, SDT, PMT) inspired by the opencaster hbbtv sample. 12 | 13 | 14 | 15 | ## Requirements 16 | 17 | * [ffmpeg](http://ffmpeg.org) 18 | * [opencaster](http://www.avalpa.com/the-key-values/15-free-software/33-opencaster) 19 | 20 | ## Related Projects 21 | 22 | [EBU Cross-Platform Authentication project](http://tech.ebu.ch/cpa) 23 | 24 | 25 | ## Contributors 26 | 27 | * [Michael Barroco](https://github.com/barroco) (EBU) 28 | 29 | 30 | ## Copyright & License 31 | 32 | Copyright (c) 2014, EBU-UER Technology & Innovation 33 | 34 | The code is under BSD (3-Clause) License. (see LICENSE.txt) 35 | -------------------------------------------------------------------------------- /create-metadata-ts.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | # 4 | # Copyright 2010, mediatvcom (http://www.mediatvcom.com/), Claude Vanderm. Based on Lorenzo Pallara scripts (l.pallara@avalpa.com) 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 | 20 | import os 21 | 22 | from dvbobjects.PSI.PAT import * 23 | from dvbobjects.PSI.NIT import * 24 | from dvbobjects.PSI.SDT import * 25 | from dvbobjects.PSI.PMT import * 26 | from dvbobjects.DVB.Descriptors import * 27 | from dvbobjects.MPEG.Descriptors import * 28 | from dvbobjects.MHP.AIT import * 29 | from dvbobjects.HBBTV.Descriptors import * 30 | 31 | # 32 | # Shared values 33 | # 34 | 35 | 36 | 37 | demo_transport_stream_id = 1 # demo value, an official value should be demanded to dvb org 38 | demo_original_network_id = 1 # demo value, an official value should be demanded to dvb org 39 | 40 | demo_service_id = [1, 2] 41 | 42 | pmt_pid = [200, 300] 43 | ait_pid = [501, 502] 44 | # ste_pid = [2002, 2034] 45 | 46 | 47 | 48 | # parameters reported into the AIT to signalize a broadband application. 49 | appli_name = ["EBU-CPA1", "EBU-CPA2"] #application name 50 | appli_root = ["http://rts1.cpa.local:8091/LaunchBar/", "http://rts2.cpa.local:8092/LaunchBar/"] #URL base of transport_protocol_descriptor 51 | appli_path = ["index.html", "index.html"] #initial_path_bytes of simple application descriptor. So the application path will be "http://my_application_root_path/myHbbTV-app/index.html" 52 | organisationId = [10, 10] # this is a demo value, dvb.org should assign an unique value 53 | applicationId = [1001, 1002] # this is a demo value. This number corresponds to a trusted application. 54 | 55 | 56 | # below, the video and audio PIDs reported into the PMT 57 | video_pid = [2064, 2066] 58 | audio_pid = [2065, 2067] 59 | 60 | # 61 | # Network Information Table 62 | # this is a basic NIT with the minimum desciptors, OpenCaster has a big library ready to use 63 | # 64 | 65 | nit = network_information_section( 66 | network_id = 1, 67 | network_descriptor_loop = [ 68 | network_descriptor(network_name = "EBU-TECH"), 69 | ], 70 | transport_stream_loop = [ 71 | transport_stream_loop_item( 72 | transport_stream_id = demo_transport_stream_id, 73 | original_network_id = demo_original_network_id, 74 | transport_descriptor_loop = [ 75 | service_list_descriptor( #Optional 76 | dvb_service_descriptor_loop = [ 77 | service_descriptor_loop_item( 78 | service_ID = demo_service_id[0], 79 | service_type = 1, # digital tv service type 80 | ), 81 | service_descriptor_loop_item( 82 | service_ID = demo_service_id[1], 83 | service_type = 1, # digital tv service type 84 | ), 85 | ], 86 | ), 87 | ], 88 | ), 89 | ], 90 | version_number = 1, # you need to change the table number every time you edit, so the decoder will compare its version with the new one and update the table 91 | section_number = 0, 92 | last_section_number = 0, 93 | ) 94 | # 95 | # Program Association Table (ISO/IEC 13818-1 2.4.4.3) 96 | # 97 | 98 | pat = program_association_section( 99 | transport_stream_id = demo_transport_stream_id, 100 | program_loop = [ 101 | program_loop_item( 102 | program_number = demo_service_id[0], 103 | PID = pmt_pid[0], 104 | ), 105 | program_loop_item( 106 | program_number = demo_service_id[1], 107 | PID = pmt_pid[1], 108 | ), 109 | program_loop_item( 110 | program_number = 0, # special program for the NIT 111 | PID = 16, 112 | ), 113 | ], 114 | version_number = 1, # you need to change the table number every time you edit, so the decoder will compare its version with the new one and update the table 115 | section_number = 0, 116 | last_section_number = 0, 117 | ) 118 | 119 | # 120 | # Program Map Table (ISO/IEC 13818-1 2.4.4.8) 121 | # this is PMT for HbbTV interactive applications 122 | # 123 | 124 | pmt = [] 125 | 126 | for i in range(0, 2): 127 | pmt.append(program_map_section( 128 | program_number = demo_service_id[i], 129 | PCR_PID = 2064+i*2, # usualy the same than the video 130 | program_info_descriptor_loop = [], 131 | stream_loop = [ 132 | stream_loop_item( 133 | stream_type = 2, # mpeg2 video stream type 134 | elementary_PID = video_pid[i], 135 | element_info_descriptor_loop = [] 136 | ), 137 | stream_loop_item( 138 | stream_type = 3, # mpeg2 audio stream type 139 | elementary_PID = audio_pid[i], 140 | element_info_descriptor_loop = [] 141 | ), 142 | stream_loop_item( 143 | stream_type = 5, # AIT stream type 144 | elementary_PID = ait_pid[i], 145 | element_info_descriptor_loop = [ 146 | application_signalling_descriptor( 147 | application_type = 0x0010, # HbbTV service 148 | AIT_version = 1, # current ait version 149 | ), 150 | ] 151 | ), 152 | ], 153 | version_number = 1, # you need to change the table number every time you edit, so the decoder will compare its version with the new one and update the table 154 | section_number = 0, 155 | last_section_number = 0, 156 | )) 157 | 158 | # 159 | # Service Description Table (ETSI EN 300 468 5.2.3) 160 | # this is a basic SDT with the minimum desciptors, OpenCaster has a big library ready to use 161 | # 162 | sdt = service_description_section( 163 | transport_stream_id = demo_transport_stream_id, 164 | original_network_id = demo_original_network_id, 165 | service_loop = [ 166 | service_loop_item( 167 | service_ID = demo_service_id[0], 168 | EIT_schedule_flag = 0, # 0 no current even information is broadcasted, 1 broadcasted 169 | EIT_present_following_flag = 0, # 0 no next event information is broadcasted, 1 is broadcasted 170 | running_status = 4, # 4 service is running, 1 not running, 2 starts in a few seconds, 3 pausing 171 | free_CA_mode = 0, # 0 means service is not scrambled, 1 means at least a stream is scrambled 172 | service_descriptor_loop = [ 173 | service_descriptor( 174 | service_type = 1, # digital television service 175 | service_provider_name = "EBU TECH", 176 | service_name = "CPA CHANNEL 1", 177 | ), 178 | ], 179 | ), 180 | service_loop_item( 181 | service_ID = demo_service_id[1], 182 | EIT_schedule_flag = 0, # 0 no current even information is broadcasted, 1 broadcasted 183 | EIT_present_following_flag = 0, # 0 no next event information is broadcasted, 1 is broadcasted 184 | running_status = 4, # 4 service is running, 1 not running, 2 starts in a few seconds, 3 pausing 185 | free_CA_mode = 0, # 0 means service is not scrambled, 1 means at least a stream is scrambled 186 | service_descriptor_loop = [ 187 | service_descriptor( 188 | service_type = 1, # digital television service 189 | service_provider_name = "EBU TECH", 190 | service_name = "CPA CHANNEL 2", 191 | ), 192 | ], 193 | ), 194 | ], 195 | version_number = 1, # you need to change the table number every time you edit, so the decoder will compare its version with the new one and update the table 196 | section_number = 0, 197 | last_section_number = 0, 198 | ) 199 | # 200 | # Application Informaton Table (ETSI TS 101 812 10.4.6) 201 | # 202 | # 203 | 204 | ait = [] 205 | for i in range(0, 2): 206 | ait.append(application_information_section( 207 | application_type = 0x0010, 208 | common_descriptor_loop = [ 209 | external_application_authorisation_descriptor( 210 | application_identifiers = [[organisationId[i],applicationId[i]]], 211 | application_priority = [ 5 ] 212 | # This descriptor informs that 2 applications are available on the program by specifying the applications identifiers (couple of organization_Id and application_Id parameters) and their related priorities (5 for the first and 1 for the second). 213 | # Actualy our service contains only one application so this descriptor is not relevent and is just here to show you how to use this descriptor. 214 | # This descriptor is not mandatory and you could remove it (i.e. common_descriptor_loop = []). 215 | ) 216 | ], 217 | application_loop = [ 218 | application_loop_item( 219 | organisation_id = organisationId[i], # this is a demo value, dvb.org should assign an unique value 220 | application_id = applicationId[i], 221 | 222 | application_control_code = 1, 223 | # 2 is PRESENT, the decoder will add this application to the user choice of application 224 | # 1 is AUTOSTART, the application will start immedtiatly to load and to execute 225 | # 7 is DISABLED, The application shall not be started and attempts to start it shall fail. 226 | # 4 is KILL, it will stop execute the application 227 | application_descriptors_loop = [ 228 | transport_protocol_descriptor( 229 | protocol_id = 0x0003, # HTTP transport protocol 230 | URL_base = appli_root[i], 231 | URL_extensions = [], 232 | transport_protocol_label = 3, # HTTP transport protocol 233 | ), 234 | application_descriptor( 235 | application_profile = 0x0000, 236 | #0x0000 basic profile 237 | #0x0001 download feature 238 | #0x0002 PVR feature 239 | #0x0004 RTSP feature 240 | version_major = 1, # corresponding to version 1.1.1 241 | version_minor = 1, 242 | version_micro = 1, 243 | service_bound_flag = 1, # 1 means the application is expected to die on service change, 0 will wait after the service change to receive all the AITs and check if the same app is signalled or not 244 | visibility = 3, # 3 the applications is visible to the user, 1 the application is visible only to other applications 245 | application_priority = 1, # 1 is lowset, it is used when more than 1 applications is executing 246 | transport_protocol_labels = [3], # If more than one protocol is signalled then each protocol is an alternative delivery mechanism. The ordering indicates 247 | # the broadcaster's view of which transport connection will provide the best user experience (first is best) 248 | ), 249 | application_name_descriptor( 250 | application_name = appli_name[i], 251 | ISO_639_language_code = "FRA" 252 | ), 253 | simple_application_location_descriptor(initial_path_bytes = appli_path[i]), 254 | ] 255 | ), 256 | 257 | ], 258 | version_number = 1, 259 | section_number = 0, 260 | last_section_number = 0, 261 | )) 262 | 263 | 264 | 265 | 266 | # 267 | # PSI marshalling and encapsulation 268 | # 269 | out = open("./nit.sec", "wb") 270 | out.write(nit.pack()) 271 | out.close 272 | out = open("./nit.sec", "wb") # python flush bug 273 | out.close 274 | os.system('/usr/local/bin/sec2ts 16 < ./nit.sec > ./nit.ts') 275 | 276 | out = open("./sdt.sec", "wb") 277 | out.write(sdt.pack()) 278 | out.close 279 | out = open("./sdt.sec", "wb") # python flush bug 280 | out.close 281 | os.system('/usr/local/bin/sec2ts 17 < ./sdt.sec > ./sdt.ts') 282 | 283 | out = open("./pat.sec", "wb") 284 | out.write(pat.pack()) 285 | out.close 286 | out = open("./pat.sec", "wb") # python flush bug 287 | out.close 288 | os.system('/usr/local/bin/sec2ts 0 < ./pat.sec > ./pat.ts') 289 | 290 | for i in range(0, 2): 291 | 292 | out = open("./pmt"+str(i)+".sec", "wb") 293 | out.write(pmt[i].pack()) 294 | out.close 295 | out = open("./pmt"+str(i)+".sec", "wb") # python flush bug 296 | out.close 297 | os.system('/usr/local/bin/sec2ts ' + str(pmt_pid[i]) + ' < ./pmt'+str(i)+'.sec > ./pmt'+str(i)+'.ts') 298 | 299 | out = open("./ait"+str(i)+".sec", "wb") 300 | out.write(ait[i].pack()) 301 | out.close 302 | out = open("./ait"+str(i)+".sec", "wb") # python flush bug 303 | out.close 304 | os.system('/usr/local/bin/sec2ts ' + str(ait_pid[i]) + ' < ./ait'+str(i)+'.sec > ./ait'+str(i)+'.ts') 305 | 306 | os.system('rm *.sec') # deleting of the section files. 307 | -------------------------------------------------------------------------------- /make-stream.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | export INPUT1="channel1.mpg" 5 | export INPUT2="channel2.mpg" 6 | 7 | mkdir output 8 | mkdir tmp 9 | 10 | ffmpeg -i $INPUT1 -an -vcodec mpeg2video -f mpeg2video -b 5000k -maxrate 5000k -minrate 5000k -bf 2 -bufsize 1835008 -y tmp/video1.mp2 11 | esvideompeg2pes tmp/video1.mp2 > tmp/video1.pes 12 | pesvideo2ts 2064 25 112 5270000 0 tmp/video1.pes > tmp/video1.ts 13 | 14 | ffmpeg -i $INPUT1 -ac 2 -vn -acodec mp2 -f mp2 -ab 128000 -ar 48000 -y tmp/audio1.mp2 15 | esaudio2pes tmp/audio1.mp2 1152 48000 768 -1 3600 > tmp/audio1.pes 16 | pesaudio2ts 2065 1152 48000 768 -1 tmp/audio1.pes > tmp/audio1.ts 17 | 18 | ffmpeg -i $INPUT2 -an -vcodec mpeg2video -f mpeg2video -b 5000k -maxrate 5000k -minrate 5000k -bf 2 -bufsize 1835008 -y tmp/video2.mp2 19 | esvideompeg2pes tmp/video2.mp2 > tmp/video2.pes 20 | pesvideo2ts 2066 25 112 5270000 0 tmp/video2.pes > tmp/video2.ts 21 | 22 | ffmpeg -i $INPUT2 -ac 2 -vn -acodec mp2 -f mp2 -ab 128000 -ar 48000 -y tmp/audio2.mp2 23 | esaudio2pes tmp/audio2.mp2 1152 48000 768 -1 3600 > tmp/audio2.pes 24 | pesaudio2ts 2067 1152 48000 768 -1 tmp/audio2.pes > tmp/audio2.ts 25 | 26 | 27 | python ./create-metadata-ts.py 28 | 29 | tscbrmuxer c:2300000 tmp/video1.ts b:188000 tmp/audio1.ts b:2300000 tmp/video2.ts b:188000 tmp/audio2.ts b:3008 pat.ts b:3008 pmt0.ts b:3008 pmt1.ts b:1400 nit.ts b:1500 sdt.ts b:1400 ait0.ts b:1400 ait1.ts > output/final.ts -------------------------------------------------------------------------------- /null.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebu/hbbtv-dvbstream/c50bf36c98f7aa79dc810a121e655bd06ea6d018/null.ts --------------------------------------------------------------------------------