Line data Source code
1 : /*
2 : * GPAC - Multimedia Framework C SDK
3 : *
4 : * Authors: Jean Le Feuvre
5 : * Copyright (c) Telecom ParisTech 2017-2021
6 : * All rights reserved
7 : *
8 : * This file is part of GPAC / DVB4Linux input filter
9 : *
10 : * GPAC is free software; you can redistribute it and/or modify
11 : * it under the terms of the GNU Lesser General Public License as published by
12 : * the Free Software Foundation; either version 2, or (at your option)
13 : * any later version.
14 : *
15 : * GPAC is distributed in the hope that it will be useful,
16 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 : * GNU Lesser General Public License for more details.
19 : *
20 : * You should have received a copy of the GNU Lesser General Public
21 : * License along with this library; see the file COPYING. If not, write to
22 : * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 : *
24 : */
25 :
26 :
27 : #include <gpac/filters.h>
28 : #include <gpac/constants.h>
29 : #include <gpac/network.h>
30 :
31 : #ifndef WIN32
32 : //#define GPAC_HAS_LINUX_DVB
33 : //#define GPAC_SIM_LINUX_DVB
34 : #endif
35 :
36 :
37 : #ifdef GPAC_HAS_LINUX_DVB
38 :
39 : #include <fcntl.h>
40 : #include <sys/ioctl.h>
41 : #include <sys/stat.h>
42 : #include <unistd.h>
43 :
44 : #ifndef GPAC_SIM_LINUX_DVB
45 : #include <linux/dvb/dmx.h>
46 : #include <linux/dvb/frontend.h>
47 : #endif
48 :
49 : typedef struct
50 : {
51 : //options
52 : const char *src;
53 : const char *chcfg;
54 : u32 block_size;
55 :
56 : //only one output pid declared
57 : GF_FilterPid *pid;
58 :
59 : u32 freq;
60 : u16 vpid;
61 : u16 apid;
62 :
63 : #ifndef GPAC_SIM_LINUX_DVB
64 : fe_spectral_inversion_t specInv;
65 : fe_modulation_t modulation;
66 : fe_bandwidth_t bandwidth;
67 : fe_transmit_mode_t TransmissionMode;
68 : fe_guard_interval_t guardInterval;
69 : fe_code_rate_t HP_CodeRate;
70 : fe_code_rate_t LP_CodeRate;
71 : fe_hierarchy_t hierarchy;
72 : #endif
73 :
74 : int demux_fd;
75 :
76 : char *block;
77 :
78 : } GF_DVBLinuxCtx;
79 :
80 :
81 0 : static GF_Err dvblin_tune(GF_DVBLinuxCtx *ctx)
82 : {
83 : FILE *chanfile;
84 : char line[255];
85 : #ifndef GPAC_SIM_LINUX_DVB
86 : int demux1, front1;
87 : struct dmx_pes_filter_params pesFilterParams;
88 : struct dvb_frontend_parameters frp;
89 : char chan_name_t[255];
90 : char freq_str[255], inv[255], bw[255], lcr[255], hier[255], cr[255],
91 : mod[255], transm[255], gi[255], apid_str[255], vpid_str[255];
92 : const char *chan_conf = ":%255[^:]:%255[^:]:%255[^:]:%255[^:]:%255[^:]:%255[^:]:%255[^:]:%255[^:]:%255[^:]:%255[^:]:%255[^:]:";
93 : #endif
94 : char *chan_name;
95 : char *tmp;
96 : char frontend_name[100], demux_name[100], dvr_name[100];
97 : u32 adapter_num;
98 :
99 0 : if (!ctx->src) {
100 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[DVB4Lin] Missing URL\n"));
101 : return GF_BAD_PARAM;
102 : }
103 0 : if (!ctx->chcfg) {
104 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[DVB4Lin] Missing channels config file\n"));
105 : return GF_BAD_PARAM;
106 : }
107 0 : chanfile = gf_fopen(ctx->chcfg, "rb");
108 0 : if (!chanfile) return GF_BAD_PARAM;
109 :
110 0 : chan_name = (char *) ctx->src+6; // 6 = strlen("dvb://")
111 :
112 : // support for multiple frontends
113 0 : tmp = strchr(chan_name, '@');
114 0 : if (tmp) {
115 0 : adapter_num = atoi(tmp+1);
116 0 : tmp[0] = 0;
117 : } else {
118 : adapter_num = 0;
119 : }
120 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("Channel name %s\n", chan_name));
121 :
122 0 : while(!gf_feof(chanfile)) {
123 0 : if ( gf_fgets(line, 255, chanfile) != NULL) {
124 0 : if (line[0]=='#') continue;
125 0 : if (line[0]=='\r') continue;
126 0 : if (line[0]=='\n') continue;
127 :
128 : #ifndef GPAC_SIM_LINUX_DVB
129 0 : strncpy(chan_name_t, line, index(line, ':')-line);
130 0 : chan_name_t[254] = 0;
131 :
132 0 : if (strncmp(chan_name,chan_name_t,strlen(chan_name))==0) {
133 0 : sscanf(strstr(line,":"), chan_conf, freq_str, inv, bw, lcr, cr, mod, transm, gi, hier, apid_str, vpid_str);
134 0 : ctx->freq = (uint32_t) atoi(freq_str);
135 0 : ctx->apid = (uint16_t) atoi(apid_str);
136 0 : ctx->vpid = (uint16_t) atoi(vpid_str);
137 : //Inversion
138 0 : if(! strcmp(inv, "INVERSION_ON")) ctx->specInv = INVERSION_ON;
139 0 : else if(! strcmp(inv, "INVERSION_OFF")) ctx->specInv = INVERSION_OFF;
140 0 : else ctx->specInv = INVERSION_AUTO;
141 : //LP Code Rate
142 0 : if(! strcmp(lcr, "FEC_1_2")) ctx->LP_CodeRate =FEC_1_2;
143 0 : else if(! strcmp(lcr, "FEC_2_3")) ctx->LP_CodeRate =FEC_2_3;
144 0 : else if(! strcmp(lcr, "FEC_3_4")) ctx->LP_CodeRate =FEC_3_4;
145 0 : else if(! strcmp(lcr, "FEC_4_5")) ctx->LP_CodeRate =FEC_4_5;
146 0 : else if(! strcmp(lcr, "FEC_6_7")) ctx->LP_CodeRate =FEC_6_7;
147 0 : else if(! strcmp(lcr, "FEC_8_9")) ctx->LP_CodeRate =FEC_8_9;
148 0 : else if(! strcmp(lcr, "FEC_5_6")) ctx->LP_CodeRate =FEC_5_6;
149 0 : else if(! strcmp(lcr, "FEC_7_8")) ctx->LP_CodeRate =FEC_7_8;
150 0 : else if(! strcmp(lcr, "FEC_NONE")) ctx->LP_CodeRate =FEC_NONE;
151 0 : else ctx->LP_CodeRate =FEC_AUTO;
152 : //HP Code Rate
153 0 : if(! strcmp(cr, "FEC_1_2")) ctx->HP_CodeRate =FEC_1_2;
154 0 : else if(! strcmp(cr, "FEC_2_3")) ctx->HP_CodeRate =FEC_2_3;
155 0 : else if(! strcmp(cr, "FEC_3_4")) ctx->HP_CodeRate =FEC_3_4;
156 0 : else if(! strcmp(cr, "FEC_4_5")) ctx->HP_CodeRate =FEC_4_5;
157 0 : else if(! strcmp(cr, "FEC_6_7")) ctx->HP_CodeRate =FEC_6_7;
158 0 : else if(! strcmp(cr, "FEC_8_9")) ctx->HP_CodeRate =FEC_8_9;
159 0 : else if(! strcmp(cr, "FEC_5_6")) ctx->HP_CodeRate =FEC_5_6;
160 0 : else if(! strcmp(cr, "FEC_7_8")) ctx->HP_CodeRate =FEC_7_8;
161 0 : else if(! strcmp(cr, "FEC_NONE")) ctx->HP_CodeRate =FEC_NONE;
162 0 : else ctx->HP_CodeRate =FEC_AUTO;
163 : //Modulation
164 0 : if(! strcmp(mod, "QAM_128")) ctx->modulation = QAM_128;
165 0 : else if(! strcmp(mod, "QAM_256")) ctx->modulation = QAM_256;
166 0 : else if(! strcmp(mod, "QAM_64")) ctx->modulation = QAM_64;
167 0 : else if(! strcmp(mod, "QAM_32")) ctx->modulation = QAM_32;
168 0 : else if(! strcmp(mod, "QAM_16")) ctx->modulation = QAM_16;
169 : //Bandwidth
170 0 : if(! strcmp(bw, "BANDWIDTH_6_MHZ")) ctx->bandwidth = BANDWIDTH_6_MHZ;
171 0 : else if(! strcmp(bw, "BANDWIDTH_7_MHZ")) ctx->bandwidth = BANDWIDTH_7_MHZ;
172 0 : else if(! strcmp(bw, "BANDWIDTH_8_MHZ")) ctx->bandwidth = BANDWIDTH_8_MHZ;
173 : //Transmission Mode
174 0 : if(! strcmp(transm, "TRANSMISSION_MODE_2K")) ctx->TransmissionMode = TRANSMISSION_MODE_2K;
175 0 : else if(! strcmp(transm, "TRANSMISSION_MODE_8K")) ctx->TransmissionMode = TRANSMISSION_MODE_8K;
176 : //Guard Interval
177 0 : if(! strcmp(gi, "GUARD_INTERVAL_1_32")) ctx->guardInterval = GUARD_INTERVAL_1_32;
178 0 : else if(! strcmp(gi, "GUARD_INTERVAL_1_16")) ctx->guardInterval = GUARD_INTERVAL_1_16;
179 0 : else if(! strcmp(gi, "GUARD_INTERVAL_1_8")) ctx->guardInterval = GUARD_INTERVAL_1_8;
180 0 : else ctx->guardInterval = GUARD_INTERVAL_1_4;
181 : //Hierarchy
182 0 : if(! strcmp(hier, "HIERARCHY_1")) ctx->hierarchy = HIERARCHY_1;
183 0 : else if(! strcmp(hier, "HIERARCHY_2")) ctx->hierarchy = HIERARCHY_2;
184 0 : else if(! strcmp(hier, "HIERARCHY_4")) ctx->hierarchy = HIERARCHY_4;
185 0 : else if(! strcmp(hier, "HIERARCHY_AUTO")) ctx->hierarchy = HIERARCHY_AUTO;
186 0 : else ctx->hierarchy = HIERARCHY_NONE;
187 :
188 : break;
189 : }
190 : #endif
191 :
192 : }
193 : }
194 0 : gf_fclose(chanfile);
195 :
196 : sprintf(frontend_name, "/dev/dvb/adapter%d/frontend0", adapter_num);
197 : sprintf(demux_name, "/dev/dvb/adapter%d/demux0", adapter_num);
198 : sprintf(dvr_name, "/dev/dvb/adapter%d/dvr0", adapter_num);
199 :
200 : #ifndef GPAC_SIM_LINUX_DVB
201 : // Open frontend
202 0 : if((front1 = open(frontend_name,O_RDWR|O_NONBLOCK)) < 0) {
203 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Cannot open frontend %s.\n", frontend_name));
204 : return GF_IO_ERR;
205 : } else {
206 0 : GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("Frontend %s opened.\n", frontend_name));
207 : }
208 : // Open demuxes
209 0 : if ((demux1=open(demux_name, O_RDWR|O_NONBLOCK)) < 0) {
210 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Cannot open demux %s\n", demux_name));
211 : return GF_IO_ERR;
212 : } else {
213 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Demux %s opened.\n", demux_name));
214 : }
215 : // Set FrontendParameters - DVB-T
216 0 : frp.frequency = ctx->freq;
217 0 : frp.inversion = ctx->specInv;
218 0 : frp.u.ofdm.bandwidth = ctx->bandwidth;
219 0 : frp.u.ofdm.code_rate_HP = ctx->HP_CodeRate;
220 0 : frp.u.ofdm.code_rate_LP = ctx->LP_CodeRate;
221 0 : frp.u.ofdm.constellation = ctx->modulation;
222 0 : frp.u.ofdm.transmission_mode = ctx->TransmissionMode;
223 0 : frp.u.ofdm.guard_interval = ctx->guardInterval;
224 0 : frp.u.ofdm.hierarchy_information = ctx->hierarchy;
225 : // Set frontend
226 0 : if (ioctl(front1, FE_SET_FRONTEND, &frp) < 0) {
227 : return GF_IO_ERR;
228 : }
229 :
230 : // Set dumex
231 0 : pesFilterParams.pid = 0x2000; // Linux-DVB API take PID=2000 for FULL/RAW TS flag
232 0 : pesFilterParams.input = DMX_IN_FRONTEND;
233 0 : pesFilterParams.output = DMX_OUT_TS_TAP;
234 0 : pesFilterParams.pes_type = DMX_PES_OTHER;
235 0 : pesFilterParams.flags = DMX_IMMEDIATE_START;
236 0 : if (ioctl(demux1, DMX_SET_PES_FILTER, &pesFilterParams) < 0) {
237 : return GF_IO_ERR;
238 : }
239 : /* The following code differs from mplayer and alike because the device is opened in blocking mode */
240 0 : if ((ctx->demux_fd = open(dvr_name, O_RDONLY/*|O_NONBLOCK*/)) < 0) {
241 : return GF_IO_ERR;
242 : }
243 : #endif
244 :
245 0 : return GF_OK;
246 : }
247 :
248 0 : static u32 gf_dvblin_get_freq_from_url(GF_DVBLinuxCtx *ctx, const char *url)
249 : {
250 : FILE *chcfgig_file;
251 : char line[255], *tmp, *channel_name;
252 :
253 : u32 freq;
254 :
255 : /* get rid of trailing @ */
256 0 : tmp = strchr(url, '@');
257 0 : if (tmp) tmp[0] = 0;
258 :
259 0 : channel_name = (char *)url+6;
260 :
261 0 : chcfgig_file = gf_fopen(ctx->chcfg, "rb");
262 0 : if (!chcfgig_file) return GF_BAD_PARAM;
263 :
264 : freq = 0;
265 0 : while(!gf_feof(chcfgig_file)) {
266 0 : if ( gf_fgets(line, 255, chcfgig_file) != NULL) {
267 0 : if (line[0]=='#') continue;
268 0 : if (line[0]=='\r') continue;
269 0 : if (line[0]=='\n') continue;
270 :
271 0 : tmp = strchr(line, ':');
272 0 : tmp[0] = 0;
273 0 : if (!strcmp(line, channel_name)) {
274 : char *tmp2;
275 0 : tmp++;
276 0 : tmp2 = strchr(tmp, ':');
277 0 : if (tmp2) tmp2[0] = 0;
278 0 : freq = (u32)atoi(tmp);
279 : break;
280 : }
281 : }
282 : }
283 : return freq;
284 : }
285 :
286 0 : GF_Err dvblin_setup_demux(GF_DVBLinuxCtx *ctx)
287 : {
288 : GF_Err e = GF_OK;
289 :
290 0 : if (!ctx->chcfg) {
291 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[DVB4Lin] Missing channels config file\n"));
292 : return GF_BAD_PARAM;
293 : }
294 0 : if (strnicmp(ctx->src, "dvb://", 6)) return GF_NOT_SUPPORTED;
295 :
296 0 : if ((ctx->freq != 0) && (ctx->freq == gf_dvblin_get_freq_from_url(ctx, ctx->src)) ) {
297 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[M2TSDemux] Tuner already tuned to that frequency\n"));
298 : return GF_OK;
299 : }
300 :
301 0 : e = dvblin_tune(ctx);
302 0 : if (e) {
303 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[M2TSDemux] Unable to tune to frequency\n"));
304 : return GF_SERVICE_ERROR;
305 : }
306 : return GF_OK;
307 : }
308 :
309 :
310 :
311 :
312 0 : GF_Err dvblin_initialize(GF_Filter *filter)
313 : {
314 : GF_Err e = GF_OK;
315 0 : GF_DVBLinuxCtx *ctx = (GF_DVBLinuxCtx *) gf_filter_get_udta(filter);
316 :
317 0 : if (!ctx || !ctx->src) return GF_BAD_PARAM;
318 0 : e = dvblin_setup_demux(ctx);
319 :
320 0 : if (e) {
321 0 : GF_LOG(GF_LOG_ERROR, GF_LOG_NETWORK, ("[DVBLinux] Failed to open %s\n", ctx->src));
322 0 : gf_filter_setup_failure(filter, e);
323 0 : return GF_URL_ERROR;
324 : }
325 0 : GF_LOG(GF_LOG_INFO, GF_LOG_NETWORK, ("[DVBLinux] opening %s\n", ctx->src));
326 :
327 0 : ctx->block = gf_malloc(ctx->block_size +1);
328 0 : return GF_OK;
329 : }
330 :
331 0 : void dvblin_finalize(GF_Filter *filter)
332 : {
333 0 : GF_DVBLinuxCtx *ctx = (GF_DVBLinuxCtx *) gf_filter_get_udta(filter);
334 : #ifndef GPAC_SIM_LINUX_DVB
335 0 : if (ctx->demux_fd) close(ctx->demux_fd);
336 : #endif
337 0 : if (ctx->block) gf_free(ctx->block);
338 0 : }
339 :
340 2939 : GF_FilterProbeScore dvblin_probe_url(const char *url, const char *mime_type)
341 : {
342 2939 : if (!strnicmp(url, "dvb://", 6)) return GF_FPROBE_SUPPORTED;
343 2939 : return GF_FPROBE_NOT_SUPPORTED;
344 : }
345 :
346 :
347 0 : static Bool dvblin_process_event(GF_Filter *filter, const GF_FilterEvent *evt)
348 : {
349 0 : GF_DVBLinuxCtx *ctx = (GF_DVBLinuxCtx *) gf_filter_get_udta(filter);
350 :
351 0 : if (!evt->base.on_pid) return GF_FALSE;
352 0 : if (evt->base.on_pid != ctx->pid) return GF_FALSE;
353 :
354 0 : switch (evt->base.type) {
355 0 : case GF_FEVT_PLAY:
356 0 : dvblin_setup_demux(ctx);
357 0 : return GF_TRUE;
358 0 : case GF_FEVT_STOP:
359 : #ifndef GPAC_SIM_LINUX_DVB
360 0 : if (ctx->demux_fd) close(ctx->demux_fd);
361 0 : ctx->demux_fd = 0;
362 : #endif
363 0 : return GF_TRUE;
364 : default:
365 : break;
366 : }
367 : return GF_FALSE;
368 : }
369 :
370 0 : static GF_Err dvblin_process(GF_Filter *filter)
371 : {
372 : GF_FilterPacket *dst_pck;
373 : u8 *out_data;
374 0 : GF_DVBLinuxCtx *ctx = (GF_DVBLinuxCtx *) gf_filter_get_udta(filter);
375 :
376 0 : if (!ctx->freq) return GF_EOS;
377 :
378 : #ifndef GPAC_SIM_LINUX_DVB
379 0 : u32 nb_read = read(ctx->demux_fd, ctx->block, ctx->block_size);
380 0 : if (!nb_read) return GF_OK;
381 : #endif
382 :
383 0 : dst_pck = gf_filter_pck_new_alloc(ctx->pid, nb_read, &out_data);
384 0 : if (!dst_pck) return GF_OUT_OF_MEM;
385 0 : memcpy(out_data, ctx->block, nb_read);
386 0 : gf_filter_pck_set_framing(dst_pck, GF_TRUE, GF_TRUE);
387 0 : gf_filter_pck_send(dst_pck);
388 0 : return GF_OK;
389 : }
390 :
391 : #else
392 : static GF_Err dvblin_process(GF_Filter *filter)
393 : {
394 : return GF_EOS;
395 : }
396 : #endif //GPAC_HAS_LINUX_DVB
397 :
398 :
399 : #ifdef GPAC_HAS_LINUX_DVB
400 : #define OFFS(_n) #_n, offsetof(GF_DVBLinuxCtx, _n)
401 : #else
402 : #define OFFS(_n) #_n, -1
403 : #endif
404 :
405 : static const GF_FilterArgs DVBLinuxArgs[] =
406 : {
407 : { OFFS(src), "URL of source content - see filter help", GF_PROP_NAME, NULL, NULL, 0},
408 : { OFFS(block_size), "block size used to read file", GF_PROP_UINT, "65536", NULL, GF_FS_ARG_HINT_ADVANCED},
409 : { OFFS(chcfg), "path to channels.conf file", GF_PROP_NAME, NULL, NULL, 0},
410 : {0}
411 : };
412 :
413 : GF_FilterRegister DVBLinuxRegister = {
414 : .name = "dvbin",
415 : GF_FS_SET_DESCRIPTION("DVB for Linux")
416 : GF_FS_SET_HELP("Experimental DVB support for linux, requires a channel config file through [-chcfg]()\n"
417 : " \n"
418 : "The URL syntax is `dvb://CHANNAME[@FRONTEND]`, with:\n"
419 : " - CHANNAME: the channel name as listed in the channel config file\n"
420 : " - frontend: the index of the DVB adapter to use (optional, default is 0)\n"
421 : )
422 : .args = DVBLinuxArgs,
423 : #ifdef GPAC_HAS_LINUX_DVB
424 : .private_size = sizeof(GF_DVBLinuxCtx),
425 : .initialize = dvblin_initialize,
426 : .finalize = dvblin_finalize,
427 : .process = dvblin_process,
428 : .process_event = dvblin_process_event,
429 : .probe_url = dvblin_probe_url
430 : #else
431 : .process = dvblin_process,
432 : #endif
433 : };
434 :
435 2877 : const GF_FilterRegister *dvblin_register(GF_FilterSession *session)
436 : {
437 : #if !defined(GPAC_HAS_LINUX_DVB) || defined(GPAC_SIM_LINUX_DVB)
438 : if (!gf_opts_get_bool("temp", "gendoc"))
439 : return NULL;
440 : DVBLinuxRegister.version = "! Warning: DVB4Linux NOT AVAILABLE IN THIS BUILD !";
441 : #endif
442 2877 : return &DVBLinuxRegister;
443 : }
444 :
|