Commit bcac7691 authored by massiot's avatar massiot

* dvblast: Initial import of DVBlast 1.0.


git-svn-id: svn://svn.videolan.org/dvblast/trunk@1 55d3f8b6-4a41-4d2d-a900-313d1436a5b8
parents
DVBlast is mostly written by Christophe Massiot <massiot@via.ecp.fr>. Parts of the code may be
borrowed from VLC, from various contributors.
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 2 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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.
dvblast-1.0:
- First public release
Installing DVBlast
==================
No autotools yet... You have to tweak the Makefile by hand, especially
if your kernel is S2API-enabled but not your distribution (indicate the
path of your kernel where appropriate). Compile the program with `make`.
Tuning DVBlast
==============
You usually want to supply DVBlast with the parameters of a transponder,
for instance for DVB-S :
dvblast -f 11570000 -s 27500000 -v 18
This tunes to frequency 11570 MHz, symbol rate 27500, horizontal (-v 18). For
DVB-S2, you must indicate a modulation with -m qpsk or -m psk_8. For DVB-T,
a bandwidth (usually -b 8 for 8 MHz multiplexes).
Configuring outputs
===================
DVBlast reads a configuration file containing one or several lines in the
format :
<IP>[:<port>] <always on> <SID> [<PID>,]*
For instance :
239.255.0.1:1234 1 10750 1234,1235,1236
The configuration file can be reloaded by sending "HUP" to the program, or
via the dvblastctl program.
The "always on" flag tells DVBlast whether the channel is expected to
be on at all times or if it may break. If set to "1", then DVBlast will
regularly reset the CAM module if it fails to descramble the service,
assuming the module is dead. Every time it is reset a few TS packets
will be lost, that is why this feature is optional.
There are three ways of configuring the PIDs to stream :
1. SID-based
239.255.0.1:1234 1 10750
DVBlast will stream all known PIDs from service 10750 (video, audio, and
subtitles). The resulting stream is fully MPEG-compliant, with PAT and PMT.
2. SID and PIDs
239.255.0.1:1234 1 10750 1234,1235
DVBlast will stream SID 10750, but only PID 1234 and 1235 will be output.
Other known PIDs will be discarded and removed from the PMT. The list of
PIDs in the config file does not include the PAT and PMT, but it must
include the PCR PID if it is different from the video or audio PID, otherwise
the stream won't be compliant.
3. PIDs only
239.255.0.1:1234 1 0 0,128,1234,1235
DVBlast will only stream the PIDs passed. No PAT and PMT will be generated,
so if they are not included the stream won't be compliant. Also the
included PAT and PMT may contain ghost programs or ESes.
Note that the CAM will not be programmed in that case (unless it has
been programmed by another line of the config file). The file is read
from the command-line :
dvblast -c /tmp/dvblast.conf
Monitoring
==========
If dvblast is run with the -r option, the dvblastctl program can be used
to retrieve information from DVBlast about the front-end or CAM module.
dvblastctl -r /tmp/dvblast.sock fe_status
dvblastctl -r /tmp/dvblast.sock mmi_status
dvblastctl -r /tmp/dvblast.sock shutdown
CAM menu
========
For easier access to the CAM menus, the dvblast_mmi.sh shell script is
provided :
dvblast_mmi.sh -r /tmp/dvblast.sock
Advanced features
=================
DVBlast may handle several DVB adapters in the same machine with the -a switch.
For better latency, run DVBlast in real-time priority (-i 1).
DVBlast can also stream the entire transponder to an address :
dvblast -u -d 172.16.42.42:1235 -f 11570000 -s 27500000 -v 18
The -u switch disables the PID filters, so that all PIDs, even the
unused ones, can be output.
# DVBlast Makefile
# Customise the path of your kernel
CFLAGS += -Wall -O3 -fomit-frame-pointer
CFLAGS += -g
CFLAGS += -I/usr/src/kernel/linux-2.6.29.1/include
LDFLAGS_DVBLAST += -ldvbpsi -lpthread
OBJ_DVBLAST = dvblast.o util.o dvb.o demux.o output.o en50221.o comm.o
OBJ_DVBLASTCTL = util.o dvblastctl.o
all: dvblast dvblastctl
$(OBJ_DVBLAST) $(OBJ_DVBLASTCTL): Makefile dvblast.h en50221.h comm.h
dvblast: $(OBJ_DVBLAST)
$(CC) -o $@ $(OBJ_DVBLAST) $(LDFLAGS_DVBLAST)
dvblastctl: $(OBJ_DVBLASTCTL)
clean:
-rm -f dvblast dvblastctl $(OBJ_DVBLAST) $(OBJ_DVBLASTCTL)
Welcome to DVBlast !
DVBlast is a simple and powerful streaming application based on the
linux-dvb API. It opens a DVB device, tunes it, places PID filters,
configures a CAM module, and demultiplexes the packets to several RTP
outputs. The configuration file for RTP outputs can be reloaded without
losing a single packet. All of this with a minimal memory and CPU
footprint. It has very few dependancies (libdvbpsi and libc). It also
supports the new S2API of linux-dvb.
--
Meuuh 2009-05-05
- Test and enhance the API for DVB-C, H, and ATSC support
- Win32 support
- Improve build system (autostuff)
- Parse the IOD to get a more precise idea of useful PIDs
/*****************************************************************************
* comm.c
*****************************************************************************
* Copyright (C) 2008 the VideoLAN team
* $Id: dvblast.c 11 2007-08-09 18:54:37Z cmassiot $
*
* Authors: Christophe Massiot <massiot@via.ecp.fr>
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <errno.h>
#include "dvblast.h"
#include "en50221.h"
#include "comm.h"
/*****************************************************************************
* Local declarations
*****************************************************************************/
int i_comm_fd = -1;
/*****************************************************************************
* comm_Open
*****************************************************************************/
void comm_Open( void )
{
int i_mask;
int i_size = 65535;
struct sockaddr_un sun_server;
if ( (i_comm_fd = socket( AF_UNIX, SOCK_DGRAM, 0 )) == -1 )
{
msg_Err( NULL, "cannot create comm socket (%s)", strerror(errno) );
return;
}
setsockopt( i_comm_fd, SOL_SOCKET, SO_RCVBUF, &i_size, sizeof(i_size) );
memset( &sun_server, 0, sizeof(sun_server) );
sun_server.sun_family = AF_UNIX;
strncpy( sun_server.sun_path, psz_srv_socket, sizeof(sun_server.sun_path) );
sun_server.sun_path[sizeof(sun_server.sun_path) - 1] = '\0';
i_mask = umask(077);
if ( bind( i_comm_fd, (struct sockaddr *)&sun_server,
SUN_LEN(&sun_server) ) < 0 )
{
msg_Err( NULL, "cannot bind comm socket (%s)", strerror(errno) );
umask( i_mask );
close( i_comm_fd );
i_comm_fd = -1;
return;
}
umask( i_mask );
}
/*****************************************************************************
* comm_Read
*****************************************************************************/
void comm_Read( void )
{
struct sockaddr_un sun_client;
socklen_t sun_length = sizeof(sun_client);
ssize_t i_size, i_answer_size = 0;
uint8_t p_buffer[COMM_BUFFER_SIZE], p_answer[COMM_BUFFER_SIZE];
uint8_t i_command, i_answer;
i_size = recvfrom( i_comm_fd, p_buffer, COMM_BUFFER_SIZE, 0,
(struct sockaddr *)&sun_client, &sun_length );
if ( i_size < COMM_HEADER_SIZE )
{
msg_Err( NULL, "cannot read comm socket (%d:%s)\n", i_size,
strerror(errno) );
return;
}
if ( p_buffer[0] != COMM_HEADER_MAGIC )
{
msg_Err( NULL, "wrong protocol version 0x%x", p_buffer[0] );
return;
}
i_command = p_buffer[1];
switch ( i_command )
{
case CMD_RELOAD:
b_hup_received = 1;
i_answer = RET_OK;
i_answer_size = 0;
break;
case CMD_FRONTEND_STATUS:
i_answer = dvb_FrontendStatus( p_answer + COMM_HEADER_SIZE,
&i_answer_size );
break;
case CMD_MMI_STATUS:
i_answer = en50221_StatusMMI( p_answer + COMM_HEADER_SIZE,
&i_answer_size );
break;
case CMD_MMI_SLOT_STATUS:
i_answer = en50221_StatusMMISlot( p_buffer + COMM_HEADER_SIZE,
i_size - COMM_HEADER_SIZE,
p_answer + COMM_HEADER_SIZE,
&i_answer_size );
break;
case CMD_MMI_OPEN:
i_answer = en50221_OpenMMI( p_buffer + COMM_HEADER_SIZE,
i_size - COMM_HEADER_SIZE );
break;
case CMD_MMI_CLOSE:
i_answer = en50221_CloseMMI( p_buffer + COMM_HEADER_SIZE,
i_size - COMM_HEADER_SIZE );
break;
case CMD_MMI_RECV:
i_answer = en50221_GetMMIObject( p_buffer + COMM_HEADER_SIZE,
i_size - COMM_HEADER_SIZE,
p_answer + COMM_HEADER_SIZE,
&i_answer_size );
break;
case CMD_MMI_SEND:
i_answer = en50221_SendMMIObject( p_buffer + COMM_HEADER_SIZE,
i_size - COMM_HEADER_SIZE );
break;
case CMD_SHUTDOWN:
msg_Err( NULL, "shutdown via comm" );
exit(EXIT_SUCCESS);
/* this is a bit violent, but hey, closing everything cleanly
* would do approximately the same */
default:
msg_Err( NULL, "wrong command %u", i_command );
i_answer = RET_HUH;
i_answer_size = 0;
break;
}
p_answer[0] = COMM_HEADER_MAGIC;
p_answer[1] = i_answer;
p_answer[2] = 0;
p_answer[3] = 0;
msg_Dbg( NULL, "answering %d to %d with size %d", i_answer, i_command,
i_answer_size );
if ( sendto( i_comm_fd, p_answer, i_answer_size + COMM_HEADER_SIZE, 0,
(struct sockaddr *)&sun_client, sun_length ) < 0 )
msg_Err( NULL, "cannot send comm socket (%s)", strerror(errno) );
}
/*****************************************************************************
* comm.h
*****************************************************************************
* Copyright (C) 2008 the VideoLAN team
* $Id: dvblast.h 3 2005-10-05 12:15:55Z cmassiot $
*
* Authors: Christophe Massiot <massiot@via.ecp.fr>
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/* DVB Card Drivers */
#include <linux/dvb/version.h>
#include <linux/dvb/dmx.h>
#include <linux/dvb/frontend.h>
#include <linux/dvb/ca.h>
#define COMM_BUFFER_SIZE 4096
#define COMM_HEADER_SIZE 4
#define COMM_HEADER_MAGIC 0x48
#define CMD_RELOAD 1
#define CMD_SHUTDOWN 2
#define CMD_FRONTEND_STATUS 3
#define CMD_MMI_STATUS 4
#define CMD_MMI_SLOT_STATUS 5 /* arg: slot */
#define CMD_MMI_OPEN 6 /* arg: slot */
#define CMD_MMI_CLOSE 7 /* arg: slot */
#define CMD_MMI_RECV 8 /* arg: slot */
#define CMD_MMI_SEND 9 /* arg: slot, en50221_mmi_object_t */
#define RET_OK 0
#define RET_ERR 1
#define RET_FRONTEND_STATUS 2
#define RET_MMI_STATUS 3
#define RET_MMI_SLOT_STATUS 4
#define RET_MMI_RECV 5
#define RET_HUH 255
struct ret_frontend_status
{
struct dvb_frontend_info info;
fe_status_t i_status;
int32_t i_ber, i_strength, i_snr;
};
struct ret_mmi_status
{
ca_caps_t caps;
};
struct ret_mmi_slot_status
{
ca_slot_info_t sinfo;
};
struct ret_mmi_recv
{
en50221_mmi_object_t object;
};
struct cmd_mmi_send
{
uint8_t i_slot;
en50221_mmi_object_t object;
};
/*****************************************************************************
* demux.c
*****************************************************************************
* Copyright (C) 2004, 2008-2009 the VideoLAN team
* $Id: demux.c 7 2006-01-25 16:26:44Z cmassiot $
*
* Authors: Christophe Massiot <massiot@via.ecp.fr>
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "dvblast.h"
#include "en50221.h"
#include <dvbpsi/demux.h>
#include <dvbpsi/pat.h>
#include <dvbpsi/eit.h>
#include <dvbpsi/dr.h>
#include <dvbpsi/psi.h>
/*****************************************************************************
* Local declarations
*****************************************************************************/
typedef struct ts_pid_t
{
int i_refcount;
int b_pes;
int i_last_cc;
int i_demux_fd;
output_t **pp_outputs;
int i_nb_outputs;
} ts_pid_t;
typedef struct sid_t
{
uint16_t i_sid, i_pmt_pid;
dvbpsi_handle p_dvbpsi_handle;
dvbpsi_pmt_t *p_current_pmt;
} sid_t;
ts_pid_t p_pids[8192];
static sid_t **pp_sids = NULL;
static int i_nb_sids = 0;
static dvbpsi_handle p_pat_dvbpsi_handle;
static dvbpsi_pat_t *p_current_pat = NULL;
static int i_demux_fd;
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static void demux_Handle( block_t *p_ts );
static void SetPID( uint16_t i_pid );
static void UnsetPID( uint16_t i_pid );
static void StartPID( output_t *p_output, uint16_t i_pid );
static void StopPID( output_t *p_output, uint16_t i_pid );
static void SelectPID( uint16_t i_sid, uint16_t i_pid );
static void UnselectPID( uint16_t i_sid, uint16_t i_pid );
static void SelectPSI( uint16_t i_sid, uint16_t i_pid );
static void UnselectPSI( uint16_t i_sid, uint16_t i_pid );
static void GetPIDS( uint16_t **ppi_wanted_pids, int *pi_nb_wanted_pids,
uint16_t i_sid,
const uint16_t *pi_pids, int i_nb_pids );
static int SIDIsSelected( uint16_t i_sid );
int PIDWouldBeSelected( dvbpsi_pmt_es_t *p_es );
static int PMTNeedsDescrambling( dvbpsi_pmt_t *p_pmt );
static void SendPAT( void );
static void SendPMT( sid_t *p_sid );
static void NewPAT( output_t *p_output );
static void NewPMT( output_t *p_output );
static void PATCallback( void *_unused, dvbpsi_pat_t *p_pat );
static void PMTCallback( void *_unused, dvbpsi_pmt_t *p_pmt );
/*****************************************************************************
* demux_Open
*****************************************************************************/
void demux_Open( void )
{
int i;
memset( p_pids, 0, sizeof(p_pids) );
dvb_Open();
for ( i = 0; i < 8192; i++ )
{
p_pids[i].i_last_cc = -1;
p_pids[i].i_demux_fd = -1;
}
if ( b_budget_mode )
i_demux_fd = dvb_SetFilter(8192);
SetPID(0); /* PAT */
p_pat_dvbpsi_handle = dvbpsi_AttachPAT( PATCallback, NULL );
}
/*****************************************************************************
* demux_Run
*****************************************************************************/
void demux_Run( void )
{
block_t *p_ts = dvb_Read();
while ( p_ts != NULL )
{
block_t *p_next = p_ts->p_next;
p_ts->p_next = NULL;
demux_Handle( p_ts );
p_ts = p_next;
}
}
/*****************************************************************************
* demux_Handle
*****************************************************************************/
static void demux_Handle( block_t *p_ts )
{
mtime_t i_wallclock = mdate();
uint16_t i_pid = block_GetPID( p_ts );
uint8_t i_cc = block_GetCC( p_ts );
int i;
if ( block_GetSync( p_ts ) != 0x47 )
{
msg_Warn( NULL, "invalid sync (0x%x)", p_ts->p_ts[0] );
block_Delete( p_ts );
return;
}
if ( i_pid != PADDING_PID && p_pids[i_pid].i_last_cc != -1
&& p_pids[i_pid].i_last_cc != i_cc /* dup */
&& (p_pids[i_pid].i_last_cc + 17 - i_cc) % 16 )
msg_Warn( NULL, "discontinuity for PID %d", i_pid );
p_pids[i_pid].i_last_cc = i_cc;
if ( block_HasTransportError( p_ts ) )
msg_Warn( NULL, "transport_error_indicator" );
if ( p_pids[i_pid].i_refcount )
{
if ( i_pid == 0 )
{
dvbpsi_PushPacket( p_pat_dvbpsi_handle, p_ts->p_ts );
if ( block_UnitStart( p_ts ) )
SendPAT();
}
else
{
for ( i = 0; i < i_nb_sids; i++ )
{
if ( pp_sids[i]->i_sid && pp_sids[i]->i_pmt_pid == i_pid )
{
dvbpsi_PushPacket( pp_sids[i]->p_dvbpsi_handle,
p_ts->p_ts );
if ( block_UnitStart( p_ts ) )
SendPMT( pp_sids[i] );
}
}
}
}
if ( block_HasPCR( p_ts ) )
{
mtime_t i_timestamp = block_GetPCR( p_ts );
for ( i = 0; i < i_nb_sids; i++ )
{
if ( pp_sids[i]->i_sid && pp_sids[i]->p_current_pmt != NULL
&& pp_sids[i]->p_current_pmt->i_pcr_pid == i_pid )
{
uint16_t i_sid = pp_sids[i]->i_sid;
int j;
for ( j = 0; j < i_nb_outputs; j++ )
{
if ( pp_outputs[j]->i_sid == i_sid )
{
pp_outputs[j]->i_ref_timestamp = i_timestamp;
pp_outputs[j]->i_ref_wallclock = i_wallclock;
}
}
}
}
}
for ( i = 0; i < p_pids[i_pid].i_nb_outputs; i++ )
{
output_t *p_output = p_pids[i_pid].pp_outputs[i];
if ( p_output != NULL )
{
if ( i_ca_handle && p_output->b_watch )
{
uint8_t *p_payload;
if ( block_GetScrambling( p_ts ) ||
( block_UnitStart( p_ts ) && p_pids[i_pid].b_pes
&& (p_payload = block_GetPayload( p_ts )) != NULL
&& p_payload + 3 < p_ts->p_ts + TS_SIZE
&& (p_payload[0] != 0 || p_payload[1] != 0
|| p_payload[2] != 1) ) )
{
p_output->i_nb_errors++;
p_output->i_last_error = i_wallclock;
}
else if ( i_wallclock > p_output->i_last_error + WATCHDOG_WAIT )
p_output->i_nb_errors = 0;
if ( p_output->i_nb_errors > MAX_ERRORS )
{
struct in_addr s;
int j;
for ( j = 0; j < i_nb_outputs; j++ )
pp_outputs[j]->i_nb_errors = 0;
s.s_addr = p_output->i_maddr;
msg_Warn( NULL,
"too many errors for stream %s:%d, resetting",
inet_ntoa( s ), p_output->i_port );
en50221_Reset();
}
}
output_Put( p_output, p_ts );
}
}
if ( output_dup.i_maddr )
output_Put( &output_dup, p_ts );
p_ts->i_refcount--;
if ( !p_ts->i_refcount )
block_Delete( p_ts );
}
/*****************************************************************************
* demux_Change : called from main thread
*****************************************************************************/
static int IsIn( uint16_t *pi_pids, int i_nb_pids, uint16_t i_pid )
{
int i;
for ( i = 0; i < i_nb_pids; i++ )
if ( i_pid == pi_pids[i] ) break;
return ( i != i_nb_pids );
}
void demux_Change( output_t *p_output, uint16_t i_sid,
uint16_t *pi_pids, int i_nb_pids )
{
int i;
uint16_t *pi_wanted_pids, *pi_current_pids;
int i_nb_wanted_pids, i_nb_current_pids;
uint16_t i_old_sid = p_output->i_sid;
int b_change = 0;
if ( i_sid == p_output->i_sid && i_nb_pids == p_output->i_nb_pids &&
(!i_nb_pids ||
!memcmp( p_output->pi_pids, pi_pids, i_nb_pids * sizeof(uint16_t) )) )
return; /* No change */
GetPIDS( &pi_wanted_pids, &i_nb_wanted_pids, i_sid, pi_pids, i_nb_pids );
GetPIDS( &pi_current_pids, &i_nb_current_pids, p_output->i_sid,
p_output->pi_pids, p_output->i_nb_pids );
for ( i = 0; i < i_nb_current_pids; i++ )
{
if ( pi_current_pids[i] != EMPTY_PID &&
!IsIn( pi_wanted_pids, i_nb_wanted_pids, pi_current_pids[i] ) )
{
StopPID( p_output, pi_current_pids[i] );
b_change = 1;
}
}
for ( i = 0; i < i_nb_wanted_pids; i++ )
{
if ( pi_wanted_pids[i] != EMPTY_PID &&
!IsIn( pi_current_pids, i_nb_current_pids, pi_wanted_pids[i] ) )
{
StartPID( p_output, pi_wanted_pids[i] );
b_change = 1;
}
}
free( pi_wanted_pids );
free( pi_current_pids );
if ( i_sid && p_output->i_sid != i_sid )
{
for ( i = 0; i < i_nb_sids; i++ )
{
if ( pp_sids[i]->i_sid == i_sid )
{
SetPID( pp_sids[i]->i_pmt_pid );
if ( pp_sids[i]->p_current_pmt != NULL )
{
p_output->b_valid = 1;
if ( i_ca_handle && !SIDIsSelected( i_sid )
&& PMTNeedsDescrambling( pp_sids[i]->p_current_pmt ) )
en50221_AddPMT( pp_sids[i]->p_current_pmt );
break;
}
}
}
}
p_output->i_sid = i_sid;
free( p_output->pi_pids );
p_output->pi_pids = malloc( sizeof(uint16_t) * i_nb_pids );
memcpy( p_output->pi_pids, pi_pids, sizeof(uint16_t) * i_nb_pids );
p_output->i_nb_pids = i_nb_pids;
if ( i_old_sid && i_old_sid != i_sid )
{
for ( i = 0; i < i_nb_sids; i++ )
{
if ( pp_sids[i]->i_sid == i_old_sid )
{
UnsetPID( pp_sids[i]->i_pmt_pid );
if ( i_ca_handle && !SIDIsSelected( i_old_sid )
&& pp_sids[i]->p_current_pmt != NULL
&& PMTNeedsDescrambling( pp_sids[i]->p_current_pmt ) )
en50221_DeletePMT( pp_sids[i]->p_current_pmt );
break;
}
}
}
if ( i_sid != i_old_sid )
{
NewPAT( p_output );
NewPMT( p_output );
}
else if ( b_change )
{
NewPMT( p_output );
for ( i = 0; i < i_nb_sids; i++ )
{
if ( pp_sids[i]->i_sid == i_sid )
{
if ( pp_sids[i]->p_current_pmt != NULL
&& PMTNeedsDescrambling( pp_sids[i]->p_current_pmt ) )
en50221_UpdatePMT( pp_sids[i]->p_current_pmt );
break;
}
}
}
}
/*****************************************************************************
* SetPID/UnsetPID
*****************************************************************************/
static void SetPID( uint16_t i_pid )
{
p_pids[i_pid].i_refcount++;
if ( !b_budget_mode && p_pids[i_pid].i_refcount
&& p_pids[i_pid].i_demux_fd == -1 )
p_pids[i_pid].i_demux_fd = dvb_SetFilter( i_pid );
}
static void UnsetPID( uint16_t i_pid )
{
p_pids[i_pid].i_refcount--;
if ( !b_budget_mode && !p_pids[i_pid].i_refcount
&& p_pids[i_pid].i_demux_fd != -1 )
{
dvb_UnsetFilter( p_pids[i_pid].i_demux_fd, i_pid );
p_pids[i_pid].i_demux_fd = -1;
}
}
/*****************************************************************************
* StartPID/StopPID
*****************************************************************************/
static void StartPID( output_t *p_output, uint16_t i_pid )
{
int j;
for ( j = 0; j < p_pids[i_pid].i_nb_outputs; j++ )
if ( p_pids[i_pid].pp_outputs[j] == p_output )
break;
if ( j == p_pids[i_pid].i_nb_outputs )
{
for ( j = 0; j < p_pids[i_pid].i_nb_outputs; j++ )
if ( p_pids[i_pid].pp_outputs[j] == NULL )
break;
if ( j == p_pids[i_pid].i_nb_outputs )
{
p_pids[i_pid].i_nb_outputs++;
p_pids[i_pid].pp_outputs = realloc( p_pids[i_pid].pp_outputs,
sizeof(output_t *)
* p_pids[i_pid].i_nb_outputs );
}
p_pids[i_pid].pp_outputs[j] = p_output;
}
SetPID( i_pid );
}
static void StopPID( output_t *p_output, uint16_t i_pid )
{
int b_wanted = 0;
int j;
for ( j = 0; j < p_pids[i_pid].i_nb_outputs; j++ )
{
if ( p_pids[i_pid].pp_outputs[j] != NULL )
{
if ( p_pids[i_pid].pp_outputs[j] == p_output )
break;
b_wanted = 1;
}
}
if ( j == p_pids[i_pid].i_nb_outputs )
msg_Warn( NULL, "unselecting an unselected PID %d", i_pid );
else
p_pids[i_pid].pp_outputs[j] = NULL;
for ( j++; !b_wanted && j < p_pids[i_pid].i_nb_outputs; j++ )
if ( p_pids[i_pid].pp_outputs[j] != NULL )
b_wanted = 1;
UnsetPID( i_pid );
}
/*****************************************************************************
* SelectPID/UnselectPID
*****************************************************************************/
static void SelectPID( uint16_t i_sid, uint16_t i_pid )
{
int i;
for ( i = 0; i < i_nb_outputs; i++ )
if ( pp_outputs[i]->i_maddr && pp_outputs[i]->i_sid == i_sid
&& !pp_outputs[i]->i_nb_pids )
StartPID( pp_outputs[i], i_pid );
}
static void UnselectPID( uint16_t i_sid, uint16_t i_pid )
{
int i;
for ( i = 0; i < i_nb_outputs; i++ )
if ( pp_outputs[i]->i_maddr && pp_outputs[i]->i_sid == i_sid
&& !pp_outputs[i]->i_nb_pids )
StopPID( pp_outputs[i], i_pid );
}
/*****************************************************************************
* SelectPSI/UnselectPSI
*****************************************************************************/
static void SelectPSI( uint16_t i_sid, uint16_t i_pid )
{
int i;
for ( i = 0; i < i_nb_outputs; i++ )
if ( pp_outputs[i]->i_maddr && pp_outputs[i]->i_sid == i_sid )
SetPID( i_pid );
}
static void UnselectPSI( uint16_t i_sid, uint16_t i_pid )
{
int i;
for ( i = 0; i < i_nb_outputs; i++ )
if ( pp_outputs[i]->i_maddr && pp_outputs[i]->i_sid == i_sid )
UnsetPID( i_pid );
}
/*****************************************************************************
* GetPIDS
*****************************************************************************/
static void GetPIDS( uint16_t **ppi_wanted_pids, int *pi_nb_wanted_pids,
uint16_t i_sid,
const uint16_t *pi_pids, int i_nb_pids )
{
dvbpsi_pmt_t *p_pmt = NULL;
uint16_t i_pmt_pid;
dvbpsi_pmt_es_t *p_es;
int i;
if ( i_nb_pids || i_sid == 0 )
{
*pi_nb_wanted_pids = i_nb_pids;
*ppi_wanted_pids = malloc( sizeof(uint16_t) * i_nb_pids );
memcpy( *ppi_wanted_pids, pi_pids, sizeof(uint16_t) * i_nb_pids );
return;
}
*pi_nb_wanted_pids = 0;
*ppi_wanted_pids = NULL;
for ( i = 0; i < i_nb_sids; i++ )
{
if ( pp_sids[i]->i_sid == i_sid )
{
p_pmt = pp_sids[i]->p_current_pmt;
i_pmt_pid = pp_sids[i]->i_pmt_pid;
break;
}
}
if ( p_pmt == NULL )
return;
i = 0;
for ( p_es = p_pmt->p_first_es; p_es != NULL; p_es = p_es->p_next )
if ( PIDWouldBeSelected( p_es ) )
{
*ppi_wanted_pids = realloc( *ppi_wanted_pids,
(*pi_nb_wanted_pids + 1) * sizeof(uint16_t) );
(*ppi_wanted_pids)[(*pi_nb_wanted_pids)++] = p_es->i_pid;
}
if ( p_pmt->i_pcr_pid != PADDING_PID && p_pmt->i_pcr_pid != i_pmt_pid
&& !IsIn( *ppi_wanted_pids, *pi_nb_wanted_pids, p_pmt->i_pcr_pid ) )
{
*ppi_wanted_pids = realloc( *ppi_wanted_pids,
(*pi_nb_wanted_pids + 1) * sizeof(uint16_t) );
(*ppi_wanted_pids)[(*pi_nb_wanted_pids)++] = p_pmt->i_pcr_pid;
}
}
/*****************************************************************************
* WritePSISection
*****************************************************************************/
static block_t *WritePSISection( dvbpsi_psi_section_t *p_section,
uint16_t i_pid, uint8_t *pi_cc )
{
block_t *p_block, **pp_last = &p_block;
uint32_t i_length;
uint8_t *p_data = p_section->p_data;
int b_first = 1;
i_length = (uint32_t)( p_section->p_payload_end - p_section->p_data ) +
( p_section->b_syntax_indicator ? 4 : 0 );
do
{
uint32_t i_copy = i_length > (184 - b_first) ? (184 - b_first) :
i_length;
int b_adaptation_field = (i_copy < (184 - b_first));
block_t *p_ts;
p_ts = *pp_last = block_New();
pp_last = &p_ts->p_next;
/* write header
* 8b 0x47 sync byte
* 1b transport_error_indicator
* 1b payload_unit_start
* 1b transport_priority
* 13b pid
* 2b transport_scrambling_control
* 2b if adaptation_field 0x03 else 0x01
* 4b continuity_counter
*/
p_ts->p_ts[0] = 0x47;
p_ts->p_ts[1] = ( b_first ? 0x40 : 0x00 ) | ( ( i_pid >> 8 ) & 0x1f );
p_ts->p_ts[2] = i_pid & 0xff;
p_ts->p_ts[3] = ( b_adaptation_field ? 0x30 : 0x10 ) | *pi_cc;
(*pi_cc)++;
*pi_cc &= 0xf;
if( b_adaptation_field )
{
int i_stuffing = 184 - i_copy - b_first;
int i;
p_ts->p_ts[4] = i_stuffing - 1;
if( i_stuffing > 1 )
{
p_ts->p_ts[5] = 0x00;
for( i = 6; i < 6 + i_stuffing - 2; i++ )
p_ts->p_ts[i] = 0xff;
}
}
if ( b_first )
p_ts->p_ts[188 - i_copy - 1] = 0; /* pointer */
/* copy payload */
memcpy( &p_ts->p_ts[188 - i_copy], p_data, i_copy );
b_first = 0;
i_length -= i_copy;
p_data += i_copy;
}
while ( i_length > 0 );
return p_block;
}
/*****************************************************************************
* SendPAT
*****************************************************************************/
static void SendPAT( void )
{
int i;
for ( i = 0; i < i_nb_outputs; i++ )
{
if ( pp_outputs[i]->i_maddr && pp_outputs[i]->p_pat_section != NULL )
{
block_t *p_block;
p_block = WritePSISection( pp_outputs[i]->p_pat_section, 0,
&pp_outputs[i]->i_pat_cc );
while ( p_block != NULL )
{
block_t *p_next = p_block->p_next;
p_block->i_refcount--;
output_Put( pp_outputs[i], p_block );
p_block = p_next;
}
}
}
}
/*****************************************************************************
* SendPMT
*****************************************************************************/
static void SendPMT( sid_t *p_sid )
{
int i;
for ( i = 0; i < i_nb_outputs; i++ )
{
if ( pp_outputs[i]->i_maddr && pp_outputs[i]->i_sid == p_sid->i_sid
&& pp_outputs[i]->p_pmt_section != NULL )
{
block_t *p_block;
p_block = WritePSISection( pp_outputs[i]->p_pmt_section,
p_sid->i_pmt_pid,
&pp_outputs[i]->i_pmt_cc );
while ( p_block != NULL )
{
block_t *p_next = p_block->p_next;
p_block->i_refcount--;
output_Put( pp_outputs[i], p_block );
p_block = p_next;
}
}
}
}
/*****************************************************************************
* NewPAT
*****************************************************************************/
static void NewPAT( output_t *p_output )
{
dvbpsi_pat_t pat;
dvbpsi_pat_program_t *p_program;
if ( p_output->p_pat_section != NULL )
dvbpsi_DeletePSISections( p_output->p_pat_section );
p_output->p_pat_section = NULL;
p_output->i_pat_version++;
if ( !p_output->i_sid ) return;
if ( p_current_pat == NULL ) return;
for( p_program = p_current_pat->p_first_program; p_program != NULL;
p_program = p_program->p_next )
if ( p_program->i_number == p_output->i_sid )
break;
if ( p_program == NULL )
{
if ( p_output->b_valid )
{
msg_Warn( NULL, "can't find program %d !", p_output->i_sid );
p_output->b_valid = 0;
}
return;
}
dvbpsi_InitPAT( &pat, p_output->i_sid, p_output->i_pat_version, 1 );
dvbpsi_PATAddProgram( &pat, p_output->i_sid, p_program->i_pid );
p_output->p_pat_section = dvbpsi_GenPATSections( &pat, 0 );
dvbpsi_EmptyPAT( &pat );
}
/*****************************************************************************
* NewPMT
*****************************************************************************/
static void NewPMT( output_t *p_output )
{
dvbpsi_pmt_t pmt, *p_current_pmt;
dvbpsi_pmt_es_t *p_current_es;
dvbpsi_descriptor_t *p_dr;
int i;
if ( p_output->p_pmt_section != NULL )
dvbpsi_DeletePSISections( p_output->p_pmt_section );
p_output->p_pmt_section = NULL;
p_output->i_pmt_version++;
if ( !p_output->i_sid ) return;
for ( i = 0; i < i_nb_sids; i++ )
if ( pp_sids[i]->i_sid == p_output->i_sid )
break;
if ( i == i_nb_sids )
return;
if ( pp_sids[i]->p_current_pmt == NULL ) return;
p_current_pmt = pp_sids[i]->p_current_pmt;
dvbpsi_InitPMT( &pmt, p_output->i_sid, p_output->i_pmt_version, 1,
p_current_pmt->i_pcr_pid );
for ( p_dr = p_current_pmt->p_first_descriptor; p_dr != NULL;
p_dr = p_dr->p_next )
dvbpsi_PMTAddDescriptor( &pmt, p_dr->i_tag, p_dr->i_length,
p_dr->p_data );
for( p_current_es = p_current_pmt->p_first_es; p_current_es != NULL;
p_current_es = p_current_es->p_next )
{
if ( (!p_output->i_nb_pids && PIDWouldBeSelected( p_current_es ))
|| IsIn( p_output->pi_pids, p_output->i_nb_pids,
p_current_es->i_pid ) )
{
dvbpsi_pmt_es_t *p_es = dvbpsi_PMTAddES( &pmt, p_current_es->i_type,
p_current_es->i_pid );
for ( p_dr = p_current_es->p_first_descriptor; p_dr != NULL;
p_dr = p_dr->p_next )
dvbpsi_PMTESAddDescriptor( p_es, p_dr->i_tag, p_dr->i_length,
p_dr->p_data );
}
}
p_output->p_pmt_section = dvbpsi_GenPMTSections( &pmt );
dvbpsi_EmptyPMT( &pmt );
}
/*****************************************************************************
* UpdatePAT
*****************************************************************************/
static void UpdatePAT( uint16_t i_sid )
{
int i;
for ( i = 0; i < i_nb_outputs; i++ )
if ( pp_outputs[i]->i_maddr && pp_outputs[i]->i_sid == i_sid )
NewPAT( pp_outputs[i] );
}
/*****************************************************************************
* UpdatePMT
*****************************************************************************/
static void UpdatePMT( uint16_t i_sid )
{
int i;
for ( i = 0; i < i_nb_outputs; i++ )
if ( pp_outputs[i]->i_maddr && pp_outputs[i]->i_sid == i_sid )
NewPMT( pp_outputs[i] );
}
/*****************************************************************************
* SIDIsSelected
*****************************************************************************/
static int SIDIsSelected( uint16_t i_sid )
{
int i;
for ( i = 0; i < i_nb_outputs; i++ )
if ( pp_outputs[i]->i_maddr && pp_outputs[i]->i_sid == i_sid )
return 1;
return 0;
}
/*****************************************************************************
* PIDIsSelected
*****************************************************************************/
int PIDIsSelected( uint16_t i_pid )
{
int i;
for ( i = 0; i < p_pids[i_pid].i_nb_outputs; i++ )
if ( p_pids[i_pid].pp_outputs[i] != NULL )
return 1;
return 0;
}
/*****************************************************************************
* PIDWouldBeSelected
*****************************************************************************/
int PIDWouldBeSelected( dvbpsi_pmt_es_t *p_es )
{
dvbpsi_descriptor_t *p_dr;
switch ( p_es->i_type )
{
case 0x1: /* video MPEG-1 */
case 0x2: /* video */
case 0x3: /* audio MPEG-1 */
case 0x4: /* audio */
case 0xf: /* audio AAC */
case 0x1b: /* video H264 */
return 1;
break;
case 0x6:
for( p_dr = p_es->p_first_descriptor; p_dr != NULL;
p_dr = p_dr->p_next )
{
if( p_dr->i_tag == 0x56 /* ttx */
|| p_dr->i_tag == 0x59 /* dvbsub */
|| p_dr->i_tag == 0x6a /* A/52 */ )
return 1;
}
break;
default:
break;
}
/* FIXME: also parse IOD */
return 0;
}
/*****************************************************************************
* PIDCarriesPES
*****************************************************************************/
int PIDCarriesPES( dvbpsi_pmt_es_t *p_es )
{
switch ( p_es->i_type )
{
case 0x1: /* video MPEG-1 */
case 0x2: /* video */
case 0x3: /* audio MPEG-1 */
case 0x4: /* audio */
case 0x6: /* private PES data */
case 0xf: /* audio AAC */
case 0x1b: /* video H264 */
return 1;
break;
default:
return 0;
break;
}
}
/*****************************************************************************
* PMTNeedsDescrambling
*****************************************************************************/
static int PMTNeedsDescrambling( dvbpsi_pmt_t *p_pmt )
{
dvbpsi_descriptor_t *p_dr;
dvbpsi_pmt_es_t *p_es;
for( p_dr = p_pmt->p_first_descriptor; p_dr != NULL; p_dr = p_dr->p_next )
if( p_dr->i_tag == 0x9 )
return 1;
for( p_es = p_pmt->p_first_es; p_es != NULL; p_es = p_es->p_next )
for( p_dr = p_es->p_first_descriptor; p_dr != NULL;
p_dr = p_dr->p_next )
if( p_dr->i_tag == 0x9 )
return 1;
return 0;
}
/*****************************************************************************
* demux_ResendCAPMTs
*****************************************************************************/
void demux_ResendCAPMTs( void )
{
int i;
for ( i = 0; i < i_nb_sids; i++ )
if ( pp_sids[i]->p_current_pmt != NULL
&& SIDIsSelected( pp_sids[i]->i_sid )
&& PMTNeedsDescrambling( pp_sids[i]->p_current_pmt ) )
en50221_AddPMT( pp_sids[i]->p_current_pmt );
}
/*****************************************************************************
* dvbpsi callbacks
*****************************************************************************/
static void PATCallback( void *_unused, dvbpsi_pat_t *p_pat )
{
dvbpsi_pat_program_t *p_program, *p_old_program;
dvbpsi_pat_t *p_old_pat = p_current_pat;
if( p_current_pat != NULL &&
( !p_pat->b_current_next ||
p_pat->i_version == p_current_pat->i_version ) )
{
dvbpsi_DeletePAT( p_pat );
return;
}
msg_Dbg( NULL, "new PAT ts_id=%d version=%d current_next=%d",
p_pat->i_ts_id, p_pat->i_version, p_pat->b_current_next );
p_current_pat = p_pat;
for( p_program = p_pat->p_first_program; p_program != NULL;
p_program = p_program->p_next )
{
int i_pmt;
msg_Dbg( NULL, " * number=%d pid=%d", p_program->i_number,
p_program->i_pid );
if( p_program->i_number == 0 )
continue;
if ( p_old_pat != NULL )
{
for ( p_old_program = p_old_pat->p_first_program;
p_old_program != NULL;
p_old_program = p_old_program->p_next )
if ( p_old_program->i_number == p_program->i_number )
break;
if ( p_old_program != NULL &&
p_old_program->i_pid == p_program->i_pid )
continue; /* No change */
if ( p_old_program != NULL &&
p_old_program->i_pid != p_program->i_pid )
{
/* Delete old PID */
UnselectPSI( p_old_program->i_number,
p_old_program->i_pid );
for ( i_pmt = 0; i_pmt < i_nb_sids; i_pmt++ )
{
if ( pp_sids[i_pmt]->i_sid == p_program->i_number )
{
dvbpsi_pmt_t *p_old_pmt = pp_sids[i_pmt]->p_current_pmt;
if ( p_old_pmt != NULL )
{
if ( i_ca_handle
&& SIDIsSelected( p_old_program->i_number )
&& PMTNeedsDescrambling( p_old_pmt ) )
en50221_DeletePMT( p_old_pmt );
dvbpsi_DeletePMT( p_old_pmt );
}
pp_sids[i_pmt]->p_current_pmt = NULL;
pp_sids[i_pmt]->i_sid = 0;
pp_sids[i_pmt]->i_pmt_pid = 0;
dvbpsi_DetachPMT( pp_sids[i_pmt]->p_dvbpsi_handle );
break;
}
}
}
}
SelectPSI( p_program->i_number, p_program->i_pid );
for ( i_pmt = 0; i_pmt < i_nb_sids; i_pmt++ )
if ( pp_sids[i_pmt]->i_sid == 0 )
break;
if ( i_pmt == i_nb_sids )
{
sid_t *p_sid = malloc( sizeof(sid_t) );
p_sid->p_current_pmt = NULL;
i_nb_sids++;
pp_sids = realloc( pp_sids, sizeof(sid_t *) * i_nb_sids );
pp_sids[i_pmt] = p_sid;
}
pp_sids[i_pmt]->i_sid = p_program->i_number;
pp_sids[i_pmt]->i_pmt_pid = p_program->i_pid;
pp_sids[i_pmt]->p_dvbpsi_handle = dvbpsi_AttachPMT( p_program->i_number,
PMTCallback, NULL );
UpdatePAT( p_program->i_number );
}
if ( p_old_pat != NULL )
{
for ( p_old_program = p_old_pat->p_first_program;
p_old_program != NULL;
p_old_program = p_old_program->p_next )
{
if( p_old_program->i_number == 0 )
continue;
for( p_program = p_pat->p_first_program; p_program != NULL;
p_program = p_program->p_next )
if ( p_program->i_number == p_old_program->i_number )
break;
if ( p_program == NULL )
{
int i_pmt;
msg_Dbg( NULL, " * removed number=%d pid=%d",
p_old_program->i_number,
p_old_program->i_pid );
UnselectPSI( p_old_program->i_number,
p_old_program->i_pid );
for ( i_pmt = 0; i_pmt < i_nb_sids; i_pmt++ )
{
if ( pp_sids[i_pmt]->i_sid == p_old_program->i_number )
{
dvbpsi_pmt_t *p_old_pmt = pp_sids[i_pmt]->p_current_pmt;
if ( p_old_pmt != NULL )
{
if ( i_ca_handle
&& SIDIsSelected( p_old_program->i_number )
&& PMTNeedsDescrambling( p_old_pmt ) )
en50221_DeletePMT( p_old_pmt );
dvbpsi_DeletePMT( p_old_pmt );
}
pp_sids[i_pmt]->p_current_pmt = NULL;
pp_sids[i_pmt]->i_sid = 0;
pp_sids[i_pmt]->i_pmt_pid = 0;
dvbpsi_DetachPMT( pp_sids[i_pmt]->p_dvbpsi_handle );
break;
}
}
UpdatePAT( p_old_program->i_number );
}
}
dvbpsi_DeletePAT( p_old_pat );
}
}
static void PMTCallback( void *_unused, dvbpsi_pmt_t *p_pmt )
{
dvbpsi_pmt_t *p_current_pmt = NULL;
dvbpsi_pmt_es_t *p_es, *p_current_es;
int b_needs_descrambling = PMTNeedsDescrambling( p_pmt );
int b_needed_descrambling = 0;
int b_is_selected = SIDIsSelected( p_pmt->i_program_number );
int i_pmt;
for ( i_pmt = 0; i_pmt < i_nb_sids; i_pmt++ )
{
if ( pp_sids[i_pmt]->i_sid == p_pmt->i_program_number )
{
p_current_pmt = pp_sids[i_pmt]->p_current_pmt;
if ( p_current_pmt != NULL )
b_needed_descrambling = PMTNeedsDescrambling( p_current_pmt );
break;
}
}
if ( i_pmt == i_nb_sids )
{
msg_Err( NULL, "unknown service %d", p_pmt->i_program_number );
dvbpsi_DeletePMT( p_pmt );
return;
}
if ( p_current_pmt != NULL &&
( !p_pmt->b_current_next ||
p_pmt->i_version == p_current_pmt->i_version ) )
{
dvbpsi_DeletePMT( p_pmt );
return;
}
msg_Dbg( NULL, "new PMT program number=%d version=%d pid_pcr=%d",
p_pmt->i_program_number, p_pmt->i_version, p_pmt->i_pcr_pid );
if ( p_pmt->i_pcr_pid != PADDING_PID
&& p_pmt->i_pcr_pid != pp_sids[i_pmt]->i_pmt_pid )
SelectPID( p_pmt->i_program_number, p_pmt->i_pcr_pid );
for( p_es = p_pmt->p_first_es; p_es != NULL; p_es = p_es->p_next )
{
msg_Dbg( NULL, " * es pid=%d type=%d",
p_es->i_pid, p_es->i_type );
if ( PIDWouldBeSelected( p_es ) )
SelectPID( p_pmt->i_program_number, p_es->i_pid );
p_pids[p_es->i_pid].b_pes = PIDCarriesPES( p_es );
}
if ( p_current_pmt != NULL )
{
if ( p_current_pmt->i_pcr_pid != p_pmt->i_pcr_pid
&& p_current_pmt->i_pcr_pid != PADDING_PID
&& p_current_pmt->i_pcr_pid != pp_sids[i_pmt]->i_pmt_pid )
UnselectPID( p_pmt->i_program_number, p_current_pmt->i_pcr_pid );
for( p_current_es = p_current_pmt->p_first_es; p_current_es != NULL;
p_current_es = p_current_es->p_next )
{
if ( PIDWouldBeSelected( p_current_es ) )
{
for( p_es = p_pmt->p_first_es; p_es != NULL;
p_es = p_es->p_next )
if ( p_es->i_pid == p_current_es->i_pid )
break;
if ( p_es == NULL )
{
msg_Dbg( NULL, " * removed es pid=%d type=%d",
p_current_es->i_pid, p_current_es->i_type );
UnselectPID( p_pmt->i_program_number, p_current_es->i_pid );
}
}
}
dvbpsi_DeletePMT( p_current_pmt );
}
pp_sids[i_pmt]->p_current_pmt = p_pmt;
if ( i_ca_handle && b_is_selected )
{
if ( b_needs_descrambling && !b_needed_descrambling )
en50221_AddPMT( p_pmt );
else if ( b_needs_descrambling && b_needed_descrambling )
en50221_UpdatePMT( p_pmt );
else if ( !b_needs_descrambling && b_needed_descrambling )
en50221_DeletePMT( p_pmt );
}
UpdatePMT( p_pmt->i_program_number );
}
/*****************************************************************************
* dvb.c
*****************************************************************************
* Copyright (C) 2008-2009 the VideoLAN team
* $Id: demux.c 7 2006-01-25 16:26:44Z cmassiot $
*
* Authors: Christophe Massiot <massiot@via.ecp.fr>
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
/* DVB Card Drivers */
#include <linux/dvb/version.h>
#include <linux/dvb/dmx.h>
#include <linux/dvb/frontend.h>
#include <linux/dvb/ca.h>
#include "dvblast.h"
#include "en50221.h"
#include "comm.h"
/*****************************************************************************
* Local declarations
*****************************************************************************/
#define FRONTEND_LOCK_TIMEOUT 10000000 /* 10 s */
#define COUNTER_WRAP 200 /* we make 200 read calls per second */
#define MAX_READ_ONCE 50
static int i_frontend, i_dvr;
static fe_status_t i_last_status;
static mtime_t i_frontend_timeout;
static unsigned int i_read_once = 1;
static unsigned int i_read_counter = 0;
static mtime_t i_last_counter = 0;
static mtime_t i_ca_next_event = 0;
mtime_t i_ca_timeout = 0;
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static block_t *DVRRead( void );
static void FrontendPoll( void );
static void FrontendSet( void );
#if DVB_API_VERSION < 5
static void FrontendSetQPSK( struct dvb_frontend_parameters *p_fep );
static void FrontendSetQAM( struct dvb_frontend_parameters *p_fep );
static void FrontendSetOFDM( struct dvb_frontend_parameters *p_fep );
static void FrontendSetATSC( struct dvb_frontend_parameters *p_fep );
#endif
/*****************************************************************************
* dvb_Open
*****************************************************************************/
void dvb_Open( void )
{
char psz_tmp[128];
sprintf( psz_tmp, "/dev/dvb/adapter%d/frontend0", i_adapter );
if( (i_frontend = open(psz_tmp, O_RDWR | O_NONBLOCK)) < 0 )
{
msg_Err( NULL, "opening device %s failed (%s)", psz_tmp,
strerror(errno) );
exit(1);
}
FrontendSet();
sprintf( psz_tmp, "/dev/dvb/adapter%d/dvr0", i_adapter );
if( (i_dvr = open(psz_tmp, O_RDONLY)) < 0 )
{
msg_Err( NULL, "opening device %s failed (%s)", psz_tmp,
strerror(errno) );
exit(1);
}
if( fcntl( i_dvr, F_SETFL, O_NONBLOCK ) == -1 )
{
msg_Warn( NULL, "couldn't set %s non-blocking mode (%s)", psz_tmp,
strerror(errno) );
}
en50221_Init();
i_ca_next_event = mdate() + i_ca_timeout;
}
/*****************************************************************************
* dvb_Read
*****************************************************************************/
block_t *dvb_Read( void )
{
struct pollfd ufds[3];
int i_ret, i_nb_fd = 2;
mtime_t i_current_date;
memset( ufds, 0, sizeof(ufds) );
ufds[0].fd = i_dvr;
ufds[0].events = POLLIN;
ufds[1].fd = i_frontend;
ufds[1].events = POLLERR | POLLPRI;
if ( i_comm_fd != -1 )
{
ufds[2].fd = i_comm_fd;
ufds[2].events = POLLIN;
i_nb_fd = 3;
}
i_ret = poll( ufds, i_nb_fd, 100 );
if ( i_ret < 0 )
{
if( errno != EINTR )
msg_Err( NULL, "poll error: %s", strerror(errno) );
return NULL;
}
i_current_date = mdate();
if ( i_ca_handle && i_ca_type == CA_CI_LINK
&& i_current_date > i_ca_next_event )
{
en50221_Poll();
i_ca_next_event = i_current_date + i_ca_timeout;
}
if ( ufds[1].revents )
FrontendPoll();
if ( i_frontend_timeout && i_current_date > i_frontend_timeout )
{
msg_Warn( NULL, "no lock, tuning again" );
FrontendSet();
}
if ( i_comm_fd != -1 && ufds[2].revents )
comm_Read();
if ( ufds[0].revents )
return DVRRead();
return NULL;
}
/*****************************************************************************
* DVRRead
*****************************************************************************/
static block_t *DVRRead( void )
{
int i, i_len;
block_t *p_ts, **pp_current = &p_ts;
struct iovec p_iov[i_read_once];
for ( i = 0; i < i_read_once; i++ )
{
*pp_current = block_New();
p_iov[i].iov_base = (*pp_current)->p_ts;
p_iov[i].iov_len = TS_SIZE;
pp_current = &(*pp_current)->p_next;
}
if ( (i_len = readv(i_dvr, p_iov, i_read_once)) < 0 )
{
msg_Err( NULL, "couldn't read from DVR device (%s)",
strerror(errno) );
i_len = 0;
}
i_len /= TS_SIZE;
//msg_Err( NULL, "Meuuh %d %d", i_len, i_read_once );
pp_current = &p_ts;
while ( i_len && *pp_current )
{
pp_current = &(*pp_current)->p_next;
i_len--;
}
block_DeleteChain( *pp_current );
*pp_current = NULL;
i_read_counter++;
if ( i_read_counter >= COUNTER_WRAP )
{
mtime_t i_current_date = mdate();
if ( i_last_counter )
{
/* Adjust the buffer size to keep the read() calls frequency
* at a certain limit */
i_read_once = (mtime_t)i_read_once * 1000000LL
/ (i_current_date - i_last_counter);
if ( i_read_once < 1 )
i_read_once = 1;
if ( i_read_once > MAX_READ_ONCE )
i_read_once = MAX_READ_ONCE;
}
i_read_counter = 0;
i_last_counter = i_current_date;
}
return p_ts;
}
/*
* Demux
*/
/*****************************************************************************
* dvb_SetFilter : controls the demux to add a filter
*****************************************************************************/
int dvb_SetFilter( uint16_t i_pid )
{
struct dmx_pes_filter_params s_filter_params;
char psz_tmp[128];
int i_fd;
sprintf( psz_tmp, "/dev/dvb/adapter%d/demux0", i_adapter );
if( (i_fd = open(psz_tmp, O_RDWR)) < 0 )
{
msg_Err( NULL, "DMXSetFilter: opening device failed (%s)",
strerror(errno) );
return -1;
}
s_filter_params.pid = i_pid;
s_filter_params.input = DMX_IN_FRONTEND;
s_filter_params.output = DMX_OUT_TS_TAP;
s_filter_params.flags = DMX_IMMEDIATE_START;
s_filter_params.pes_type = DMX_PES_OTHER;
if ( ioctl( i_fd, DMX_SET_PES_FILTER, &s_filter_params ) < 0 )
{
msg_Err( NULL, "failed setting filter on %d (%s)", i_pid,
strerror(errno) );
close( i_fd );
return -1;
}
msg_Dbg( NULL, "setting filter on PID %d", i_pid );
return i_fd;
}
/*****************************************************************************
* dvb_UnsetFilter : removes a filter
*****************************************************************************/
void dvb_UnsetFilter( int i_fd, uint16_t i_pid )
{
if ( ioctl( i_fd, DMX_STOP ) < 0 )
msg_Err( NULL, "DMX_STOP failed (%s)", strerror(errno) );
else
msg_Dbg( NULL, "unsetting filter on PID %d", i_pid );
close( i_fd );
}
/*
* Frontend
*/
/*****************************************************************************
* FrontendPoll : Poll for frontend events
*****************************************************************************/
static void FrontendPoll( void )
{
struct dvb_frontend_event event;
fe_status_t i_status, i_diff;
for( ;; )
{
int i_ret = ioctl( i_frontend, FE_GET_EVENT, &event );
if( i_ret < 0 )
{
if( errno == EWOULDBLOCK )
return; /* no more events */
msg_Err( NULL, "reading frontend event failed (%d) %s",
i_ret, strerror(errno) );
return;
}
i_status = event.status;
i_diff = i_status ^ i_last_status;
i_last_status = i_status;
{
#define IF_UP( x ) \
} \
if ( i_diff & (x) ) \
{ \
if ( i_status & (x) )
IF_UP( FE_HAS_SIGNAL )
msg_Dbg( NULL, "frontend has acquired signal" );
else
msg_Dbg( NULL, "frontend has lost signal" );
IF_UP( FE_HAS_CARRIER )
msg_Dbg( NULL, "frontend has acquired carrier" );
else
msg_Dbg( NULL, "frontend has lost carrier" );
IF_UP( FE_HAS_VITERBI )
msg_Dbg( NULL, "frontend has acquired stable FEC" );
else
msg_Dbg( NULL, "frontend has lost FEC" );
IF_UP( FE_HAS_SYNC )
msg_Dbg( NULL, "frontend has acquired sync" );
else
msg_Dbg( NULL, "frontend has lost sync" );
IF_UP( FE_HAS_LOCK )
{
int32_t i_value = 0;
msg_Dbg( NULL, "frontend has acquired lock" );
i_frontend_timeout = 0;
/* Read some statistics */
if( ioctl( i_frontend, FE_READ_BER, &i_value ) >= 0 )
msg_Dbg( NULL, "- Bit error rate: %d", i_value );
if( ioctl( i_frontend, FE_READ_SIGNAL_STRENGTH, &i_value ) >= 0 )
msg_Dbg( NULL, "- Signal strength: %d", i_value );
if( ioctl( i_frontend, FE_READ_SNR, &i_value ) >= 0 )
msg_Dbg( NULL, "- SNR: %d", i_value );
}
else
{
msg_Dbg( NULL, "frontend has lost lock" );
i_frontend_timeout = mdate() + FRONTEND_LOCK_TIMEOUT;
}
IF_UP( FE_REINIT )
{
/* The frontend was reinited. */
msg_Warn( NULL, "reiniting frontend");
FrontendSet();
}
}
#undef IF_UP
}
}
#if DVB_API_VERSION >= 5
/*****************************************************************************
* GetModulation : helper function for both APIs
*****************************************************************************/
static fe_modulation_t GetModulation(void)
{
#define GET_MODULATION( mod ) \
if ( !strcasecmp( psz_modulation, #mod ) ) \
return mod;
GET_MODULATION(QPSK);
GET_MODULATION(QAM_16);
GET_MODULATION(QAM_32);
GET_MODULATION(QAM_64);
GET_MODULATION(QAM_128);
GET_MODULATION(QAM_256);
GET_MODULATION(QAM_AUTO);
GET_MODULATION(VSB_8);
GET_MODULATION(VSB_16);
#if DVB_API_VERSION >= 5
GET_MODULATION(PSK_8);
GET_MODULATION(APSK_16);
GET_MODULATION(APSK_32);
GET_MODULATION(DQPSK);
#endif
#undef GET_MODULATION
msg_Err( NULL, "invalid modulation %s", psz_modulation );
exit(1);
}
/*****************************************************************************
* FrontendSet
*****************************************************************************/
/* S2API */
static struct dtv_property dvbs_cmdargs[] = {
{ .cmd = DTV_FREQUENCY, .u.data = 0 },
{ .cmd = DTV_MODULATION, .u.data = QPSK },
{ .cmd = DTV_INVERSION, .u.data = INVERSION_AUTO },
{ .cmd = DTV_SYMBOL_RATE, .u.data = 27500000 },
{ .cmd = DTV_VOLTAGE, .u.data = SEC_VOLTAGE_OFF },
{ .cmd = DTV_TONE, .u.data = SEC_TONE_OFF },
{ .cmd = DTV_INNER_FEC, .u.data = FEC_AUTO },
{ .cmd = DTV_TUNE },
};
static struct dtv_properties dvbs_cmdseq = {
.num = 8,
.props = dvbs_cmdargs
};
static struct dtv_property dvbs2_cmdargs[] = {
{ .cmd = DTV_FREQUENCY, .u.data = 0 },
{ .cmd = DTV_MODULATION, .u.data = PSK_8 },
{ .cmd = DTV_INVERSION, .u.data = INVERSION_AUTO },
{ .cmd = DTV_SYMBOL_RATE, .u.data = 27500000 },
{ .cmd = DTV_VOLTAGE, .u.data = SEC_VOLTAGE_OFF },
{ .cmd = DTV_TONE, .u.data = SEC_TONE_OFF },
{ .cmd = DTV_INNER_FEC, .u.data = FEC_AUTO },
{ .cmd = DTV_DELIVERY_SYSTEM, .u.data = SYS_DVBS2 },
{ .cmd = DTV_PILOT, .u.data = PILOT_AUTO },
{ .cmd = DTV_ROLLOFF, .u.data = ROLLOFF_AUTO },
{ .cmd = DTV_TUNE },
};
static struct dtv_properties dvbs2_cmdseq = {
.num = 11,
.props = dvbs2_cmdargs
};
static struct dtv_property dvbc_cmdargs[] = {
{ .cmd = DTV_FREQUENCY, .u.data = 0 },
{ .cmd = DTV_MODULATION, .u.data = QAM_AUTO },
{ .cmd = DTV_INVERSION, .u.data = INVERSION_AUTO },
{ .cmd = DTV_SYMBOL_RATE, .u.data = 27500000 },
{ .cmd = DTV_TUNE },
};
static struct dtv_properties dvbc_cmdseq = {
.num = 5,
.props = dvbc_cmdargs
};
static struct dtv_property dvbt_cmdargs[] = {
{ .cmd = DTV_FREQUENCY, .u.data = 0 },
{ .cmd = DTV_MODULATION, .u.data = QAM_AUTO },
{ .cmd = DTV_INVERSION, .u.data = INVERSION_AUTO },
{ .cmd = DTV_BANDWIDTH_HZ, .u.data = 8000000 },
{ .cmd = DTV_TUNE },
};
static struct dtv_properties dvbt_cmdseq = {
.num = 5,
.props = dvbt_cmdargs
};
#define FREQUENCY 0
#define MODULATION 1
#define INVERSION 2
#define SYMBOL_RATE 3
#define BANDWIDTH 3
#define VOLTAGE 4
#define TONE 5
#define INNER_FEC 6
static void FrontendSet( void )
{
struct dvb_frontend_info info;
struct dtv_properties *p;
if ( ioctl( i_frontend, FE_GET_INFO, &info ) < 0 )
{
msg_Err( NULL, "FE_GET_INFO failed (%s)", strerror(errno) );
exit(1);
}
switch ( info.type )
{
case FE_OFDM:
p = &dvbt_cmdseq;
p->props[FREQUENCY].u.data = i_frequency;
if ( psz_modulation != NULL )
p->props[MODULATION].u.data = GetModulation();
p->props[BANDWIDTH].u.data = i_bandwidth * 1000000;
msg_Dbg( NULL, "tuning OFDM frontend to f=%d bandwidth=%d modulation=%s",
i_frequency, i_bandwidth,
psz_modulation == NULL ? "qam_auto" : psz_modulation );
break;
case FE_QAM:
p = &dvbc_cmdseq;
p->props[FREQUENCY].u.data = i_frequency;
if ( psz_modulation != NULL )
p->props[MODULATION].u.data = GetModulation();
p->props[SYMBOL_RATE].u.data = i_srate;
msg_Dbg( NULL, "tuning QAM frontend to f=%d srate=%d modulation=%s",
i_frequency, i_srate,
psz_modulation == NULL ? "qam_auto" : psz_modulation );
break;
case FE_QPSK:
if ( psz_modulation != NULL )
{
p = &dvbs2_cmdseq;
p->props[MODULATION].u.data = GetModulation();
}
else
p = &dvbs_cmdseq;
p->props[SYMBOL_RATE].u.data = i_srate;
switch ( i_voltage )
{
case 0: p->props[VOLTAGE].u.data = SEC_VOLTAGE_OFF; break;
default:
case 13: p->props[VOLTAGE].u.data = SEC_VOLTAGE_13; break;
case 18: p->props[VOLTAGE].u.data = SEC_VOLTAGE_18; break;
}
p->props[TONE].u.data = b_tone ? SEC_TONE_ON : SEC_TONE_OFF;
/* Automatic mode. */
if ( i_frequency >= 950000 && i_frequency <= 2150000 )
{
msg_Dbg( NULL, "frequency %d is in IF-band", i_frequency );
p->props[FREQUENCY].u.data = i_frequency;
}
else if ( i_frequency >= 2500000 && i_frequency <= 2700000 )
{
msg_Dbg( NULL, "frequency %d is in S-band", i_frequency );
p->props[FREQUENCY].u.data = i_frequency - 3650000;
}
else if ( i_frequency >= 3400000 && i_frequency <= 4200000 )
{
msg_Dbg( NULL, "frequency %d is in C-band (lower)",
i_frequency );
p->props[FREQUENCY].u.data = i_frequency - 5150000;
}
else if ( i_frequency >= 4500000 && i_frequency <= 4800000 )
{
msg_Dbg( NULL, "frequency %d is in C-band (higher)",
i_frequency );
p->props[FREQUENCY].u.data = i_frequency - 5950000;
}
else if ( i_frequency >= 10700000 && i_frequency < 11700000 )
{
msg_Dbg( NULL, "frequency %d is in Ku-band (lower)",
i_frequency );
p->props[FREQUENCY].u.data = i_frequency - 9750000;
}
else if ( i_frequency >= 11700000 && i_frequency <= 13250000 )
{
msg_Dbg( NULL, "frequency %d is in Ku-band (higher)",
i_frequency );
p->props[FREQUENCY].u.data = i_frequency - 10600000;
p->props[TONE].u.data = SEC_TONE_ON;
}
else
{
msg_Err( NULL, "frequency %d is out of any known band",
i_frequency );
exit(1);
}
msg_Dbg( NULL, "tuning QPSK frontend to f=%d srate=%d v=%d p=%d modulation=%s",
i_frequency, i_srate, i_voltage, b_tone,
psz_modulation == NULL ? "legacy" : psz_modulation );
break;
default:
msg_Err( NULL, "unknown frontend type %d", info.type );
exit(1);
}
/* Empty the event queue */
for ( ; ; )
{
struct dvb_frontend_event event;
if ( ioctl( i_frontend, FE_GET_EVENT, &event ) < 0
&& errno == EWOULDBLOCK )
break;
}
/* Now send it all to the frontend device */
if ( ioctl( i_frontend, FE_SET_PROPERTY, p ) < 0 )
{
msg_Err( NULL, "setting frontend failed (%s)", strerror(errno) );
exit(1);
}
i_last_status = 0;
i_frontend_timeout = mdate() + FRONTEND_LOCK_TIMEOUT;
}
#else /* !S2API */
static void FrontendSet( void )
{
struct dvb_frontend_info info;
struct dvb_frontend_parameters fep;
if ( ioctl( i_frontend, FE_GET_INFO, &info ) < 0 )
{
msg_Err( NULL, "FE_GET_INFO failed (%s)", strerror(errno) );
exit(1);
}
switch ( info.type )
{
case FE_OFDM:
FrontendSetOFDM( &fep );
break;
case FE_QAM:
FrontendSetQAM( &fep );
break;
case FE_QPSK:
FrontendSetQPSK( &fep );
break;
case FE_ATSC:
FrontendSetATSC( &fep );
break;
default:
msg_Err( NULL, "unknown frontend type %d", info.type );
exit(1);
}
/* Empty the event queue */
for ( ; ; )
{
struct dvb_frontend_event event;
if ( ioctl( i_frontend, FE_GET_EVENT, &event ) < 0
&& errno == EWOULDBLOCK )
break;
}
/* Now send it all to the frontend device */
if ( ioctl( i_frontend, FE_SET_FRONTEND, &fep ) < 0 )
{
msg_Err( NULL, "setting frontend failed (%s)", strerror(errno) );
exit(1);
}
i_last_status = 0;
i_frontend_timeout = mdate() + FRONTEND_LOCK_TIMEOUT;
}
/*****************************************************************************
* FrontendSetQPSK
*****************************************************************************/
static void FrontendSetQPSK( struct dvb_frontend_parameters *p_fep )
{
fe_sec_voltage_t fe_voltage;
fe_sec_tone_mode_t fe_tone;
p_fep->inversion = INVERSION_AUTO;
p_fep->u.qpsk.symbol_rate = i_srate;
p_fep->u.qpsk.fec_inner = FEC_AUTO;
switch ( i_voltage )
{
case 0: fe_voltage = SEC_VOLTAGE_OFF; break;
default:
case 13: fe_voltage = SEC_VOLTAGE_13; break;
case 18: fe_voltage = SEC_VOLTAGE_18; break;
}
fe_tone = b_tone ? SEC_TONE_ON : SEC_TONE_OFF;
/* Automatic mode. */
if ( i_frequency >= 950000 && i_frequency <= 2150000 )
{
msg_Dbg( NULL, "frequency %d is in IF-band", i_frequency );
p_fep->frequency = i_frequency;
}
else if ( i_frequency >= 2500000 && i_frequency <= 2700000 )
{
msg_Dbg( NULL, "frequency %d is in S-band", i_frequency );
p_fep->frequency = i_frequency - 3650000;
}
else if ( i_frequency >= 3400000 && i_frequency <= 4200000 )
{
msg_Dbg( NULL, "frequency %d is in C-band (lower)",
i_frequency );
p_fep->frequency = i_frequency - 5150000;
}
else if ( i_frequency >= 4500000 && i_frequency <= 4800000 )
{
msg_Dbg( NULL, "frequency %d is in C-band (higher)",
i_frequency );
p_fep->frequency = i_frequency - 5950000;
}
else if ( i_frequency >= 10700000 && i_frequency < 11700000 )
{
msg_Dbg( NULL, "frequency %d is in Ku-band (lower)",
i_frequency );
p_fep->frequency = i_frequency - 9750000;
}
else if ( i_frequency >= 11700000 && i_frequency <= 13250000 )
{
msg_Dbg( NULL, "frequency %d is in Ku-band (higher)",
i_frequency );
p_fep->frequency = i_frequency - 10600000;
fe_tone = SEC_TONE_ON;
}
else
{
msg_Err( NULL, "frequency %d is out of any known band",
i_frequency );
exit(1);
}
/* Switch off continuous tone. */
if ( ioctl( i_frontend, FE_SET_TONE, SEC_TONE_OFF ) < 0 )
{
msg_Err( NULL, "FE_SET_TONE failed (%s)", strerror(errno) );
exit(1);
}
/* Configure LNB voltage. */
if ( ioctl( i_frontend, FE_SET_VOLTAGE, fe_voltage ) < 0 )
{
msg_Err( NULL, "FE_SET_VOLTAGE failed (%s)", strerror(errno) );
exit(1);
}
/* Wait for at least 15 ms. */
msleep(15000);
if ( ioctl( i_frontend, FE_SET_TONE, fe_tone ) < 0 )
{
msg_Err( NULL, "FE_SET_TONE failed (%s)", strerror(errno) );
exit(1);
}
/* TODO: Diseqc */
msleep(50000);
msg_Dbg( NULL, "tuning QPSK frontend to f=%d, srate=%d, v=%d, p=%d",
i_frequency, i_srate, i_voltage, b_tone );
}
/*****************************************************************************
* FrontendSetQAM
*****************************************************************************/
static void FrontendSetQAM( struct dvb_frontend_parameters *p_fep )
{
p_fep->frequency = i_frequency;
p_fep->inversion = INVERSION_AUTO;
p_fep->u.qam.symbol_rate = i_srate;
p_fep->u.qam.fec_inner = FEC_AUTO;
p_fep->u.qam.modulation = QAM_AUTO;
msg_Dbg( NULL, "tuning QAM frontend to f=%d, srate=%d", i_frequency,
i_srate );
}
/*****************************************************************************
* FrontendSetOFDM
*****************************************************************************/
static void FrontendSetOFDM( struct dvb_frontend_parameters *p_fep )
{
p_fep->frequency = i_frequency;
p_fep->inversion = INVERSION_AUTO;
switch ( i_bandwidth )
{
case 6: p_fep->u.ofdm.bandwidth = BANDWIDTH_6_MHZ; break;
case 7: p_fep->u.ofdm.bandwidth = BANDWIDTH_7_MHZ; break;
default:
case 8: p_fep->u.ofdm.bandwidth = BANDWIDTH_8_MHZ; break;
}
p_fep->u.ofdm.code_rate_HP = FEC_AUTO;
p_fep->u.ofdm.code_rate_LP = FEC_AUTO;
p_fep->u.ofdm.constellation = QAM_AUTO;
p_fep->u.ofdm.transmission_mode = TRANSMISSION_MODE_AUTO;
p_fep->u.ofdm.guard_interval = GUARD_INTERVAL_AUTO;
p_fep->u.ofdm.hierarchy_information = HIERARCHY_AUTO;
msg_Dbg( NULL, "tuning OFDM frontend to f=%d, bandwidth=%d", i_frequency,
i_bandwidth );
}
/*****************************************************************************
* FrontendSetATSC
*****************************************************************************/
static void FrontendSetATSC( struct dvb_frontend_parameters *p_fep )
{
p_fep->frequency = i_frequency;
p_fep->u.vsb.modulation = QAM_AUTO;
msg_Dbg( NULL, "tuning ATSC frontend to f=%d", i_frequency );
}
#endif /* S2API */
/*****************************************************************************
* dvb_FrontendStatus
*****************************************************************************/
uint8_t dvb_FrontendStatus( uint8_t *p_answer, ssize_t *pi_size )
{
struct ret_frontend_status *p_ret = (struct ret_frontend_status *)p_answer;
if ( ioctl( i_frontend, FE_GET_INFO, &p_ret->info ) < 0 )
{
msg_Err( NULL, "ioctl FE_GET_INFO failed (%s)", strerror(errno) );
return RET_ERR;
}
if ( ioctl( i_frontend, FE_READ_STATUS, &p_ret->i_status ) < 0 )
{
msg_Err( NULL, "ioctl FE_READ_STATUS failed (%s)", strerror(errno) );
return RET_ERR;
}
if ( p_ret->i_status & FE_HAS_LOCK )
{
if ( ioctl( i_frontend, FE_READ_BER, &p_ret->i_ber ) < 0 )
msg_Err( NULL, "ioctl FE_READ_BER failed (%s)", strerror(errno) );
if ( ioctl( i_frontend, FE_READ_SIGNAL_STRENGTH, &p_ret->i_strength )
< 0 )
msg_Err( NULL, "ioctl FE_READ_SIGNAL_STRENGTH failed (%s)",
strerror(errno) );
if ( ioctl( i_frontend, FE_READ_SNR, &p_ret->i_snr ) < 0 )
msg_Err( NULL, "ioctl FE_READ_SNR failed (%s)", strerror(errno) );
}
*pi_size = sizeof(struct ret_frontend_status);
return RET_FRONTEND_STATUS;
}
/*****************************************************************************
* dvblast.c
*****************************************************************************
* Copyright (C) 2004, 2008-2009 the VideoLAN team
* $Id: dvblast.c 11 2007-08-09 18:54:37Z cmassiot $
*
* Authors: Christophe Massiot <massiot@via.ecp.fr>
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include "dvblast.h"
/*****************************************************************************
* Local declarations
*****************************************************************************/
output_t **pp_outputs = NULL;
int i_nb_outputs = 0;
output_t output_dup = { 0 };
static char *psz_conf_file = NULL;
char *psz_srv_socket = NULL;
int i_ttl = 64;
in_addr_t i_ssrc = 0;
static int i_priority = -1;
int i_adapter = 0;
int i_frequency = 0;
int i_srate = 27500000;
int i_voltage = 13;
int b_tone = 0;
int i_bandwidth = 8;
char *psz_modulation = NULL;
int b_budget_mode = 0;
volatile int b_hup_received = 0;
/*****************************************************************************
* Configuration files
*****************************************************************************/
static void ReadConfiguration( char *psz_file )
{
FILE *p_file;
char psz_line[2048];
int i;
if ( (p_file = fopen( psz_file, "r" )) == NULL )
{
msg_Err( NULL, "can't fopen config file %s", psz_file );
return;
}
while ( fgets( psz_line, sizeof(psz_line), p_file ) != NULL )
{
output_t *p_output = NULL;
char *psz_parser, *psz_token, *psz_token2;
struct in_addr maddr;
uint16_t i_port = DEFAULT_PORT;
uint16_t i_sid = 0;
uint16_t *pi_pids = NULL;
int i_nb_pids = 0;
int b_watch;
psz_token = strtok_r( psz_line, "\t\n ", &psz_parser );
if ( psz_token == NULL )
continue;
if ( (psz_token2 = strrchr( psz_token, ':' )) != NULL )
{
*psz_token2 = '\0';
i_port = atoi( psz_token2 + 1 );
}
if ( !inet_aton( psz_token, &maddr ) )
continue;
psz_token = strtok_r( NULL, "\t\n ", &psz_parser );
if ( psz_token == NULL )
continue;
b_watch = atoi( psz_token );
psz_token = strtok_r( NULL, "\t\n ", &psz_parser );
if ( psz_token == NULL )
continue;
i_sid = strtol(psz_token, NULL, 0);
psz_token = strtok_r( NULL, "\t\n ", &psz_parser );
if ( psz_token != NULL )
{
psz_parser = NULL;
for ( ; ; )
{
psz_token = strtok_r( psz_token, ",", &psz_parser );
if ( psz_token == NULL )
break;
pi_pids = realloc( pi_pids,
(i_nb_pids + 1) * sizeof(uint16_t) );
pi_pids[i_nb_pids++] = strtol(psz_token, NULL, 0);
psz_token = NULL;
}
}
msg_Dbg( NULL, "conf: %s:%u w=%d sid=%d pids[%d]=%d,%d,%d,%d,%d...",
inet_ntoa( maddr ), i_port, b_watch, i_sid, i_nb_pids,
i_nb_pids < 1 ? -1 : pi_pids[0],
i_nb_pids < 2 ? -1 : pi_pids[1],
i_nb_pids < 3 ? -1 : pi_pids[2],
i_nb_pids < 4 ? -1 : pi_pids[3],
i_nb_pids < 5 ? -1 : pi_pids[4] );
for ( i = 0; i < i_nb_outputs; i++ )
{
if ( pp_outputs[i]->i_maddr == maddr.s_addr
&& pp_outputs[i]->i_port == i_port )
{
p_output = pp_outputs[i];
break;
}
}
if ( i == i_nb_outputs )
p_output = output_Create( maddr.s_addr, i_port );
if ( p_output != NULL )
{
demux_Change( p_output, i_sid, pi_pids, i_nb_pids );
p_output->b_watch = (b_watch == 1);
p_output->b_still_present = 1;
}
free( pi_pids );
}
fclose( p_file );
for ( i = 0; i < i_nb_outputs; i++ )
{
if ( pp_outputs[i]->i_maddr && !pp_outputs[i]->b_still_present )
{
struct in_addr s;
s.s_addr = pp_outputs[i]->i_maddr;
msg_Dbg( NULL, "closing %s:%u", inet_ntoa( s ),
pp_outputs[i]->i_port );
demux_Change( pp_outputs[i], 0, NULL, 0 );
output_Close( pp_outputs[i] );
}
pp_outputs[i]->b_still_present = 0;
}
}
/*****************************************************************************
* Signal Handler
*****************************************************************************/
static void SigHandler( int i_signal )
{
b_hup_received = 1;
}
/*****************************************************************************
* Entry point
*****************************************************************************/
void usage()
{
msg_Err( NULL, "Usage: dvblast -c <config file> [-r <remote socket>] [-t <ttl>] [-o <SSRC IP>] [-i <RT priority>] [-a <adapter>] -f <frequency> [-s <symbol rate>] [-v <0|13|18>] [-p] [-b <bandwidth>] [-m <modulation] [-u] [-d <dest IP:port>]" );
msg_Err( NULL, "-v: voltage to apply to the LNB (QPSK)" );
msg_Err( NULL, "-p: force 22kHz pulses for high-band selection (DVB-S)" );
msg_Err( NULL, "-m: DVB-C qpsk|qam_16|qam_32|qam_64|qam_128|qam_256 (default qam_auto)" );
msg_Err( NULL, " DVB-T qam_16|qam_32|qam_64|qam_128|qam_256 (default qam_auto)" );
msg_Err( NULL, " DVB-S2 qpsk|psk_8 (default legacy DVB-S)" );
msg_Err( NULL, "-u: turn on budget mode (no hardware PID filtering)" );
msg_Err( NULL, "-d: duplicate all received packets to a given port" );
exit(1);
}
int main( int i_argc, char **pp_argv )
{
struct sched_param param;
int i_error;
if ( i_argc == 1 )
usage();
msg_Err( NULL, "restarting" );
for ( ; ; )
{
char c;
if ( (c = getopt(i_argc, pp_argv, "c:r:t:o:i:a:f:s:v:pb:m:ud:h")) == -1 )
break;
switch ( c )
{
case 'c':
psz_conf_file = optarg;
break;
case 'r':
psz_srv_socket = optarg;
break;
case 't':
i_ttl = strtol( optarg, NULL, 0 );
break;
case 'o':
{
struct in_addr maddr;
if ( !inet_aton( optarg, &maddr ) )
usage();
i_ssrc = maddr.s_addr;
break;
}
case 'i':
i_priority = strtol( optarg, NULL, 0 );
break;
case 'a':
i_adapter = strtol( optarg, NULL, 0 );
break;
case 'f':
i_frequency = strtol( optarg, NULL, 0 );
break;
case 's':
i_srate = strtol( optarg, NULL, 0 );
break;
case 'v':
i_voltage = strtol( optarg, NULL, 0 );
break;
case 'p':
b_tone = 1;
break;
case 'b':
i_bandwidth = strtol( optarg, NULL, 0 );
break;
case 'm':
psz_modulation = optarg;
break;
case 'u':
b_budget_mode = 1;
break;
case 'd':
{
char *psz_token;
uint16_t i_port = DEFAULT_PORT;
struct in_addr maddr;
if ( (psz_token = strrchr( optarg, ':' )) != NULL )
{
*psz_token = '\0';
i_port = atoi( psz_token + 1 );
}
if ( !inet_aton( optarg, &maddr ) )
usage();
output_Init( &output_dup, maddr.s_addr, i_port );
break;
}
case 'h':
default:
usage();
}
}
signal( SIGHUP, SigHandler );
srand( time(NULL) * getpid() );
demux_Open();
if ( i_priority > 0 )
{
memset( &param, 0, sizeof(struct sched_param) );
param.sched_priority = i_priority;
if ( (i_error = pthread_setschedparam( pthread_self(), SCHED_RR,
&param )) )
{
msg_Warn( NULL, "couldn't set thread priority: %s",
strerror(i_error) );
}
}
ReadConfiguration( psz_conf_file );
if ( psz_srv_socket != NULL )
comm_Open();
for ( ; ; )
{
if ( b_hup_received )
{
b_hup_received = 0;
msg_Warn( NULL, "HUP received, reloading" );
ReadConfiguration( psz_conf_file );
}
demux_Run();
}
}
/*****************************************************************************
* dvblast.h
*****************************************************************************
* Copyright (C) 2004, 2008-2009 the VideoLAN team
* $Id: dvblast.h 3 2005-10-05 12:15:55Z cmassiot $
*
* Authors: Christophe Massiot <massiot@via.ecp.fr>
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include <dvbpsi/dvbpsi.h>
#include <dvbpsi/descriptor.h>
#include <dvbpsi/pmt.h>
#define DEFAULT_PORT 3001
#define TS_SIZE 188
#define NB_BLOCKS 7
#define RTP_SIZE 12
#define EMPTY_PID 8192
#define PADDING_PID 8191
#define WATCHDOG_WAIT 10000000LL
#define MAX_ERRORS 100000
typedef int64_t mtime_t;
typedef struct block_t
{
uint8_t p_ts[TS_SIZE];
int i_refcount;
struct block_t *p_next;
} block_t;
typedef struct output_t
{
in_addr_t i_maddr;
uint16_t i_port;
/* output */
int i_handle;
block_t *pp_blocks[NB_BLOCKS];
int i_depth;
uint16_t i_cc;
mtime_t i_ref_timestamp;
mtime_t i_ref_wallclock;
/* demux */
int i_nb_errors;
mtime_t i_last_error;
int b_valid;
dvbpsi_psi_section_t *p_pat_section;
uint8_t i_pat_version, i_pat_cc;
dvbpsi_psi_section_t *p_pmt_section;
uint8_t i_pmt_version, i_pmt_cc;
/* configuration */
uint16_t i_sid; /* 0 if raw mode */
uint16_t *pi_pids;
int i_nb_pids;
int b_watch;
int b_still_present;
} output_t;
extern output_t **pp_outputs;
extern int i_nb_outputs;
extern output_t output_dup;
extern char *psz_srv_socket;
extern int i_ttl;
extern in_addr_t i_ssrc;
extern int i_adapter;
extern int i_frequency;
extern int i_srate;
extern int i_voltage;
extern int b_tone;
extern int i_bandwidth;
extern char *psz_modulation;
extern int b_budget_mode;
extern volatile int b_hup_received;
extern mtime_t i_ca_timeout;
extern int i_comm_fd;
/*****************************************************************************
* Prototypes
*****************************************************************************/
void msg_Info( void *_unused, const char *psz_format, ... );
void msg_Err( void *_unused, const char *psz_format, ... );
void msg_Warn( void *_unused, const char *psz_format, ... );
void msg_Dbg( void *_unused, const char *psz_format, ... );
mtime_t mdate( void );
void msleep( mtime_t delay );
void dvb_Open( void );
block_t * dvb_Read( void );
int dvb_SetFilter( uint16_t i_pid );
void dvb_UnsetFilter( int i_fd, uint16_t i_pid );
uint8_t dvb_FrontendStatus( uint8_t *p_answer, ssize_t *pi_size );
void demux_Open( void );
void demux_Run( void );
void demux_Change( output_t *p_output, uint16_t i_sid,
uint16_t *pi_pids, int i_nb_pids );
void demux_ResendCAPMTs( void );
int PIDIsSelected( uint16_t i_pid );
output_t *output_Create( in_addr_t i_maddr, uint16_t i_port );
int output_Init( output_t *p_output, in_addr_t i_maddr, uint16_t i_port );
void output_Close( output_t *p_output );
void output_Put( output_t *p_output, block_t *p_block );
void comm_Open( void );
void comm_Read( void );
/*****************************************************************************
* block_New
*****************************************************************************/
static inline block_t *block_New( void )
{
block_t *p_block = malloc(sizeof(block_t));
p_block->p_next = NULL;
p_block->i_refcount = 1;
return p_block;
}
/*****************************************************************************
* block_Delete
*****************************************************************************/
static inline void block_Delete( block_t *p_block )
{
free( p_block );
}
/*****************************************************************************
* block_DeleteChain
*****************************************************************************/
static inline void block_DeleteChain( block_t *p_block )
{
while ( p_block != NULL )
{
block_t *p_next = p_block->p_next;
free( p_block );
p_block = p_next;
}
}
/*****************************************************************************
* block_GetSync
*****************************************************************************/
static inline uint8_t block_GetSync( block_t *p_block )
{
return p_block->p_ts[0];
}
/*****************************************************************************
* block_HasTransportError
*****************************************************************************/
static inline uint8_t block_HasTransportError( block_t *p_block )
{
return p_block->p_ts[1] & 0x80;
}
/*****************************************************************************
* block_UnitStart
*****************************************************************************/
static inline uint8_t block_UnitStart( block_t *p_block )
{
return p_block->p_ts[1] & 0x40;
}
/*****************************************************************************
* block_GetPID
*****************************************************************************/
static inline uint16_t block_GetPID( block_t *p_block )
{
return (((uint16_t)p_block->p_ts[1] & 0x1f) << 8)
| p_block->p_ts[2];
}
/*****************************************************************************
* block_GetScrambling
*****************************************************************************/
static inline uint8_t block_GetScrambling( block_t *p_block )
{
return p_block->p_ts[3] & 0xc0;
}
/*****************************************************************************
* block_GetCC
*****************************************************************************/
static inline uint8_t block_GetCC( block_t *p_block )
{
return p_block->p_ts[3] & 0xf;
}
/*****************************************************************************
* block_HasPCR
*****************************************************************************/
static inline int block_HasPCR( block_t *p_block )
{
return ( p_block->p_ts[3] & 0x20 ) && /* adaptation field present */
( p_block->p_ts[4] >= 7 ) && /* adaptation field size */
( p_block->p_ts[5] & 0x10 ); /* has PCR */
}
/*****************************************************************************
* block_GetPCR
*****************************************************************************/
static inline mtime_t block_GetPCR( block_t *p_block )
{
return ( (mtime_t)p_block->p_ts[6] << 25 ) |
( (mtime_t)p_block->p_ts[7] << 17 ) |
( (mtime_t)p_block->p_ts[8] << 9 ) |
( (mtime_t)p_block->p_ts[9] << 1 ) |
( (mtime_t)p_block->p_ts[10] >> 7 );
}
/*****************************************************************************
* block_GetPayload
*****************************************************************************/
static inline uint8_t *block_GetPayload( block_t *p_block )
{
if ( !(p_block->p_ts[3] & 0x10) )
return NULL;
if ( !(p_block->p_ts[3] & 0x20) )
return &p_block->p_ts[4];
return &p_block->p_ts[ 5 + p_block->p_ts[4] ];
}
#!/bin/sh
###############################################################################
# dvblast_mmi.sh
###############################################################################
# Copyright (C) 1998-2008 the VideoLAN team
# $Id: ead45da90b964b384dd20bfad15b7233e7243c66 $
#
# Authors: Christophe Massiot <massiot@via.ecp.fr>
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
###############################################################################
BASE_DIR=`dirname $_`
#
# Check args
#
if test x"$1" != x"-r" -o -z "$2"; then
echo "Usage: $0 -r <remote socket> [<slot 0-n>]" >&2
exit 1
fi
SOCKET=$2
SLOT=$3
if which dvblastctl >/dev/null; then
DVBLASTCTL="dvblastctl -r $SOCKET"
elif test -x "$PWD/dvblastctl"; then
DVBLASTCTL="$PWD/dvblastctl -r $SOCKET"
elif test -x "$BASE_DIR/dvblastctl"; then
DVBLASTCTL="$BASE_DIR/dvblastctl -r $SOCKET"
else
echo "Couldn't find dvblastctl"
exit 1
fi
#
# Check adapter status
#
$DVBLASTCTL mmi_status >/dev/null
NUM_SLOTS=$?
if test $NUM_SLOTS -eq 255; then
echo "Unable to set up a connection" >&2
exit 2
fi
if test $NUM_SLOTS -eq 0; then
echo "Adapter has no available CAM module" >&2
exit 3
fi
if test -z $SLOT; then
echo "Defaulting to slot #0"
SLOT=0
fi
if test "$SLOT" -ge $NUM_SLOTS; then
echo "Slot out of range, pick in the range 0-`expr $NUM_SLOTS - 1`" >&2
exit 3
fi
#
# Check CAM status
#
$DVBLASTCTL mmi_slot_status $SLOT >/dev/null
STATUS=$?
if test $STATUS -ne 0; then
echo "Slot is not ready, retry later" >&2
exit 3
fi
$DVBLASTCTL mmi_get $SLOT >/dev/null
STATUS=$?
if test $STATUS -eq 255; then
echo "Opening MMI session..."
$DVBLASTCTL mmi_open $SLOT
STATUS=$?
if test $STATUS -eq 255; then
echo "Communication error" >&2
exit 2
elif test $STATUS -ne 0; then
echo "Couldn't open MMI session" >&2
exit 4
fi
sleep 3
fi
#
# Da loop
#
while :; do
$DVBLASTCTL mmi_get $SLOT
STATUS=$?
case $STATUS in
255)
echo "Connection closed" >&2
exit 2
;;
254)
echo -n "Your choice (empty for extra choices) ? "
;;
253)
echo "CAUTION: the password won't be bulleted, be alone"
echo -n "Your choice (empty for extra choices) ? "
;;
0)
echo -n "Your choice: (B)ack, (C)lose or (R)etry ? "
;;
*)
echo -n "Your choice: [0-$STATUS], (C)lose or (R)etry ? "
;;
esac
read -r ANSWER
case $STATUS in
254|253)
if test -z "$ANSWER"; then
echo -n "(B)ack, (C)lose or (R)etry ? "
read -r ANSWER
case "$ANSWER" in
B|b|Back|back|BACK)
if ! $DVBLASTCTL mmi_send_text $SLOT; then
echo "mmi_send_text failed, apparently" >&2
else
sleep 1
fi
;;
C|c|Close|close|CLOSE)
$DVBLASTCTL mmi_close $SLOT
exit 0
;;
R|r|Retry|retry|RETRY)
:
;;
*)
echo "Invalid string, retry..."
;;
esac
else
if ! $DVBLASTCTL mmi_send_text $SLOT "$ANSWER"; then
echo "mmi_send_text failed, apparently" >&2
else
sleep 1
fi
fi
;;
*)
case "$ANSWER" in
B|b|Back|back|BACK)
if ! $DVBLASTCTL mmi_send_choice $SLOT 0; then
echo "mmi_send_choice failed, apparently" >&2
else
sleep 1
fi
;;
C|c|Close|close|CLOSE)
$DVBLASTCTL mmi_close $SLOT
exit 0
;;
R|r|Retry|retry|RETRY)
:
;;
*)
echo "$ANSWER" | grep -q "^[[:digit:]]\+\$"
if test $? -ne 0; then
echo "Invalid string, retry..."
else
if ! $DVBLASTCTL mmi_send_choice $SLOT "$ANSWER"; then
echo "mmi_send_choice failed, apparently" >&2
else
sleep 1
fi
fi
;;
esac
;;
esac
echo
done
/*****************************************************************************
* dvblastctl.c
*****************************************************************************
* Copyright (C) 2008 the VideoLAN team
* $Id: dvblast.c 11 2007-08-09 18:54:37Z cmassiot $
*
* Authors: Christophe Massiot <massiot@via.ecp.fr>
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <errno.h>
#include "dvblast.h"
#include "en50221.h"
#include "comm.h"
void usage()
{
msg_Err( NULL, "Usage: dvblastctl -r <remote socket> reload|shutdown|fe_status|mmi_status|mmi_open|mmi_close|mmi_get|mmi_send_text|mmi_send_choice [<CAM slot>] [<text/choice>]" );
exit(1);
}
int main( int i_argc, char **ppsz_argv )
{
char psz_client_socket[FILENAME_MAX];
char *psz_srv_socket = NULL;
int i_fd, i_mask;
int i = 65535;
ssize_t i_size;
struct sockaddr_un sun_client, sun_server;
uint8_t p_buffer[COMM_BUFFER_SIZE];
for ( ; ; )
{
char c;
if ( (c = getopt(i_argc, ppsz_argv, "r:h")) == -1 )
break;
switch ( c )
{
case 'r':
psz_srv_socket = optarg;
break;
case 'h':
default:
usage();
}
}
if ( ppsz_argv[optind] == NULL || psz_srv_socket == NULL )
usage();
if ( strcmp(ppsz_argv[optind], "reload")
&& strcmp(ppsz_argv[optind], "shutdown")
&& strcmp(ppsz_argv[optind], "fe_status")
&& strcmp(ppsz_argv[optind], "mmi_status")
&& strcmp(ppsz_argv[optind], "mmi_slot_status")
&& strcmp(ppsz_argv[optind], "mmi_open")
&& strcmp(ppsz_argv[optind], "mmi_close")
&& strcmp(ppsz_argv[optind], "mmi_get")
&& strcmp(ppsz_argv[optind], "mmi_send_text")
&& strcmp(ppsz_argv[optind], "mmi_send_choice") )
usage();
if ( (!strcmp(ppsz_argv[optind], "mmi_slot_status")
|| !strcmp(ppsz_argv[optind], "mmi_open")
|| !strcmp(ppsz_argv[optind], "mmi_close")
|| !strcmp(ppsz_argv[optind], "mmi_get")
|| !strcmp(ppsz_argv[optind], "mmi_send_text")
|| !strcmp(ppsz_argv[optind], "mmi_send_choice"))
&& ppsz_argv[optind + 1] == NULL )
usage();
if ( !strcmp(ppsz_argv[optind], "mmi_send_choice")
&& ppsz_argv[optind + 2] == NULL )
usage();
#warning expect brain-dead gcc warning about tmpnam here
tmpnam(psz_client_socket);
if ( (i_fd = socket( AF_UNIX, SOCK_DGRAM, 0 )) < 0 )
{
msg_Err( NULL, "cannot create UNIX socket (%s)", strerror(errno) );
return -1;
}
setsockopt( i_fd, SOL_SOCKET, SO_RCVBUF, &i, sizeof(i) );
memset( &sun_client, 0, sizeof(sun_client) );
sun_client.sun_family = AF_UNIX;
strncpy( sun_client.sun_path, psz_client_socket,
sizeof(sun_client.sun_path) );
sun_client.sun_path[sizeof(sun_client.sun_path) - 1] = '\0';
i_mask = umask(077);
if ( bind( i_fd, (struct sockaddr *)&sun_client,
SUN_LEN(&sun_client) ) < 0 )
{
msg_Err( NULL, "cannot bind (%s)", strerror(errno) );
umask( i_mask );
close( i_fd );
exit(255);
}
umask( i_mask );
memset( &sun_server, 0, sizeof(sun_server) );
sun_server.sun_family = AF_UNIX;
strncpy( sun_server.sun_path, psz_srv_socket, sizeof(sun_server.sun_path) );
sun_server.sun_path[sizeof(sun_server.sun_path) - 1] = '\0';
p_buffer[0] = COMM_HEADER_MAGIC;
p_buffer[2] = 0;
p_buffer[3] = 0;
i_size = COMM_HEADER_SIZE;
if ( !strcmp(ppsz_argv[optind], "reload") )
p_buffer[1] = CMD_RELOAD;
else if ( !strcmp(ppsz_argv[optind], "shutdown") )
p_buffer[1] = CMD_SHUTDOWN;
else if ( !strcmp(ppsz_argv[optind], "fe_status") )
p_buffer[1] = CMD_FRONTEND_STATUS;
else if ( !strcmp(ppsz_argv[optind], "mmi_status") )
p_buffer[1] = CMD_MMI_STATUS;
else
{
p_buffer[4] = atoi(ppsz_argv[optind + 1]);
i_size = COMM_HEADER_SIZE + 1;
if ( !strcmp(ppsz_argv[optind], "mmi_slot_status") )
p_buffer[1] = CMD_MMI_SLOT_STATUS;
else if ( !strcmp(ppsz_argv[optind], "mmi_open") )
p_buffer[1] = CMD_MMI_OPEN;
else if ( !strcmp(ppsz_argv[optind], "mmi_close") )
p_buffer[1] = CMD_MMI_CLOSE;
else if ( !strcmp(ppsz_argv[optind], "mmi_get") )
p_buffer[1] = CMD_MMI_RECV;
else
{
struct cmd_mmi_send *p_cmd = (struct cmd_mmi_send *)&p_buffer[4];
p_buffer[1] = CMD_MMI_SEND;
p_cmd->i_slot = atoi(ppsz_argv[optind + 1]);
if ( !strcmp(ppsz_argv[optind], "mmi_send_text") )
{
en50221_mmi_object_t object;
object.i_object_type = EN50221_MMI_ANSW;
if ( ppsz_argv[optind + 2] == NULL
|| ppsz_argv[optind + 2][0] == '\0' )
{
object.u.answ.b_ok = 0;
object.u.answ.psz_answ = "";
}
else
{
object.u.answ.b_ok = 1;
object.u.answ.psz_answ = ppsz_argv[optind + 2];
}
i_size = COMM_BUFFER_SIZE - COMM_HEADER_SIZE
- ((void *)&p_cmd->object - (void *)p_cmd);
if ( en50221_SerializeMMIObject( (uint8_t *)&p_cmd->object,
&i_size, &object ) == -1 )
{
msg_Err( NULL, "buffer too small" );
close( i_fd );
unlink( psz_client_socket );
exit(255);
}
i_size += COMM_HEADER_SIZE
+ ((void *)&p_cmd->object - (void *)p_cmd);
}
else /* mmi_send_choice */
{
i_size = COMM_HEADER_SIZE + sizeof(struct cmd_mmi_send);
p_cmd->object.i_object_type = EN50221_MMI_MENU_ANSW;
p_cmd->object.u.menu_answ.i_choice
= atoi(ppsz_argv[optind + 2]);
}
}
}
if ( sendto( i_fd, p_buffer, i_size, 0, (struct sockaddr *)&sun_server,
SUN_LEN(&sun_server) ) < 0 )
{
msg_Err( NULL, "cannot send comm socket (%s)", strerror(errno) );
close( i_fd );
unlink( psz_client_socket );
exit(255);
}
i_size = recv( i_fd, p_buffer, COMM_BUFFER_SIZE, 0 );
close( i_fd );
unlink( psz_client_socket );
if ( i_size < COMM_HEADER_SIZE )
{
msg_Err( NULL, "cannot recv comm socket (%d:%s)", i_size,
strerror(errno) );
exit(255);
}
if ( p_buffer[0] != COMM_HEADER_MAGIC )
{
msg_Err( NULL, "wrong protocol version 0x%x", p_buffer[0] );
exit(255);
}
switch ( p_buffer[1] )
{
case RET_OK:
exit(0);
break;
case RET_ERR:
msg_Err( NULL, "request failed" );
exit(255);
break;
case RET_HUH:
msg_Err( NULL, "internal error" );
exit(255);
break;
case RET_FRONTEND_STATUS:
{
struct ret_frontend_status *p_ret =
(struct ret_frontend_status *)&p_buffer[COMM_HEADER_SIZE];
if ( i_size != COMM_HEADER_SIZE + sizeof(struct ret_frontend_status) )
{
msg_Err( NULL, "bad frontend status" );
exit(255);
}
switch ( p_ret->info.type )
{
case FE_QPSK: printf("type: QPSK\n"); break;
case FE_QAM: printf("type: QAM\n"); break;
case FE_OFDM: printf("type: OFDM\n"); break;
default: printf("type: UNKNOWN\n"); break;
}
#define PRINT_INFO( x ) \
printf( STRINGIFY(x) ": %u\n", p_ret->info.x );
PRINT_INFO( frequency_min );
PRINT_INFO( frequency_max );
PRINT_INFO( frequency_stepsize );
PRINT_INFO( frequency_tolerance );
PRINT_INFO( symbol_rate_min );
PRINT_INFO( symbol_rate_max );
PRINT_INFO( symbol_rate_tolerance );
PRINT_INFO( notifier_delay );
#undef PRINT_INFO
printf("\ncapability list:\n");
#define PRINT_CAPS( x ) \
if ( p_ret->info.caps & (FE_##x) ) \
printf( STRINGIFY(x) "\n" );
PRINT_CAPS( IS_STUPID );
PRINT_CAPS( CAN_INVERSION_AUTO );
PRINT_CAPS( CAN_FEC_1_2 );
PRINT_CAPS( CAN_FEC_2_3 );
PRINT_CAPS( CAN_FEC_3_4 );
PRINT_CAPS( CAN_FEC_4_5 );
PRINT_CAPS( CAN_FEC_5_6 );
PRINT_CAPS( CAN_FEC_6_7 );
PRINT_CAPS( CAN_FEC_7_8 );
PRINT_CAPS( CAN_FEC_8_9 );
PRINT_CAPS( CAN_FEC_AUTO );
PRINT_CAPS( CAN_QPSK );
PRINT_CAPS( CAN_QAM_16 );
PRINT_CAPS( CAN_QAM_32 );
PRINT_CAPS( CAN_QAM_64 );
PRINT_CAPS( CAN_QAM_128 );
PRINT_CAPS( CAN_QAM_256 );
PRINT_CAPS( CAN_QAM_AUTO );
PRINT_CAPS( CAN_TRANSMISSION_MODE_AUTO );
PRINT_CAPS( CAN_BANDWIDTH_AUTO );
PRINT_CAPS( CAN_GUARD_INTERVAL_AUTO );
PRINT_CAPS( CAN_HIERARCHY_AUTO );
PRINT_CAPS( CAN_8VSB );
PRINT_CAPS( CAN_16VSB );
#if DVB_API_VERSION >= 5
PRINT_CAPS( HAS_EXTENDED_CAPS );
/* FIXME: apparently this doesn't exist in some versions of the
* linux-dvb headers */
/* PRINT_CAPS( CAN_2G_MODULATION ); */
#endif
PRINT_CAPS( NEEDS_BENDING );
PRINT_CAPS( CAN_MUTE_TS );
PRINT_CAPS( CAN_RECOVER );
#undef PRINT_CAPS
printf("\nstatus:\n");
#define PRINT_STATUS( x ) \
if ( p_ret->i_status & (FE_##x) ) \
printf( STRINGIFY(x) "\n" );
PRINT_STATUS( HAS_SIGNAL );
PRINT_STATUS( HAS_CARRIER );
PRINT_STATUS( HAS_VITERBI );
PRINT_STATUS( HAS_SYNC );
PRINT_STATUS( HAS_LOCK );
PRINT_STATUS( REINIT );
#undef PRINT_STATUS
if ( p_ret->i_status & FE_HAS_LOCK )
{
printf("\nBit error rate: %d\n", p_ret->i_ber);
printf("Signal strength: %d\n", p_ret->i_strength);
printf("SNR: %d\n", p_ret->i_snr);
exit(0);
}
exit(1);
break;
}
case RET_MMI_STATUS:
{
struct ret_mmi_status *p_ret =
(struct ret_mmi_status *)&p_buffer[COMM_HEADER_SIZE];
if ( i_size != COMM_HEADER_SIZE + sizeof(struct ret_mmi_status) )
{
msg_Err( NULL, "bad MMI status" );
exit(255);
}
printf("CA interface with %d %s, type:\n", p_ret->caps.slot_num,
p_ret->caps.slot_num == 1 ? "slot" : "slots");
#define PRINT_CAPS( x, s ) \
if ( p_ret->caps.slot_type & (CA_##x) ) \
printf(s "\n");
PRINT_CAPS( CI, "CI high level interface" );
PRINT_CAPS( CI_LINK, "CI link layer level interface" );
PRINT_CAPS( CI_PHYS, "CI physical layer level interface (not supported)" );
PRINT_CAPS( DESCR, "built-in descrambler" );
PRINT_CAPS( SC, "simple smartcard interface" );
#undef PRINT_CAPS
printf("\n%d available %s\n", p_ret->caps.descr_num,
p_ret->caps.descr_num == 1 ? "descrambler (key)" :
"descramblers (keys)");
#define PRINT_DESC( x ) \
if ( p_ret->caps.descr_type & (CA_##x) ) \
printf( STRINGIFY(x) "\n" );
PRINT_DESC( ECD );
PRINT_DESC( NDS );
PRINT_DESC( DSS );
#undef PRINT_DESC
exit( p_ret->caps.slot_num );
break;
}
case RET_MMI_SLOT_STATUS:
{
struct ret_mmi_slot_status *p_ret =
(struct ret_mmi_slot_status *)&p_buffer[COMM_HEADER_SIZE];
if ( i_size < COMM_HEADER_SIZE + sizeof(struct ret_mmi_slot_status) )
{
msg_Err( NULL, "bad MMI slot status" );
exit(255);
}
printf("CA slot #%u: ", p_ret->sinfo.num);
#define PRINT_TYPE( x, s ) \
if ( p_ret->sinfo.type & (CA_##x) ) \
printf(s);
PRINT_TYPE( CI, "high level, " );
PRINT_TYPE( CI_LINK, "link layer level, " );
PRINT_TYPE( CI_PHYS, "physical layer level, " );
#undef PRINT_TYPE
if ( p_ret->sinfo.flags & CA_CI_MODULE_READY )
{
printf("module present and ready\n");
exit(0);
}
if ( p_ret->sinfo.flags & CA_CI_MODULE_PRESENT )
printf("module present, not ready\n");
else
printf("module not present\n");
exit(1);
break;
}
case RET_MMI_RECV:
{
struct ret_mmi_recv *p_ret =
(struct ret_mmi_recv *)&p_buffer[COMM_HEADER_SIZE];
if ( i_size < COMM_HEADER_SIZE + sizeof(struct ret_mmi_recv) )
{
msg_Err( NULL, "bad MMI recv" );
exit(255);
}
en50221_UnserializeMMIObject( &p_ret->object, i_size
- COMM_HEADER_SIZE - ((void *)&p_ret->object - (void *)p_ret) );
switch ( p_ret->object.i_object_type )
{
case EN50221_MMI_ENQ:
printf("%s\n", p_ret->object.u.enq.psz_text);
printf("(empty to cancel)\n");
exit(p_ret->object.u.enq.b_blind ? 253 : 254);
break;
case EN50221_MMI_MENU:
printf("%s\n", p_ret->object.u.menu.psz_title);
printf("%s\n", p_ret->object.u.menu.psz_subtitle);
printf("0 - Cancel\n");
for ( i = 0; i < p_ret->object.u.menu.i_choices; i++ )
printf("%d - %s\n", i + 1,
p_ret->object.u.menu.ppsz_choices[i]);
printf("%s\n", p_ret->object.u.menu.psz_bottom);
exit(p_ret->object.u.menu.i_choices);
break;
case EN50221_MMI_LIST:
printf("%s\n", p_ret->object.u.menu.psz_title);
printf("%s\n", p_ret->object.u.menu.psz_subtitle);
for ( i = 0; i < p_ret->object.u.menu.i_choices; i++ )
printf("%s\n", p_ret->object.u.menu.ppsz_choices[i]);
printf("%s\n", p_ret->object.u.menu.psz_bottom);
printf("(0 to cancel)\n");
exit(0);
break;
default:
printf("unknown MMI object\n");
exit(255);
break;
}
exit(255);
break;
}
default:
msg_Err( NULL, "wrong answer %u", p_buffer[1] );
exit(255);
}
}
/*****************************************************************************
* en50221.c : implementation of the transport, session and applications
* layers of EN 50 221
*****************************************************************************
* Copyright (C) 2004-2005 the VideoLAN team
*
* Authors: Christophe Massiot <massiot@via.ecp.fr>
* Based on code from libdvbci Copyright (C) 2000 Klaus Schmidinger
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
/* DVB Card Drivers */
#include <linux/dvb/version.h>
#include <linux/dvb/dmx.h>
#include <linux/dvb/frontend.h>
#include <linux/dvb/ca.h>
#include "dvblast.h"
#include "en50221.h"
#include "comm.h"
#define TAB_APPEND( count, tab, p ) \
if( (count) > 0 ) \
{ \
(tab) = realloc( tab, sizeof( void ** ) * ( (count) + 1 ) ); \
} \
else \
{ \
(tab) = malloc( sizeof( void ** ) ); \
} \
(tab)[count] = (p); \
(count)++
/*****************************************************************************
* Local declarations
*****************************************************************************/
#undef DEBUG_TPDU
#define HLCI_WAIT_CAM_READY 0
#define CAM_PROG_MAX MAX_PROGRAMS
typedef struct en50221_session_t
{
int i_slot;
int i_resource_id;
void (* pf_handle)( access_t *, int, uint8_t *, int );
void (* pf_close)( access_t *, int );
void (* pf_manage)( access_t *, int );
void *p_sys;
} en50221_session_t;
int i_ca_handle = 0;
int i_ca_type = -1;
static int i_nb_slots = 0;
static int pb_active_slot[MAX_CI_SLOTS];
static int pb_tc_has_data[MAX_CI_SLOTS];
static int pb_slot_mmi_expected[MAX_CI_SLOTS];
static int pb_slot_mmi_undisplayed[MAX_CI_SLOTS];
static en50221_session_t p_sessions[MAX_SESSIONS];
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static void ResourceManagerOpen( access_t * p_access, int i_session_id );
static void ApplicationInformationOpen( access_t * p_access, int i_session_id );
static void ConditionalAccessOpen( access_t * p_access, int i_session_id );
static void DateTimeOpen( access_t * p_access, int i_session_id );
static void MMIOpen( access_t * p_access, int i_session_id );
/*****************************************************************************
* Utility functions
*****************************************************************************/
#define SIZE_INDICATOR 0x80
static uint8_t *GetLength( uint8_t *p_data, int *pi_length )
{
*pi_length = *p_data++;
if ( (*pi_length & SIZE_INDICATOR) != 0 )
{
int l = *pi_length & ~SIZE_INDICATOR;
int i;
*pi_length = 0;
for ( i = 0; i < l; i++ )
*pi_length = (*pi_length << 8) | *p_data++;
}
return p_data;
}
static uint8_t *SetLength( uint8_t *p_data, int i_length )
{
uint8_t *p = p_data;
if ( i_length < 128 )
{
*p++ = i_length;
}
else if ( i_length < 256 )
{
*p++ = SIZE_INDICATOR | 0x1;
*p++ = i_length;
}
else if ( i_length < 65536 )
{
*p++ = SIZE_INDICATOR | 0x2;
*p++ = i_length >> 8;
*p++ = i_length & 0xff;
}
else if ( i_length < 16777216 )
{
*p++ = SIZE_INDICATOR | 0x3;
*p++ = i_length >> 16;
*p++ = (i_length >> 8) & 0xff;
*p++ = i_length & 0xff;
}
else
{
*p++ = SIZE_INDICATOR | 0x4;
*p++ = i_length >> 24;
*p++ = (i_length >> 16) & 0xff;
*p++ = (i_length >> 8) & 0xff;
*p++ = i_length & 0xff;
}
return p;
}
/*
* Transport layer
*/
#define MAX_TPDU_SIZE 2048
#define MAX_TPDU_DATA (MAX_TPDU_SIZE - 4)
#define DATA_INDICATOR 0x80
#define T_SB 0x80
#define T_RCV 0x81
#define T_CREATE_TC 0x82
#define T_CTC_REPLY 0x83
#define T_DELETE_TC 0x84
#define T_DTC_REPLY 0x85
#define T_REQUEST_TC 0x86
#define T_NEW_TC 0x87
#define T_TC_ERROR 0x88
#define T_DATA_LAST 0xA0
#define T_DATA_MORE 0xA1
static void Dump( int b_outgoing, uint8_t *p_data, int i_size )
{
#ifdef DEBUG_TPDU
int i;
#define MAX_DUMP 256
fprintf(stderr, "%s ", b_outgoing ? "-->" : "<--");
for ( i = 0; i < i_size && i < MAX_DUMP; i++)
fprintf(stderr, "%02X ", p_data[i]);
fprintf(stderr, "%s\n", i_size >= MAX_DUMP ? "..." : "");
#endif
}
/*****************************************************************************
* TPDUSend
*****************************************************************************/
static int TPDUSend( access_t * p_access, uint8_t i_slot, uint8_t i_tag,
const uint8_t *p_content, int i_length )
{
uint8_t i_tcid = i_slot + 1;
uint8_t p_data[MAX_TPDU_SIZE];
int i_size;
i_size = 0;
p_data[0] = i_slot;
p_data[1] = i_tcid;
p_data[2] = i_tag;
switch ( i_tag )
{
case T_RCV:
case T_CREATE_TC:
case T_CTC_REPLY:
case T_DELETE_TC:
case T_DTC_REPLY:
case T_REQUEST_TC:
p_data[3] = 1; /* length */
p_data[4] = i_tcid;
i_size = 5;
break;
case T_NEW_TC:
case T_TC_ERROR:
p_data[3] = 2; /* length */
p_data[4] = i_tcid;
p_data[5] = p_content[0];
i_size = 6;
break;
case T_DATA_LAST:
case T_DATA_MORE:
{
/* i_length <= MAX_TPDU_DATA */
uint8_t *p = p_data + 3;
p = SetLength( p, i_length + 1 );
*p++ = i_tcid;
if ( i_length )
memcpy( p, p_content, i_length );
i_size = i_length + (p - p_data);
}
break;
default:
break;
}
Dump( 1, p_data, i_size );
if ( write( i_ca_handle, p_data, i_size ) != i_size )
{
msg_Err( p_access, "cannot write to CAM device (%m)" );
return -1;
}
return 0;
}
/*****************************************************************************
* TPDURecv
*****************************************************************************/
#define CAM_READ_TIMEOUT 3500 // ms
static int TPDURecv( access_t * p_access, uint8_t i_slot, uint8_t *pi_tag,
uint8_t *p_data, int *pi_size )
{
uint8_t i_tcid = i_slot + 1;
int i_size;
struct pollfd pfd[1];
pfd[0].fd = i_ca_handle;
pfd[0].events = POLLIN;
if ( !(poll(pfd, 1, CAM_READ_TIMEOUT) > 0 && (pfd[0].revents & POLLIN)) )
{
msg_Err( p_access, "cannot poll from CAM device" );
return -1;
}
if ( pi_size == NULL )
{
p_data = malloc( MAX_TPDU_SIZE );
}
for ( ; ; )
{
i_size = read( i_ca_handle, p_data, MAX_TPDU_SIZE );
if ( i_size >= 0 || errno != EINTR )
break;
}
if ( i_size < 5 )
{
msg_Err( p_access, "cannot read from CAM device (%d:%m)", i_size );
return -1;
}
if ( p_data[1] != i_tcid )
{
msg_Err( p_access, "invalid read from CAM device (%d instead of %d)",
p_data[1], i_tcid );
return -1;
}
*pi_tag = p_data[2];
pb_tc_has_data[i_slot] = (i_size >= 4
&& p_data[i_size - 4] == T_SB
&& p_data[i_size - 3] == 2
&& (p_data[i_size - 1] & DATA_INDICATOR))
? 1 : 0;
Dump( 0, p_data, i_size );
if ( pi_size == NULL )
free( p_data );
else
*pi_size = i_size;
return 0;
}
/*
* Session layer
*/
#define ST_SESSION_NUMBER 0x90
#define ST_OPEN_SESSION_REQUEST 0x91
#define ST_OPEN_SESSION_RESPONSE 0x92
#define ST_CREATE_SESSION 0x93
#define ST_CREATE_SESSION_RESPONSE 0x94
#define ST_CLOSE_SESSION_REQUEST 0x95
#define ST_CLOSE_SESSION_RESPONSE 0x96
#define SS_OK 0x00
#define SS_NOT_ALLOCATED 0xF0
#define RI_RESOURCE_MANAGER 0x00010041
#define RI_APPLICATION_INFORMATION 0x00020041
#define RI_CONDITIONAL_ACCESS_SUPPORT 0x00030041
#define RI_HOST_CONTROL 0x00200041
#define RI_DATE_TIME 0x00240041
#define RI_MMI 0x00400041
static int ResourceIdToInt( uint8_t *p_data )
{
return ((int)p_data[0] << 24) | ((int)p_data[1] << 16)
| ((int)p_data[2] << 8) | p_data[3];
}
/*****************************************************************************
* SPDUSend
*****************************************************************************/
static int SPDUSend( access_t * p_access, int i_session_id,
uint8_t *p_data, int i_size )
{
uint8_t *p_spdu = malloc( i_size + 4 );
uint8_t *p = p_spdu;
uint8_t i_tag;
uint8_t i_slot = p_sessions[i_session_id - 1].i_slot;
*p++ = ST_SESSION_NUMBER;
*p++ = 0x02;
*p++ = (i_session_id >> 8);
*p++ = i_session_id & 0xff;
memcpy( p, p_data, i_size );
i_size += 4;
p = p_spdu;
while ( i_size > 0 )
{
if ( i_size > MAX_TPDU_DATA )
{
if ( TPDUSend( p_access, i_slot, T_DATA_MORE, p,
MAX_TPDU_DATA ) != 0 )
{
msg_Err( p_access, "couldn't send TPDU on session %d",
i_session_id );
free( p_spdu );
return -1;
}
p += MAX_TPDU_DATA;
i_size -= MAX_TPDU_DATA;
}
else
{
if ( TPDUSend( p_access, i_slot, T_DATA_LAST, p, i_size )
!= 0 )
{
msg_Err( p_access, "couldn't send TPDU on session %d",
i_session_id );
free( p_spdu );
return -1;
}
i_size = 0;
}
if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) != 0
|| i_tag != T_SB )
{
msg_Err( p_access, "couldn't recv TPDU on session %d",
i_session_id );
free( p_spdu );
return -1;
}
}
free( p_spdu );
return 0;
}
/*****************************************************************************
* SessionOpen
*****************************************************************************/
static void SessionOpen( access_t * p_access, uint8_t i_slot,
uint8_t *p_spdu, int i_size )
{
int i_session_id;
int i_resource_id = ResourceIdToInt( &p_spdu[2] );
uint8_t p_response[16];
int i_status = SS_NOT_ALLOCATED;
uint8_t i_tag;
for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
{
if ( !p_sessions[i_session_id - 1].i_resource_id )
break;
}
if ( i_session_id == MAX_SESSIONS )
{
msg_Err( p_access, "too many sessions !" );
return;
}
p_sessions[i_session_id - 1].i_slot = i_slot;
p_sessions[i_session_id - 1].i_resource_id = i_resource_id;
p_sessions[i_session_id - 1].pf_close = NULL;
p_sessions[i_session_id - 1].pf_manage = NULL;
if ( i_resource_id == RI_RESOURCE_MANAGER
|| i_resource_id == RI_APPLICATION_INFORMATION
|| i_resource_id == RI_CONDITIONAL_ACCESS_SUPPORT
|| i_resource_id == RI_DATE_TIME
|| i_resource_id == RI_MMI )
{
i_status = SS_OK;
}
p_response[0] = ST_OPEN_SESSION_RESPONSE;
p_response[1] = 0x7;
p_response[2] = i_status;
p_response[3] = p_spdu[2];
p_response[4] = p_spdu[3];
p_response[5] = p_spdu[4];
p_response[6] = p_spdu[5];
p_response[7] = i_session_id >> 8;
p_response[8] = i_session_id & 0xff;
if ( TPDUSend( p_access, i_slot, T_DATA_LAST, p_response, 9 ) !=
0 )
{
msg_Err( p_access,
"SessionOpen: couldn't send TPDU on slot %d", i_slot );
return;
}
if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) != 0 )
{
msg_Err( p_access,
"SessionOpen: couldn't recv TPDU on slot %d", i_slot );
return;
}
switch ( i_resource_id )
{
case RI_RESOURCE_MANAGER:
ResourceManagerOpen( p_access, i_session_id ); break;
case RI_APPLICATION_INFORMATION:
ApplicationInformationOpen( p_access, i_session_id ); break;
case RI_CONDITIONAL_ACCESS_SUPPORT:
ConditionalAccessOpen( p_access, i_session_id ); break;
case RI_DATE_TIME:
DateTimeOpen( p_access, i_session_id ); break;
case RI_MMI:
MMIOpen( p_access, i_session_id ); break;
case RI_HOST_CONTROL:
default:
msg_Err( p_access, "unknown resource id (0x%x)", i_resource_id );
p_sessions[i_session_id - 1].i_resource_id = 0;
}
}
#if 0
/* unused code for the moment - commented out to keep gcc happy */
/*****************************************************************************
* SessionCreate
*****************************************************************************/
static void SessionCreate( access_t * p_access, int i_slot, int i_resource_id )
{
uint8_t p_response[16];
uint8_t i_tag;
int i_session_id;
for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
{
if ( !p_sessions[i_session_id - 1].i_resource_id )
break;
}
if ( i_session_id == MAX_SESSIONS )
{
msg_Err( p_access, "too many sessions !" );
return;
}
p_sessions[i_session_id - 1].i_slot = i_slot;
p_sessions[i_session_id - 1].i_resource_id = i_resource_id;
p_sessions[i_session_id - 1].pf_close = NULL;
p_sessions[i_session_id - 1].pf_manage = NULL;
p_sessions[i_session_id - 1].p_sys = NULL;
p_response[0] = ST_CREATE_SESSION;
p_response[1] = 0x6;
p_response[2] = i_resource_id >> 24;
p_response[3] = (i_resource_id >> 16) & 0xff;
p_response[4] = (i_resource_id >> 8) & 0xff;
p_response[5] = i_resource_id & 0xff;
p_response[6] = i_session_id >> 8;
p_response[7] = i_session_id & 0xff;
if ( TPDUSend( p_access, i_slot, T_DATA_LAST, p_response, 4 ) !=
0 )
{
msg_Err( p_access,
"SessionCreate: couldn't send TPDU on slot %d", i_slot );
return;
}
if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) != 0 )
{
msg_Err( p_access,
"SessionCreate: couldn't recv TPDU on slot %d", i_slot );
return;
}
}
#endif
/*****************************************************************************
* SessionCreateResponse
*****************************************************************************/
static void SessionCreateResponse( access_t * p_access, uint8_t i_slot,
uint8_t *p_spdu, int i_size )
{
int i_status = p_spdu[2];
int i_resource_id = ResourceIdToInt( &p_spdu[3] );
int i_session_id = ((int)p_spdu[7] << 8) | p_spdu[8];
if ( i_status != SS_OK )
{
msg_Err( p_access, "SessionCreateResponse: failed to open session %d"
" resource=0x%x status=0x%x", i_session_id, i_resource_id,
i_status );
p_sessions[i_session_id - 1].i_resource_id = 0;
return;
}
switch ( i_resource_id )
{
case RI_RESOURCE_MANAGER:
ResourceManagerOpen( p_access, i_session_id ); break;
case RI_APPLICATION_INFORMATION:
ApplicationInformationOpen( p_access, i_session_id ); break;
case RI_CONDITIONAL_ACCESS_SUPPORT:
ConditionalAccessOpen( p_access, i_session_id ); break;
case RI_DATE_TIME:
DateTimeOpen( p_access, i_session_id ); break;
case RI_MMI:
MMIOpen( p_access, i_session_id ); break;
case RI_HOST_CONTROL:
default:
msg_Err( p_access, "unknown resource id (0x%x)", i_resource_id );
p_sessions[i_session_id - 1].i_resource_id = 0;
}
}
/*****************************************************************************
* SessionSendClose
*****************************************************************************/
static void SessionSendClose( access_t * p_access, int i_session_id )
{
uint8_t p_response[16];
uint8_t i_tag;
uint8_t i_slot = p_sessions[i_session_id - 1].i_slot;
p_response[0] = ST_CLOSE_SESSION_REQUEST;
p_response[1] = 0x2;
p_response[2] = i_session_id >> 8;
p_response[3] = i_session_id & 0xff;
if ( TPDUSend( p_access, i_slot, T_DATA_LAST, p_response, 4 ) !=
0 )
{
msg_Err( p_access,
"SessionSendClose: couldn't send TPDU on slot %d", i_slot );
return;
}
if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) != 0 )
{
msg_Err( p_access,
"SessionSendClose: couldn't recv TPDU on slot %d", i_slot );
return;
}
}
/*****************************************************************************
* SessionClose
*****************************************************************************/
static void SessionClose( access_t * p_access, int i_session_id )
{
uint8_t p_response[16];
uint8_t i_tag;
uint8_t i_slot = p_sessions[i_session_id - 1].i_slot;
if ( p_sessions[i_session_id - 1].pf_close != NULL )
p_sessions[i_session_id - 1].pf_close( p_access, i_session_id );
p_sessions[i_session_id - 1].i_resource_id = 0;
p_response[0] = ST_CLOSE_SESSION_RESPONSE;
p_response[1] = 0x3;
p_response[2] = SS_OK;
p_response[3] = i_session_id >> 8;
p_response[4] = i_session_id & 0xff;
if ( TPDUSend( p_access, i_slot, T_DATA_LAST, p_response, 5 ) !=
0 )
{
msg_Err( p_access,
"SessionClose: couldn't send TPDU on slot %d", i_slot );
return;
}
if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) != 0 )
{
msg_Err( p_access,
"SessionClose: couldn't recv TPDU on slot %d", i_slot );
return;
}
}
/*****************************************************************************
* SPDUHandle
*****************************************************************************/
static void SPDUHandle( access_t * p_access, uint8_t i_slot,
uint8_t *p_spdu, int i_size )
{
int i_session_id;
switch ( p_spdu[0] )
{
case ST_SESSION_NUMBER:
if ( i_size <= 4 )
return;
i_session_id = ((int)p_spdu[2] << 8) | p_spdu[3];
p_sessions[i_session_id - 1].pf_handle( p_access, i_session_id,
p_spdu + 4, i_size - 4 );
break;
case ST_OPEN_SESSION_REQUEST:
if ( i_size != 6 || p_spdu[1] != 0x4 )
return;
SessionOpen( p_access, i_slot, p_spdu, i_size );
break;
case ST_CREATE_SESSION_RESPONSE:
if ( i_size != 9 || p_spdu[1] != 0x7 )
return;
SessionCreateResponse( p_access, i_slot, p_spdu, i_size );
break;
case ST_CLOSE_SESSION_REQUEST:
if ( i_size != 4 || p_spdu[1] != 0x2 )
return;
i_session_id = ((int)p_spdu[2] << 8) | p_spdu[3];
SessionClose( p_access, i_session_id );
break;
case ST_CLOSE_SESSION_RESPONSE:
if ( i_size != 5 || p_spdu[1] != 0x3 )
return;
i_session_id = ((int)p_spdu[3] << 8) | p_spdu[4];
if ( p_spdu[2] )
{
msg_Err( p_access, "closing a session which is not allocated (%d)",
i_session_id );
}
else
{
if ( p_sessions[i_session_id - 1].pf_close != NULL )
p_sessions[i_session_id - 1].pf_close( p_access,
i_session_id );
p_sessions[i_session_id - 1].i_resource_id = 0;
}
break;
default:
msg_Err( p_access, "unexpected tag in SPDUHandle (%x)", p_spdu[0] );
break;
}
}
/*
* Application layer
*/
#define AOT_NONE 0x000000
#define AOT_PROFILE_ENQ 0x9F8010
#define AOT_PROFILE 0x9F8011
#define AOT_PROFILE_CHANGE 0x9F8012
#define AOT_APPLICATION_INFO_ENQ 0x9F8020
#define AOT_APPLICATION_INFO 0x9F8021
#define AOT_ENTER_MENU 0x9F8022
#define AOT_CA_INFO_ENQ 0x9F8030
#define AOT_CA_INFO 0x9F8031
#define AOT_CA_PMT 0x9F8032
#define AOT_CA_PMT_REPLY 0x9F8033
#define AOT_TUNE 0x9F8400
#define AOT_REPLACE 0x9F8401
#define AOT_CLEAR_REPLACE 0x9F8402
#define AOT_ASK_RELEASE 0x9F8403
#define AOT_DATE_TIME_ENQ 0x9F8440
#define AOT_DATE_TIME 0x9F8441
#define AOT_CLOSE_MMI 0x9F8800
#define AOT_DISPLAY_CONTROL 0x9F8801
#define AOT_DISPLAY_REPLY 0x9F8802
#define AOT_TEXT_LAST 0x9F8803
#define AOT_TEXT_MORE 0x9F8804
#define AOT_KEYPAD_CONTROL 0x9F8805
#define AOT_KEYPRESS 0x9F8806
#define AOT_ENQ 0x9F8807
#define AOT_ANSW 0x9F8808
#define AOT_MENU_LAST 0x9F8809
#define AOT_MENU_MORE 0x9F880A
#define AOT_MENU_ANSW 0x9F880B
#define AOT_LIST_LAST 0x9F880C
#define AOT_LIST_MORE 0x9F880D
#define AOT_SUBTITLE_SEGMENT_LAST 0x9F880E
#define AOT_SUBTITLE_SEGMENT_MORE 0x9F880F
#define AOT_DISPLAY_MESSAGE 0x9F8810
#define AOT_SCENE_END_MARK 0x9F8811
#define AOT_SCENE_DONE 0x9F8812
#define AOT_SCENE_CONTROL 0x9F8813
#define AOT_SUBTITLE_DOWNLOAD_LAST 0x9F8814
#define AOT_SUBTITLE_DOWNLOAD_MORE 0x9F8815
#define AOT_FLUSH_DOWNLOAD 0x9F8816
#define AOT_DOWNLOAD_REPLY 0x9F8817
#define AOT_COMMS_CMD 0x9F8C00
#define AOT_CONNECTION_DESCRIPTOR 0x9F8C01
#define AOT_COMMS_REPLY 0x9F8C02
#define AOT_COMMS_SEND_LAST 0x9F8C03
#define AOT_COMMS_SEND_MORE 0x9F8C04
#define AOT_COMMS_RCV_LAST 0x9F8C05
#define AOT_COMMS_RCV_MORE 0x9F8C06
/*****************************************************************************
* APDUGetTag
*****************************************************************************/
static int APDUGetTag( const uint8_t *p_apdu, int i_size )
{
if ( i_size >= 3 )
{
int i, t = 0;
for ( i = 0; i < 3; i++ )
t = (t << 8) | *p_apdu++;
return t;
}
return AOT_NONE;
}
/*****************************************************************************
* APDUGetLength
*****************************************************************************/
static uint8_t *APDUGetLength( uint8_t *p_apdu, int *pi_size )
{
return GetLength( &p_apdu[3], pi_size );
}
/*****************************************************************************
* APDUSend
*****************************************************************************/
static int APDUSend( access_t * p_access, int i_session_id, int i_tag,
uint8_t *p_data, int i_size )
{
uint8_t *p_apdu = malloc( i_size + 12 );
uint8_t *p = p_apdu;
ca_msg_t ca_msg;
int i_ret;
*p++ = (i_tag >> 16);
*p++ = (i_tag >> 8) & 0xff;
*p++ = i_tag & 0xff;
p = SetLength( p, i_size );
if ( i_size )
memcpy( p, p_data, i_size );
if ( i_ca_type == CA_CI_LINK )
{
i_ret = SPDUSend( p_access, i_session_id, p_apdu, i_size + p - p_apdu );
}
else
{
if ( i_size + p - p_apdu > 256 )
{
msg_Err( p_access, "CAM: apdu overflow" );
i_ret = -1;
}
else
{
char *psz_hex;
ca_msg.length = i_size + p - p_apdu;
if ( i_size == 0 ) ca_msg.length=3;
psz_hex = (char*)malloc( ca_msg.length*3 + 1);
memcpy( ca_msg.msg, p_apdu, i_size + p - p_apdu );
i_ret = ioctl(i_ca_handle, CA_SEND_MSG, &ca_msg );
if ( i_ret < 0 )
{
msg_Err( p_access, "Error sending to CAM: %m" );
i_ret = -1;
}
}
}
free( p_apdu );
return i_ret;
}
/*
* Resource Manager
*/
/*****************************************************************************
* ResourceManagerHandle
*****************************************************************************/
static void ResourceManagerHandle( access_t * p_access, int i_session_id,
uint8_t *p_apdu, int i_size )
{
int i_tag = APDUGetTag( p_apdu, i_size );
switch ( i_tag )
{
case AOT_PROFILE_ENQ:
{
int resources[] = { htonl(RI_RESOURCE_MANAGER),
htonl(RI_APPLICATION_INFORMATION),
htonl(RI_CONDITIONAL_ACCESS_SUPPORT),
htonl(RI_DATE_TIME),
htonl(RI_MMI)
};
APDUSend( p_access, i_session_id, AOT_PROFILE, (uint8_t*)resources,
sizeof(resources) );
break;
}
case AOT_PROFILE:
APDUSend( p_access, i_session_id, AOT_PROFILE_CHANGE, NULL, 0 );
break;
default:
msg_Err( p_access, "unexpected tag in ResourceManagerHandle (0x%x)",
i_tag );
}
}
/*****************************************************************************
* ResourceManagerOpen
*****************************************************************************/
static void ResourceManagerOpen( access_t * p_access, int i_session_id )
{
msg_Dbg( p_access, "opening ResourceManager session (%d)", i_session_id );
p_sessions[i_session_id - 1].pf_handle = ResourceManagerHandle;
APDUSend( p_access, i_session_id, AOT_PROFILE_ENQ, NULL, 0 );
}
/*
* Application Information
*/
/*****************************************************************************
* ApplicationInformationEnterMenu
*****************************************************************************/
static void ApplicationInformationEnterMenu( access_t * p_access,
int i_session_id )
{
int i_slot = p_sessions[i_session_id - 1].i_slot;
msg_Dbg( p_access, "entering MMI menus on session %d", i_session_id );
APDUSend( p_access, i_session_id, AOT_ENTER_MENU, NULL, 0 );
pb_slot_mmi_expected[i_slot] = 1;
}
/*****************************************************************************
* ApplicationInformationHandle
*****************************************************************************/
static void ApplicationInformationHandle( access_t * p_access, int i_session_id,
uint8_t *p_apdu, int i_size )
{
int i_tag = APDUGetTag( p_apdu, i_size );
switch ( i_tag )
{
case AOT_APPLICATION_INFO:
{
int i_type, i_manufacturer, i_code;
int l = 0;
uint8_t *d = APDUGetLength( p_apdu, &l );
if ( l < 4 ) break;
p_apdu[l + 4] = '\0';
i_type = *d++;
i_manufacturer = ((int)d[0] << 8) | d[1];
d += 2;
i_code = ((int)d[0] << 8) | d[1];
d += 2;
d = GetLength( d, &l );
d[l] = '\0';
msg_Info( p_access, "CAM: %s, %02X, %04X, %04X",
d, i_type, i_manufacturer, i_code );
break;
}
default:
msg_Err( p_access,
"unexpected tag in ApplicationInformationHandle (0x%x)",
i_tag );
}
}
/*****************************************************************************
* ApplicationInformationOpen
*****************************************************************************/
static void ApplicationInformationOpen( access_t * p_access, int i_session_id )
{
msg_Dbg( p_access, "opening ApplicationInformation session (%d)", i_session_id );
p_sessions[i_session_id - 1].pf_handle = ApplicationInformationHandle;
APDUSend( p_access, i_session_id, AOT_APPLICATION_INFO_ENQ, NULL, 0 );
}
/*
* Conditional Access
*/
#define MAX_CASYSTEM_IDS 16
typedef struct
{
uint16_t pi_system_ids[MAX_CASYSTEM_IDS + 1];
int i_selected_programs;
} system_ids_t;
static int CheckSystemID( system_ids_t *p_ids, uint16_t i_id )
{
int i = 0;
if( !p_ids ) return 1;
while ( p_ids->pi_system_ids[i] )
{
if ( p_ids->pi_system_ids[i] == i_id )
return 1;
i++;
}
return 0;
}
/*****************************************************************************
* CAPMTBuild
*****************************************************************************/
static int GetCADSize( system_ids_t *p_ids, dvbpsi_descriptor_t *p_dr )
{
int i_cad_size = 0;
while ( p_dr != NULL )
{
if( p_dr->i_tag == 0x9 )
{
uint16_t i_sysid = ((uint16_t)p_dr->p_data[0] << 8)
| p_dr->p_data[1];
if ( CheckSystemID( p_ids, i_sysid ) )
i_cad_size += p_dr->i_length + 2;
}
p_dr = p_dr->p_next;
}
return i_cad_size;
}
static uint8_t *CAPMTHeader( system_ids_t *p_ids, uint8_t i_list_mgt,
uint16_t i_program_number, uint8_t i_version,
int i_size, dvbpsi_descriptor_t *p_dr,
uint8_t i_cmd )
{
uint8_t *p_data;
if ( i_size )
p_data = malloc( 7 + i_size );
else
p_data = malloc( 6 );
p_data[0] = i_list_mgt;
p_data[1] = i_program_number >> 8;
p_data[2] = i_program_number & 0xff;
p_data[3] = ((i_version & 0x1f) << 1) | 0x1;
if ( i_size )
{
int i;
p_data[4] = (i_size + 1) >> 8;
p_data[5] = (i_size + 1) & 0xff;
p_data[6] = i_cmd;
i = 7;
while ( p_dr != NULL )
{
if( p_dr->i_tag == 0x9 )
{
uint16_t i_sysid = ((uint16_t)p_dr->p_data[0] << 8)
| p_dr->p_data[1];
if ( CheckSystemID( p_ids, i_sysid ) )
{
p_data[i] = 0x9;
p_data[i + 1] = p_dr->i_length;
memcpy( &p_data[i + 2], p_dr->p_data, p_dr->i_length );
// p_data[i+4] &= 0x1f;
i += p_dr->i_length + 2;
}
}
p_dr = p_dr->p_next;
}
}
else
{
p_data[4] = 0;
p_data[5] = 0;
}
return p_data;
}
static uint8_t *CAPMTES( system_ids_t *p_ids, uint8_t *p_capmt,
int i_capmt_size, uint8_t i_type, uint16_t i_pid,
int i_size, dvbpsi_descriptor_t *p_dr,
uint8_t i_cmd )
{
uint8_t *p_data;
int i;
if ( i_size )
p_data = realloc( p_capmt, i_capmt_size + 6 + i_size );
else
p_data = realloc( p_capmt, i_capmt_size + 5 );
i = i_capmt_size;
p_data[i] = i_type;
p_data[i + 1] = i_pid >> 8;
p_data[i + 2] = i_pid & 0xff;
if ( i_size )
{
p_data[i + 3] = (i_size + 1) >> 8;
p_data[i + 4] = (i_size + 1) & 0xff;
p_data[i + 5] = i_cmd;
i += 6;
while ( p_dr != NULL )
{
if( p_dr->i_tag == 0x9 )
{
uint16_t i_sysid = ((uint16_t)p_dr->p_data[0] << 8)
| p_dr->p_data[1];
if ( CheckSystemID( p_ids, i_sysid ) )
{
p_data[i] = 0x9;
p_data[i + 1] = p_dr->i_length;
memcpy( &p_data[i + 2], p_dr->p_data, p_dr->i_length );
i += p_dr->i_length + 2;
}
}
p_dr = p_dr->p_next;
}
}
else
{
p_data[i + 3] = 0;
p_data[i + 4] = 0;
}
return p_data;
}
static uint8_t *CAPMTBuild( access_t * p_access, int i_session_id,
dvbpsi_pmt_t *p_pmt, uint8_t i_list_mgt,
uint8_t i_cmd, int *pi_capmt_size )
{
system_ids_t *p_ids =
(system_ids_t *)p_sessions[i_session_id - 1].p_sys;
dvbpsi_pmt_es_t *p_es;
int i_cad_size, i_cad_program_size;
uint8_t *p_capmt;
i_cad_size = i_cad_program_size =
GetCADSize( p_ids, p_pmt->p_first_descriptor );
for( p_es = p_pmt->p_first_es; p_es != NULL; p_es = p_es->p_next )
{
i_cad_size += GetCADSize( p_ids, p_es->p_first_descriptor );
}
if ( !i_cad_size )
{
msg_Warn( p_access,
"no compatible scrambling system for SID %d on session %d",
p_pmt->i_program_number, i_session_id );
*pi_capmt_size = 0;
return NULL;
}
p_capmt = CAPMTHeader( p_ids, i_list_mgt, p_pmt->i_program_number,
p_pmt->i_version, i_cad_program_size,
p_pmt->p_first_descriptor, i_cmd );
if ( i_cad_program_size )
*pi_capmt_size = 7 + i_cad_program_size;
else
*pi_capmt_size = 6;
for( p_es = p_pmt->p_first_es; p_es != NULL; p_es = p_es->p_next )
{
if ( !PIDIsSelected( p_es->i_pid ) )
continue;
i_cad_size = GetCADSize( p_ids, p_es->p_first_descriptor );
if ( i_cad_size || i_cad_program_size )
{
p_capmt = CAPMTES( p_ids, p_capmt, *pi_capmt_size, p_es->i_type,
p_es->i_pid, i_cad_size,
p_es->p_first_descriptor, i_cmd );
if ( i_cad_size )
*pi_capmt_size += 6 + i_cad_size;
else
*pi_capmt_size += 5;
}
}
return p_capmt;
}
/*****************************************************************************
* CAPMTFirst
*****************************************************************************/
static void CAPMTFirst( access_t * p_access, int i_session_id,
dvbpsi_pmt_t *p_pmt )
{
uint8_t *p_capmt;
int i_capmt_size;
msg_Dbg( p_access, "adding first CAPMT for SID %d on session %d",
p_pmt->i_program_number, i_session_id );
p_capmt = CAPMTBuild( p_access, i_session_id, p_pmt,
0x3 /* only */, 0x1 /* ok_descrambling */,
&i_capmt_size );
if ( i_capmt_size )
APDUSend( p_access, i_session_id, AOT_CA_PMT, p_capmt, i_capmt_size );
}
/*****************************************************************************
* CAPMTAdd
*****************************************************************************/
static void CAPMTAdd( access_t * p_access, int i_session_id,
dvbpsi_pmt_t *p_pmt )
{
system_ids_t *p_ids =
(system_ids_t *)p_sessions[i_session_id - 1].p_sys;
uint8_t *p_capmt;
int i_capmt_size;
if( p_ids->i_selected_programs >= CAM_PROG_MAX )
{
msg_Warn( p_access, "Not adding CAPMT for SID %d, too many programs",
p_pmt->i_program_number );
return;
}
p_ids->i_selected_programs++;
if( p_ids->i_selected_programs == 1 )
{
CAPMTFirst( p_access, i_session_id, p_pmt );
return;
}
msg_Dbg( p_access, "adding CAPMT for SID %d on session %d",
p_pmt->i_program_number, i_session_id );
p_capmt = CAPMTBuild( p_access, i_session_id, p_pmt,
0x4 /* add */, 0x1 /* ok_descrambling */,
&i_capmt_size );
if ( i_capmt_size )
APDUSend( p_access, i_session_id, AOT_CA_PMT, p_capmt, i_capmt_size );
}
/*****************************************************************************
* CAPMTUpdate
*****************************************************************************/
static void CAPMTUpdate( access_t * p_access, int i_session_id,
dvbpsi_pmt_t *p_pmt )
{
uint8_t *p_capmt;
int i_capmt_size;
msg_Dbg( p_access, "updating CAPMT for SID %d on session %d",
p_pmt->i_program_number, i_session_id );
p_capmt = CAPMTBuild( p_access, i_session_id, p_pmt,
0x5 /* update */, 0x1 /* ok_descrambling */,
&i_capmt_size );
if ( i_capmt_size )
APDUSend( p_access, i_session_id, AOT_CA_PMT, p_capmt, i_capmt_size );
}
/*****************************************************************************
* CAPMTDelete
*****************************************************************************/
static void CAPMTDelete( access_t * p_access, int i_session_id,
dvbpsi_pmt_t *p_pmt )
{
system_ids_t *p_ids =
(system_ids_t *)p_sessions[i_session_id - 1].p_sys;
uint8_t *p_capmt;
int i_capmt_size;
p_ids->i_selected_programs--;
msg_Dbg( p_access, "deleting CAPMT for SID %d on session %d",
p_pmt->i_program_number, i_session_id );
p_capmt = CAPMTBuild( p_access, i_session_id, p_pmt,
0x5 /* update */, 0x4 /* not selected */,
&i_capmt_size );
if ( i_capmt_size )
APDUSend( p_access, i_session_id, AOT_CA_PMT, p_capmt, i_capmt_size );
}
/*****************************************************************************
* ConditionalAccessHandle
*****************************************************************************/
static void ConditionalAccessHandle( access_t * p_access, int i_session_id,
uint8_t *p_apdu, int i_size )
{
system_ids_t *p_ids =
(system_ids_t *)p_sessions[i_session_id - 1].p_sys;
int i_tag = APDUGetTag( p_apdu, i_size );
switch ( i_tag )
{
case AOT_CA_INFO:
{
int i;
int l = 0;
uint8_t *d = APDUGetLength( p_apdu, &l );
msg_Dbg( p_access, "CA system IDs supported by the application :" );
for ( i = 0; i < l / 2; i++ )
{
p_ids->pi_system_ids[i] = ((uint16_t)d[0] << 8) | d[1];
d += 2;
msg_Dbg( p_access, "- 0x%x", p_ids->pi_system_ids[i] );
}
p_ids->pi_system_ids[i] = 0;
demux_ResendCAPMTs();
break;
}
default:
msg_Err( p_access,
"unexpected tag in ConditionalAccessHandle (0x%x)",
i_tag );
}
}
/*****************************************************************************
* ConditionalAccessClose
*****************************************************************************/
static void ConditionalAccessClose( access_t * p_access, int i_session_id )
{
msg_Dbg( p_access, "closing ConditionalAccess session (%d)", i_session_id );
free( p_sessions[i_session_id - 1].p_sys );
}
/*****************************************************************************
* ConditionalAccessOpen
*****************************************************************************/
static void ConditionalAccessOpen( access_t * p_access, int i_session_id )
{
msg_Dbg( p_access, "opening ConditionalAccess session (%d)", i_session_id );
p_sessions[i_session_id - 1].pf_handle = ConditionalAccessHandle;
p_sessions[i_session_id - 1].pf_close = ConditionalAccessClose;
p_sessions[i_session_id - 1].p_sys = malloc(sizeof(system_ids_t));
memset( p_sessions[i_session_id - 1].p_sys, 0,
sizeof(system_ids_t) );
APDUSend( p_access, i_session_id, AOT_CA_INFO_ENQ, NULL, 0 );
}
/*
* Date Time
*/
typedef struct
{
int i_interval;
mtime_t i_last;
} date_time_t;
/*****************************************************************************
* DateTimeSend
*****************************************************************************/
static void DateTimeSend( access_t * p_access, int i_session_id )
{
date_time_t *p_date =
(date_time_t *)p_sessions[i_session_id - 1].p_sys;
time_t t = time(NULL);
struct tm tm_gmt;
struct tm tm_loc;
if ( gmtime_r(&t, &tm_gmt) && localtime_r(&t, &tm_loc) )
{
int Y = tm_gmt.tm_year;
int M = tm_gmt.tm_mon + 1;
int D = tm_gmt.tm_mday;
int L = (M == 1 || M == 2) ? 1 : 0;
int MJD = 14956 + D + (int)((Y - L) * 365.25)
+ (int)((M + 1 + L * 12) * 30.6001);
uint8_t p_response[7];
#define DEC2BCD(d) (((d / 10) << 4) + (d % 10))
p_response[0] = htons(MJD) >> 8;
p_response[1] = htons(MJD) & 0xff;
p_response[2] = DEC2BCD(tm_gmt.tm_hour);
p_response[3] = DEC2BCD(tm_gmt.tm_min);
p_response[4] = DEC2BCD(tm_gmt.tm_sec);
p_response[5] = htons(tm_loc.tm_gmtoff / 60) >> 8;
p_response[6] = htons(tm_loc.tm_gmtoff / 60) & 0xff;
APDUSend( p_access, i_session_id, AOT_DATE_TIME, p_response, 7 );
p_date->i_last = mdate();
}
}
/*****************************************************************************
* DateTimeHandle
*****************************************************************************/
static void DateTimeHandle( access_t * p_access, int i_session_id,
uint8_t *p_apdu, int i_size )
{
date_time_t *p_date =
(date_time_t *)p_sessions[i_session_id - 1].p_sys;
int i_tag = APDUGetTag( p_apdu, i_size );
switch ( i_tag )
{
case AOT_DATE_TIME_ENQ:
{
int l;
const uint8_t *d = APDUGetLength( p_apdu, &l );
if ( l > 0 )
{
p_date->i_interval = *d;
msg_Dbg( p_access, "DateTimeHandle : interval set to %d",
p_date->i_interval );
}
else
p_date->i_interval = 0;
DateTimeSend( p_access, i_session_id );
break;
}
default:
msg_Err( p_access, "unexpected tag in DateTimeHandle (0x%x)", i_tag );
}
}
/*****************************************************************************
* DateTimeManage
*****************************************************************************/
static void DateTimeManage( access_t * p_access, int i_session_id )
{
date_time_t *p_date =
(date_time_t *)p_sessions[i_session_id - 1].p_sys;
if ( p_date->i_interval
&& mdate() > p_date->i_last + (mtime_t)p_date->i_interval * 1000000 )
{
DateTimeSend( p_access, i_session_id );
}
}
/*****************************************************************************
* DateTimeClose
*****************************************************************************/
static void DateTimeClose( access_t * p_access, int i_session_id )
{
msg_Dbg( p_access, "closing DateTime session (%d)", i_session_id );
free( p_sessions[i_session_id - 1].p_sys );
}
/*****************************************************************************
* DateTimeOpen
*****************************************************************************/
static void DateTimeOpen( access_t * p_access, int i_session_id )
{
msg_Dbg( p_access, "opening DateTime session (%d)", i_session_id );
p_sessions[i_session_id - 1].pf_handle = DateTimeHandle;
p_sessions[i_session_id - 1].pf_manage = DateTimeManage;
p_sessions[i_session_id - 1].pf_close = DateTimeClose;
p_sessions[i_session_id - 1].p_sys = malloc(sizeof(date_time_t));
memset( p_sessions[i_session_id - 1].p_sys, 0, sizeof(date_time_t) );
DateTimeSend( p_access, i_session_id );
}
/*
* MMI
*/
/* Display Control Commands */
#define DCC_SET_MMI_MODE 0x01
#define DCC_DISPLAY_CHARACTER_TABLE_LIST 0x02
#define DCC_INPUT_CHARACTER_TABLE_LIST 0x03
#define DCC_OVERLAY_GRAPHICS_CHARACTERISTICS 0x04
#define DCC_FULL_SCREEN_GRAPHICS_CHARACTERISTICS 0x05
/* MMI Modes */
#define MM_HIGH_LEVEL 0x01
#define MM_LOW_LEVEL_OVERLAY_GRAPHICS 0x02
#define MM_LOW_LEVEL_FULL_SCREEN_GRAPHICS 0x03
/* Display Reply IDs */
#define DRI_MMI_MODE_ACK 0x01
#define DRI_LIST_DISPLAY_CHARACTER_TABLES 0x02
#define DRI_LIST_INPUT_CHARACTER_TABLES 0x03
#define DRI_LIST_GRAPHIC_OVERLAY_CHARACTERISTICS 0x04
#define DRI_LIST_FULL_SCREEN_GRAPHIC_CHARACTERISTICS 0x05
#define DRI_UNKNOWN_DISPLAY_CONTROL_CMD 0xF0
#define DRI_UNKNOWN_MMI_MODE 0xF1
#define DRI_UNKNOWN_CHARACTER_TABLE 0xF2
/* Enquiry Flags */
#define EF_BLIND 0x01
/* Answer IDs */
#define AI_CANCEL 0x00
#define AI_ANSWER 0x01
typedef struct
{
en50221_mmi_object_t last_object;
} mmi_t;
static inline void en50221_MMIFree( en50221_mmi_object_t *p_object )
{
int i;
switch ( p_object->i_object_type )
{
case EN50221_MMI_ENQ:
free( p_object->u.enq.psz_text );
break;
case EN50221_MMI_ANSW:
if ( p_object->u.answ.b_ok )
{
free( p_object->u.answ.psz_answ );
}
break;
case EN50221_MMI_MENU:
case EN50221_MMI_LIST:
free( p_object->u.menu.psz_title );
free( p_object->u.menu.psz_subtitle );
free( p_object->u.menu.psz_bottom );
for ( i = 0; i < p_object->u.menu.i_choices; i++ )
{
free( p_object->u.menu.ppsz_choices[i] );
}
free( p_object->u.menu.ppsz_choices );
break;
default:
break;
}
}
/*****************************************************************************
* MMISendObject
*****************************************************************************/
static void MMISendObject( access_t *p_access, int i_session_id,
en50221_mmi_object_t *p_object )
{
int i_slot = p_sessions[i_session_id - 1].i_slot;
uint8_t *p_data;
int i_size, i_tag;
switch ( p_object->i_object_type )
{
case EN50221_MMI_ANSW:
i_tag = AOT_ANSW;
i_size = 1 + strlen( p_object->u.answ.psz_answ );
p_data = malloc( i_size );
p_data[0] = (p_object->u.answ.b_ok == 1) ? 0x1 : 0x0;
strncpy( (char *)&p_data[1], p_object->u.answ.psz_answ, i_size - 1 );
break;
case EN50221_MMI_MENU_ANSW:
i_tag = AOT_MENU_ANSW;
i_size = 1;
p_data = malloc( i_size );
p_data[0] = p_object->u.menu_answ.i_choice;
break;
default:
msg_Err( p_access, "unknown MMI object %d", p_object->i_object_type );
return;
}
APDUSend( p_access, i_session_id, i_tag, p_data, i_size );
free( p_data );
pb_slot_mmi_expected[i_slot] = 1;
}
/*****************************************************************************
* MMISendClose
*****************************************************************************/
static void MMISendClose( access_t *p_access, int i_session_id )
{
int i_slot = p_sessions[i_session_id - 1].i_slot;
APDUSend( p_access, i_session_id, AOT_CLOSE_MMI, NULL, 0 );
pb_slot_mmi_expected[i_slot] = 1;
}
/*****************************************************************************
* MMIDisplayReply
*****************************************************************************/
static void MMIDisplayReply( access_t *p_access, int i_session_id )
{
uint8_t p_response[2];
p_response[0] = DRI_MMI_MODE_ACK;
p_response[1] = MM_HIGH_LEVEL;
APDUSend( p_access, i_session_id, AOT_DISPLAY_REPLY, p_response, 2 );
msg_Dbg( p_access, "sending DisplayReply on session (%d)", i_session_id );
}
/*****************************************************************************
* MMIGetText
*****************************************************************************/
static char *MMIGetText( access_t *p_access, uint8_t **pp_apdu, int *pi_size )
{
int i_tag = APDUGetTag( *pp_apdu, *pi_size );
char *psz_text;
int l;
uint8_t *d;
if ( i_tag != AOT_TEXT_LAST )
{
msg_Err( p_access, "unexpected text tag: %06x", i_tag );
*pi_size = 0;
return strdup( "" );
}
d = APDUGetLength( *pp_apdu, &l );
psz_text = malloc( l + 1 );
strncpy( psz_text, (char *)d, l );
psz_text[l] = '\0';
*pp_apdu += l + 4;
*pi_size -= l + 4;
return psz_text;
}
/*****************************************************************************
* MMIHandleEnq
*****************************************************************************/
static void MMIHandleEnq( access_t *p_access, int i_session_id,
uint8_t *p_apdu, int i_size )
{
mmi_t *p_mmi = (mmi_t *)p_sessions[i_session_id - 1].p_sys;
int i_slot = p_sessions[i_session_id - 1].i_slot;
int l;
uint8_t *d = APDUGetLength( p_apdu, &l );
en50221_MMIFree( &p_mmi->last_object );
p_mmi->last_object.i_object_type = EN50221_MMI_ENQ;
p_mmi->last_object.u.enq.b_blind = (*d & 0x1) ? 1 : 0;
d += 2; /* skip answer_text_length because it is not mandatory */
l -= 2;
p_mmi->last_object.u.enq.psz_text = malloc( l + 1 );
strncpy( p_mmi->last_object.u.enq.psz_text, (char *)d, l );
p_mmi->last_object.u.enq.psz_text[l] = '\0';
msg_Dbg( p_access, "MMI enq: %s%s", p_mmi->last_object.u.enq.psz_text,
p_mmi->last_object.u.enq.b_blind == 1 ? " (blind)" : "" );
pb_slot_mmi_expected[i_slot] = 0;
pb_slot_mmi_undisplayed[i_slot] = 1;
}
/*****************************************************************************
* MMIHandleMenu
*****************************************************************************/
static void MMIHandleMenu( access_t *p_access, int i_session_id, int i_tag,
uint8_t *p_apdu, int i_size )
{
mmi_t *p_mmi = (mmi_t *)p_sessions[i_session_id - 1].p_sys;
int i_slot = p_sessions[i_session_id - 1].i_slot;
int l;
uint8_t *d = APDUGetLength( p_apdu, &l );
en50221_MMIFree( &p_mmi->last_object );
p_mmi->last_object.i_object_type = (i_tag == AOT_MENU_LAST) ?
EN50221_MMI_MENU : EN50221_MMI_LIST;
p_mmi->last_object.u.menu.i_choices = 0;
p_mmi->last_object.u.menu.ppsz_choices = NULL;
if ( l > 0 )
{
l--; d++; /* choice_nb */
#define GET_FIELD( x ) \
if ( l > 0 ) \
{ \
p_mmi->last_object.u.menu.psz_##x \
= MMIGetText( p_access, &d, &l ); \
msg_Dbg( p_access, "MMI " STRINGIFY( x ) ": %s", \
p_mmi->last_object.u.menu.psz_##x ); \
}
GET_FIELD( title );
GET_FIELD( subtitle );
GET_FIELD( bottom );
#undef GET_FIELD
while ( l > 0 )
{
char *psz_text = MMIGetText( p_access, &d, &l );
TAB_APPEND( p_mmi->last_object.u.menu.i_choices,
p_mmi->last_object.u.menu.ppsz_choices,
psz_text );
msg_Dbg( p_access, "MMI choice: %s", psz_text );
}
}
pb_slot_mmi_expected[i_slot] = 0;
pb_slot_mmi_undisplayed[i_slot] = 1;
}
/*****************************************************************************
* MMIHandle
*****************************************************************************/
static void MMIHandle( access_t *p_access, int i_session_id,
uint8_t *p_apdu, int i_size )
{
int i_tag = APDUGetTag( p_apdu, i_size );
switch ( i_tag )
{
case AOT_DISPLAY_CONTROL:
{
int l;
uint8_t *d = APDUGetLength( p_apdu, &l );
if ( l > 0 )
{
switch ( *d )
{
case DCC_SET_MMI_MODE:
if ( l == 2 && d[1] == MM_HIGH_LEVEL )
MMIDisplayReply( p_access, i_session_id );
else
msg_Err( p_access, "unsupported MMI mode %02x", d[1] );
break;
default:
msg_Err( p_access, "unsupported display control command %02x",
*d );
break;
}
}
break;
}
case AOT_ENQ:
MMIHandleEnq( p_access, i_session_id, p_apdu, i_size );
break;
case AOT_LIST_LAST:
case AOT_MENU_LAST:
MMIHandleMenu( p_access, i_session_id, i_tag, p_apdu, i_size );
break;
case AOT_CLOSE_MMI:
SessionSendClose( p_access, i_session_id );
break;
default:
msg_Err( p_access, "unexpected tag in MMIHandle (0x%x)", i_tag );
}
}
/*****************************************************************************
* MMIClose
*****************************************************************************/
static void MMIClose( access_t *p_access, int i_session_id )
{
int i_slot = p_sessions[i_session_id - 1].i_slot;
mmi_t *p_mmi = (mmi_t *)p_sessions[i_session_id - 1].p_sys;
en50221_MMIFree( &p_mmi->last_object );
free( p_sessions[i_session_id - 1].p_sys );
msg_Dbg( p_access, "closing MMI session (%d)", i_session_id );
pb_slot_mmi_expected[i_slot] = 0;
pb_slot_mmi_undisplayed[i_slot] = 1;
}
/*****************************************************************************
* MMIOpen
*****************************************************************************/
static void MMIOpen( access_t *p_access, int i_session_id )
{
mmi_t *p_mmi;
msg_Dbg( p_access, "opening MMI session (%d)", i_session_id );
p_sessions[i_session_id - 1].pf_handle = MMIHandle;
p_sessions[i_session_id - 1].pf_close = MMIClose;
p_sessions[i_session_id - 1].p_sys = malloc(sizeof(mmi_t));
p_mmi = (mmi_t *)p_sessions[i_session_id - 1].p_sys;
p_mmi->last_object.i_object_type = EN50221_MMI_NONE;
}
/*
* Hardware handling
*/
/*****************************************************************************
* InitSlot: Open the transport layer
*****************************************************************************/
#define MAX_TC_RETRIES 20
static int InitSlot( access_t * p_access, int i_slot )
{
int i;
if ( TPDUSend( p_access, i_slot, T_CREATE_TC, NULL, 0 )
!= 0 )
{
msg_Err( p_access, "en50221_Init: couldn't send TPDU on slot %d",
i_slot );
return -1;
}
/* This is out of the spec */
for ( i = 0; i < MAX_TC_RETRIES; i++ )
{
uint8_t i_tag;
if ( TPDURecv( p_access, i_slot, &i_tag, NULL, NULL ) == 0
&& i_tag == T_CTC_REPLY )
{
pb_active_slot[i_slot] = 1;
break;
}
if ( TPDUSend( p_access, i_slot, T_CREATE_TC, NULL, 0 )
!= 0 )
{
msg_Err( p_access,
"en50221_Init: couldn't send TPDU on slot %d",
i_slot );
continue;
}
}
if ( pb_active_slot[i_slot] )
{
i_ca_timeout = 100000;
return 0;
}
return -1;
}
/*****************************************************************************
* ResetSlot
*****************************************************************************/
static void ResetSlot( int i_slot )
{
int i_session_id;
if ( ioctl( i_ca_handle, CA_RESET, 1 << i_slot ) != 0 )
msg_Err( NULL, "en50221_Poll: couldn't reset slot %d", i_slot );
pb_active_slot[i_slot] = 0;
pb_tc_has_data[i_slot] = 0;
/* Close all sessions for this slot. */
for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
{
if ( p_sessions[i_session_id - 1].i_resource_id
&& p_sessions[i_session_id - 1].i_slot == i_slot )
{
if ( p_sessions[i_session_id - 1].pf_close != NULL )
{
p_sessions[i_session_id - 1].pf_close( NULL, i_session_id );
}
p_sessions[i_session_id - 1].i_resource_id = 0;
}
}
i_ca_timeout = 100000;
}
/*
* External entry points
*/
/*****************************************************************************
* en50221_Init : Initialize the CAM for en50221
*****************************************************************************/
void en50221_Init( void )
{
char psz_tmp[128];
ca_caps_t caps;
memset( &caps, 0, sizeof( ca_caps_t ));
sprintf( psz_tmp, "/dev/dvb/adapter%d/ca0", i_adapter );
if( (i_ca_handle = open(psz_tmp, O_RDWR | O_NONBLOCK)) < 0 )
{
msg_Warn( NULL, "failed opening CAM device %s (%s)",
psz_tmp, strerror(errno) );
i_ca_handle = 0;
return;
}
if ( ioctl( i_ca_handle, CA_GET_CAP, &caps ) != 0 )
{
msg_Err( NULL, "failed getting CAM capabilities (%s)",
strerror(errno) );
close( i_ca_handle );
i_ca_handle = 0;
return;
}
/* Output CA capabilities */
msg_Dbg( NULL, "CA interface with %d %s", caps.slot_num,
caps.slot_num == 1 ? "slot" : "slots" );
if ( caps.slot_type & CA_CI )
msg_Dbg( NULL, " CI high level interface type" );
if ( caps.slot_type & CA_CI_LINK )
msg_Dbg( NULL, " CI link layer level interface type" );
if ( caps.slot_type & CA_CI_PHYS )
msg_Dbg( NULL, " CI physical layer level interface type (not supported) " );
if ( caps.slot_type & CA_DESCR )
msg_Dbg( NULL, " built-in descrambler detected" );
if ( caps.slot_type & CA_SC )
msg_Dbg( NULL, " simple smart card interface" );
msg_Dbg( NULL, " %d available %s", caps.descr_num,
caps.descr_num == 1 ? "descrambler (key)" : "descramblers (keys)" );
if ( caps.descr_type & CA_ECD )
msg_Dbg( NULL, " ECD scrambling system supported" );
if ( caps.descr_type & CA_NDS )
msg_Dbg( NULL, " NDS scrambling system supported" );
if ( caps.descr_type & CA_DSS )
msg_Dbg( NULL, " DSS scrambling system supported" );
if ( caps.slot_num == 0 )
{
msg_Err( NULL, "CAM module with no slots" );
close( i_ca_handle );
i_ca_handle = 0;
return;
}
if( caps.slot_type & CA_CI_LINK )
i_ca_type = CA_CI_LINK;
else if( caps.slot_type & CA_CI )
i_ca_type = CA_CI;
else
{
msg_Err( NULL, "Incompatible CAM interface" );
close( i_ca_handle );
i_ca_handle = 0;
return;
}
i_nb_slots = caps.slot_num;
memset( p_sessions, 0, sizeof(en50221_session_t) * MAX_SESSIONS );
en50221_Reset();
}
/*****************************************************************************
* en50221_Reset : Reset the CAM for en50221
*****************************************************************************/
void en50221_Reset( void )
{
memset( pb_active_slot, 0, sizeof(int) * MAX_CI_SLOTS );
memset( pb_tc_has_data, 0, sizeof(int) * MAX_CI_SLOTS );
memset( pb_slot_mmi_expected, 0, sizeof(int) * MAX_CI_SLOTS );
memset( pb_slot_mmi_undisplayed, 0, sizeof(int) * MAX_CI_SLOTS );
if( i_ca_type & CA_CI_LINK )
{
int i_slot;
for ( i_slot = 0; i_slot < i_nb_slots; i_slot++ )
ResetSlot( i_slot );
}
else
{
struct ca_slot_info info;
info.num = 0;
/* We don't reset the CAM in that case because it's done by the
* ASIC. */
if ( ioctl( i_ca_handle, CA_GET_SLOT_INFO, &info ) < 0 )
{
msg_Err( NULL, "en50221_Init: couldn't get slot info" );
close( i_ca_handle );
i_ca_handle = 0;
return;
}
if( info.flags == 0 )
{
msg_Err( NULL, "en50221_Init: no CAM inserted" );
close( i_ca_handle );
i_ca_handle = 0;
return;
}
/* Allocate a dummy sessions */
p_sessions[ 0 ].i_resource_id = RI_CONDITIONAL_ACCESS_SUPPORT;
/* Get application info to find out which cam we are using and make
sure everything is ready to play */
ca_msg_t ca_msg;
ca_msg.length=3;
ca_msg.msg[0] = ( AOT_APPLICATION_INFO & 0xFF0000 ) >> 16;
ca_msg.msg[1] = ( AOT_APPLICATION_INFO & 0x00FF00 ) >> 8;
ca_msg.msg[2] = ( AOT_APPLICATION_INFO & 0x0000FF ) >> 0;
memset( &ca_msg.msg[3], 0, 253 );
APDUSend( NULL, 1, AOT_APPLICATION_INFO_ENQ, NULL, 0 );
if ( ioctl( i_ca_handle, CA_GET_MSG, &ca_msg ) < 0 )
{
msg_Err( NULL, "en50221_Init: failed getting message" );
close( i_ca_handle );
i_ca_handle = 0;
return;
}
#if HLCI_WAIT_CAM_READY
while( ca_msg.msg[8] == 0xff && ca_msg.msg[9] == 0xff )
{
msleep(1);
msg_Dbg( NULL, "CAM: please wait" );
APDUSend( NULL, 1, AOT_APPLICATION_INFO_ENQ, NULL, 0 );
ca_msg.length=3;
ca_msg.msg[0] = ( AOT_APPLICATION_INFO & 0xFF0000 ) >> 16;
ca_msg.msg[1] = ( AOT_APPLICATION_INFO & 0x00FF00 ) >> 8;
ca_msg.msg[2] = ( AOT_APPLICATION_INFO & 0x0000FF ) >> 0;
memset( &ca_msg.msg[3], 0, 253 );
if ( ioctl( i_ca_handle, CA_GET_MSG, &ca_msg ) < 0 )
{
msg_Err( NULL, "en50221_Init: failed getting message" );
close( i_ca_handle );
i_ca_handle = 0;
return;
}
msg_Dbg( NULL, "en50221_Init: Got length: %d, tag: 0x%x", ca_msg.length, APDUGetTag( ca_msg.msg, ca_msg.length ) );
}
#else
if( ca_msg.msg[8] == 0xff && ca_msg.msg[9] == 0xff )
{
msg_Err( NULL, "CAM returns garbage as application info!" );
close( i_ca_handle );
i_ca_handle = 0;
return;
}
#endif
msg_Dbg( NULL, "found CAM %s using id 0x%x", &ca_msg.msg[12],
(ca_msg.msg[8]<<8)|ca_msg.msg[9] );
}
}
/*****************************************************************************
* en50221_Poll : Poll the CAM for TPDUs
*****************************************************************************/
void en50221_Poll( void )
{
int i_slot;
int i_session_id;
for ( i_slot = 0; i_slot < i_nb_slots; i_slot++ )
{
uint8_t i_tag;
ca_slot_info_t sinfo;
sinfo.num = i_slot;
if ( ioctl( i_ca_handle, CA_GET_SLOT_INFO, &sinfo ) != 0 )
{
msg_Err( NULL, "en50221_Poll: couldn't get info on slot %d",
i_slot );
continue;
}
if ( !(sinfo.flags & CA_CI_MODULE_READY) )
{
if ( pb_active_slot[i_slot] )
{
msg_Dbg( NULL, "en50221_Poll: slot %d has been removed",
i_slot );
pb_active_slot[i_slot] = 0;
pb_slot_mmi_expected[i_slot] = 0;
pb_slot_mmi_undisplayed[i_slot] = 0;
/* Close all sessions for this slot. */
for ( i_session_id = 1; i_session_id <= MAX_SESSIONS;
i_session_id++ )
{
if ( p_sessions[i_session_id - 1].i_resource_id
&& p_sessions[i_session_id - 1].i_slot
== i_slot )
{
if ( p_sessions[i_session_id - 1].pf_close
!= NULL )
{
p_sessions[i_session_id - 1].pf_close(
NULL, i_session_id );
}
p_sessions[i_session_id - 1].i_resource_id = 0;
}
}
}
continue;
}
else if ( !pb_active_slot[i_slot] )
{
InitSlot( NULL, i_slot );
if ( !pb_active_slot[i_slot] )
{
msg_Dbg( NULL, "en50221_Poll: resetting slot %d", i_slot );
ResetSlot( i_slot );
continue;
}
msg_Dbg( NULL, "en50221_Poll: slot %d is active",
i_slot );
}
if ( !pb_tc_has_data[i_slot] )
{
if ( TPDUSend( NULL, i_slot, T_DATA_LAST, NULL, 0 ) != 0 )
{
msg_Err( NULL,
"en50221_Poll: couldn't send TPDU on slot %d, resetting",
i_slot );
ResetSlot( i_slot );
continue;
}
if ( TPDURecv( NULL, i_slot, &i_tag, NULL, NULL ) != 0 )
{
msg_Err( NULL,
"en50221_Poll: couldn't recv TPDU on slot %d, resetting",
i_slot );
ResetSlot( i_slot );
continue;
}
}
while ( pb_tc_has_data[i_slot] )
{
uint8_t p_tpdu[MAX_TPDU_SIZE];
int i_size, i_session_size;
uint8_t *p_session;
if ( TPDUSend( NULL, i_slot, T_RCV, NULL, 0 ) != 0 )
{
msg_Err( NULL,
"en50221_Poll: couldn't send TPDU on slot %d, resetting",
i_slot );
ResetSlot( i_slot );
continue;
}
if ( TPDURecv( NULL, i_slot, &i_tag, p_tpdu, &i_size ) != 0 )
{
msg_Err( NULL,
"en50221_Poll: couldn't recv TPDU on slot %d, resetting",
i_slot );
ResetSlot( i_slot );
continue;
}
p_session = GetLength( &p_tpdu[3], &i_session_size );
if ( i_session_size <= 1 )
continue;
p_session++;
i_session_size--;
if ( i_tag != T_DATA_LAST )
{
/* I sometimes see a CAM responding T_SB to our T_RCV.
* It said it had data to send, but does not send it after
* our T_RCV. There is probably something wrong here. I
* experienced that this case happens on start-up, and the
* CAM doesn't open any session at all, so it is quite
* useless. Reset it. */
msg_Err( NULL,
"en50221_Poll: invalid TPDU 0x%x, resetting", i_tag );
ResetSlot( i_slot );
break;
}
SPDUHandle( NULL, i_slot, p_session, i_session_size );
}
}
for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
{
if ( p_sessions[i_session_id - 1].i_resource_id
&& p_sessions[i_session_id - 1].pf_manage )
{
p_sessions[i_session_id - 1].pf_manage( NULL, i_session_id );
}
}
}
/*****************************************************************************
* en50221_AddPMT :
*****************************************************************************/
void en50221_AddPMT( dvbpsi_pmt_t *p_pmt )
{
int i_session_id;
for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
if ( p_sessions[i_session_id - 1].i_resource_id
== RI_CONDITIONAL_ACCESS_SUPPORT )
CAPMTAdd( NULL, i_session_id, p_pmt );
}
/*****************************************************************************
* en50221_UpdatePMT :
*****************************************************************************/
void en50221_UpdatePMT( dvbpsi_pmt_t *p_pmt )
{
int i_session_id;
for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
if ( p_sessions[i_session_id - 1].i_resource_id
== RI_CONDITIONAL_ACCESS_SUPPORT )
CAPMTUpdate( NULL, i_session_id, p_pmt );
}
/*****************************************************************************
* en50221_DeletePMT :
*****************************************************************************/
void en50221_DeletePMT( dvbpsi_pmt_t *p_pmt )
{
int i_session_id;
for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
if ( p_sessions[i_session_id - 1].i_resource_id
== RI_CONDITIONAL_ACCESS_SUPPORT )
CAPMTDelete( NULL, i_session_id, p_pmt );
}
/*****************************************************************************
* en50221_StatusMMI :
*****************************************************************************/
uint8_t en50221_StatusMMI( uint8_t *p_answer, ssize_t *pi_size )
{
struct ret_mmi_status *p_ret = (struct ret_mmi_status *)p_answer;
if ( ioctl( i_ca_handle, CA_GET_CAP, &p_ret->caps ) != 0 )
{
msg_Err( NULL, "ioctl CA_GET_CAP failed (%s)", strerror(errno) );
return RET_ERR;
}
*pi_size = sizeof(struct ret_mmi_status);
return RET_MMI_STATUS;
}
/*****************************************************************************
* en50221_StatusMMISlot :
*****************************************************************************/
uint8_t en50221_StatusMMISlot( uint8_t *p_buffer, ssize_t i_size,
uint8_t *p_answer, ssize_t *pi_size )
{
int i_slot;
struct ret_mmi_slot_status *p_ret = (struct ret_mmi_slot_status *)p_answer;
if ( i_size != 1 ) return RET_HUH;
i_slot = *p_buffer;
p_ret->sinfo.num = i_slot;
if ( ioctl( i_ca_handle, CA_GET_SLOT_INFO, &p_ret->sinfo ) != 0 )
{
msg_Err( NULL, "ioctl CA_GET_SLOT_INFO failed (%s)", strerror(errno) );
return RET_ERR;
}
*pi_size = sizeof(struct ret_mmi_slot_status);
return RET_MMI_SLOT_STATUS;
}
/*****************************************************************************
* en50221_OpenMMI :
*****************************************************************************/
uint8_t en50221_OpenMMI( uint8_t *p_buffer, ssize_t i_size )
{
int i_slot;
if ( i_size != 1 ) return RET_HUH;
i_slot = *p_buffer;
if( i_ca_type & CA_CI_LINK )
{
int i_session_id;
for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
{
if ( p_sessions[i_session_id - 1].i_resource_id == RI_MMI
&& p_sessions[i_session_id - 1].i_slot == i_slot )
{
msg_Dbg( NULL,
"MMI menu is already opened on slot %d (session=%d)",
i_slot, i_session_id );
return RET_OK;
}
}
for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
{
if ( p_sessions[i_session_id - 1].i_resource_id
== RI_APPLICATION_INFORMATION
&& p_sessions[i_session_id - 1].i_slot == i_slot )
{
ApplicationInformationEnterMenu( NULL, i_session_id );
return RET_OK;
}
}
msg_Err( NULL, "no application information on slot %d", i_slot );
return RET_ERR;
}
else
{
msg_Err( NULL, "MMI menu not supported" );
return RET_ERR;
}
}
/*****************************************************************************
* en50221_CloseMMI :
*****************************************************************************/
uint8_t en50221_CloseMMI( uint8_t *p_buffer, ssize_t i_size )
{
int i_slot;
if ( i_size != 1 ) return RET_HUH;
i_slot = *p_buffer;
if( i_ca_type & CA_CI_LINK )
{
int i_session_id;
for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
{
if ( p_sessions[i_session_id - 1].i_resource_id == RI_MMI
&& p_sessions[i_session_id - 1].i_slot == i_slot )
{
MMISendClose( NULL, i_session_id );
return RET_OK;
}
}
msg_Warn( NULL, "closing a non-existing MMI session on slot %d",
i_slot );
return RET_ERR;
}
else
{
msg_Err( NULL, "MMI menu not supported" );
return RET_ERR;
}
}
/*****************************************************************************
* en50221_GetMMIObject :
*****************************************************************************/
uint8_t en50221_GetMMIObject( uint8_t *p_buffer, ssize_t i_size,
uint8_t *p_answer, ssize_t *pi_size )
{
int i_session_id, i_slot;
struct ret_mmi_recv *p_ret = (struct ret_mmi_recv *)p_answer;
if ( i_size != 1 ) return RET_HUH;
i_slot = *p_buffer;
if ( pb_slot_mmi_expected[i_slot] == 1 )
return RET_ERR; /* should not happen */
p_ret->object.i_object_type = EN50221_MMI_NONE;
*pi_size = sizeof(struct ret_mmi_recv);
for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
{
if ( p_sessions[i_session_id - 1].i_resource_id == RI_MMI
&& p_sessions[i_session_id - 1].i_slot == i_slot )
{
mmi_t *p_mmi =
(mmi_t *)p_sessions[i_session_id - 1].p_sys;
if ( p_mmi == NULL )
{
*pi_size = 0;
return RET_ERR; /* should not happen */
}
*pi_size = COMM_BUFFER_SIZE - COMM_HEADER_SIZE -
((void *)&p_ret->object - (void *)p_ret);
if ( en50221_SerializeMMIObject( (uint8_t *)&p_ret->object,
pi_size, &p_mmi->last_object ) == -1 )
{
*pi_size = 0;
msg_Err( NULL, "MMI structure too big" );
return RET_ERR;
}
*pi_size += ((void *)&p_ret->object - (void *)p_ret);
break;
}
}
return RET_MMI_RECV;
}
/*****************************************************************************
* en50221_SendMMIObject :
*****************************************************************************/
uint8_t en50221_SendMMIObject( uint8_t *p_buffer, ssize_t i_size )
{
int i_session_id, i_slot;
struct cmd_mmi_send *p_cmd = (struct cmd_mmi_send *)p_buffer;
if ( en50221_UnserializeMMIObject( &p_cmd->object, i_size -
((void *)&p_cmd->object - (void *)p_cmd) ) == -1 )
return RET_ERR;
i_slot = p_cmd->i_slot;
for ( i_session_id = 1; i_session_id <= MAX_SESSIONS; i_session_id++ )
{
if ( p_sessions[i_session_id - 1].i_resource_id == RI_MMI
&& p_sessions[i_session_id - 1].i_slot == i_slot )
{
MMISendObject( NULL, i_session_id, &p_cmd->object );
return RET_OK;
}
}
msg_Err( NULL, "SendMMIObject when no MMI session is opened !" );
return RET_ERR;
}
/*****************************************************************************
* en50221.h
*****************************************************************************
* Copyright (C) 2008 the VideoLAN team
* $Id: dvblast.h 3 2005-10-05 12:15:55Z cmassiot $
*
* Authors: Christophe Massiot <massiot@via.ecp.fr>
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include <malloc.h>
typedef void * access_t;
#define STRINGIFY( z ) UGLY_KLUDGE( z )
#define UGLY_KLUDGE( z ) #z
#define EN50221_MMI_NONE 0
#define EN50221_MMI_ENQ 1
#define EN50221_MMI_ANSW 2
#define EN50221_MMI_MENU 3
#define EN50221_MMI_MENU_ANSW 4
#define EN50221_MMI_LIST 5
typedef struct en50221_mmi_object_t
{
int i_object_type;
union
{
struct
{
int b_blind;
char *psz_text;
} enq;
struct
{
int b_ok;
char *psz_answ;
} answ;
struct
{
char *psz_title, *psz_subtitle, *psz_bottom;
char **ppsz_choices;
int i_choices;
} menu; /* menu and list are the same */
struct
{
int i_choice;
} menu_answ;
} u;
} en50221_mmi_object_t;
#define MAX_CI_SLOTS 16
#define MAX_SESSIONS 32
#define MAX_PROGRAMS 24
extern int i_ca_handle;
extern int i_ca_type;
/*****************************************************************************
* Prototypes
*****************************************************************************/
void en50221_Init( void );
void en50221_Reset( void );
void en50221_Poll( void );
void en50221_AddPMT( dvbpsi_pmt_t *p_pmt );
void en50221_UpdatePMT( dvbpsi_pmt_t *p_pmt );
void en50221_DeletePMT( dvbpsi_pmt_t *p_pmt );
uint8_t en50221_StatusMMI( uint8_t *p_answer, ssize_t *pi_size );
uint8_t en50221_StatusMMISlot( uint8_t *p_buffer, ssize_t i_size,
uint8_t *p_answer, ssize_t *pi_size );
uint8_t en50221_OpenMMI( uint8_t *p_buffer, ssize_t i_size );
uint8_t en50221_CloseMMI( uint8_t *p_buffer, ssize_t i_size );
uint8_t en50221_GetMMIObject( uint8_t *p_buffer, ssize_t i_size,
uint8_t *p_answer, ssize_t *pi_size );
uint8_t en50221_SendMMIObject( uint8_t *p_buffer, ssize_t i_size );
/*
* This is where it gets scary: do not show to < 18 yrs old
*/
/*****************************************************************************
* en50221_SerializeMMIObject :
*****************************************************************************/
static inline int en50221_SerializeMMIObject( uint8_t *p_answer,
ssize_t *pi_size,
en50221_mmi_object_t *p_object )
{
ssize_t i_max_size = *pi_size;
en50221_mmi_object_t *p_serialized = (en50221_mmi_object_t *)p_answer;
char **pp_tmp;
int i;
#define STORE_MEMBER(pp_pointer, i_size) \
if ( i_size + *pi_size > i_max_size ) \
return -1; \
memcpy( p_answer, *pp_pointer, i_size ); \
*pp_pointer = (void *)*pi_size; \
*pi_size += i_size; \
p_answer += i_size;
if ( sizeof(en50221_mmi_object_t) > i_max_size )
return -1;
memcpy( p_answer, p_object, sizeof(en50221_mmi_object_t) );
*pi_size = sizeof(en50221_mmi_object_t);
p_answer += sizeof(en50221_mmi_object_t);
switch ( p_object->i_object_type )
{
case EN50221_MMI_ENQ:
STORE_MEMBER( &p_serialized->u.enq.psz_text,
strlen(p_object->u.enq.psz_text) + 1 );
break;
case EN50221_MMI_ANSW:
STORE_MEMBER( &p_serialized->u.answ.psz_answ,
strlen(p_object->u.answ.psz_answ) + 1 );
break;
case EN50221_MMI_MENU:
case EN50221_MMI_LIST:
STORE_MEMBER( &p_serialized->u.menu.psz_title,
strlen(p_object->u.menu.psz_title) + 1 );
STORE_MEMBER( &p_serialized->u.menu.psz_subtitle,
strlen(p_object->u.menu.psz_subtitle) + 1 );
STORE_MEMBER( &p_serialized->u.menu.psz_bottom,
strlen(p_object->u.menu.psz_bottom) + 1 );
/* pointer alignment */
i = ((*pi_size + 7) / 8) * 8 - *pi_size;
*pi_size += i;
p_answer += i;
pp_tmp = (char **)p_answer;
STORE_MEMBER( &p_serialized->u.menu.ppsz_choices,
p_object->u.menu.i_choices * sizeof(char *) );
for ( i = 0; i < p_object->u.menu.i_choices; i++ )
{
STORE_MEMBER( &pp_tmp[i],
strlen(p_object->u.menu.ppsz_choices[i]) + 1 );
}
break;
default:
break;
}
return 0;
}
/*****************************************************************************
* en50221_UnserializeMMIObject :
*****************************************************************************/
static inline int en50221_UnserializeMMIObject( en50221_mmi_object_t *p_object,
ssize_t i_size )
{
int i, j;
#define CHECK_MEMBER(pp_member) \
if ( (ptrdiff_t)*pp_member >= i_size ) \
return -1; \
for ( i = 0; ((char *)p_object + (ptrdiff_t)*pp_member)[i] != '\0'; \
i++ ) \
if ( (ptrdiff_t)*pp_member + i >= i_size ) \
return -1; \
*pp_member += (ptrdiff_t)p_object;
switch ( p_object->i_object_type )
{
case EN50221_MMI_ENQ:
CHECK_MEMBER(&p_object->u.enq.psz_text);
break;
case EN50221_MMI_ANSW:
CHECK_MEMBER(&p_object->u.answ.psz_answ);
break;
case EN50221_MMI_MENU:
case EN50221_MMI_LIST:
CHECK_MEMBER(&p_object->u.menu.psz_title);
CHECK_MEMBER(&p_object->u.menu.psz_subtitle);
CHECK_MEMBER(&p_object->u.menu.psz_bottom);
if ( (ptrdiff_t)p_object->u.menu.ppsz_choices
+ p_object->u.menu.i_choices * sizeof(char *) >= i_size )
return -1;
p_object->u.menu.ppsz_choices = (char **)((char *)p_object
+ (ptrdiff_t)p_object->u.menu.ppsz_choices);
for ( j = 0; j < p_object->u.menu.i_choices; j++ )
{
CHECK_MEMBER(&p_object->u.menu.ppsz_choices[j]);
}
break;
default:
break;
}
return 0;
}
/*****************************************************************************
* output.c
*****************************************************************************
* Copyright (C) 2004, 2008-2009 the VideoLAN team
* $Id: output.c 2 2005-09-23 19:06:05Z cmassiot $
*
* Authors: Christophe Massiot <massiot@via.ecp.fr>
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <errno.h>
#include "dvblast.h"
/*****************************************************************************
* Local prototypes
*****************************************************************************/
static int net_Open( output_t *p_output );
static void rtp_SetHdr( output_t *p_output, uint8_t *p_hdr );
/*****************************************************************************
* output_Create : called from main thread
*****************************************************************************/
output_t *output_Create( in_addr_t i_maddr, uint16_t i_port )
{
int i;
output_t *p_output = NULL;
for ( i = 0; i < i_nb_outputs; i++ )
{
if ( !pp_outputs[i]->i_maddr )
{
p_output = pp_outputs[i];
break;
}
}
if ( p_output == NULL )
{
p_output = malloc( sizeof(output_t) );
memset( p_output, 0, sizeof(output_t) );
i_nb_outputs++;
pp_outputs = realloc( pp_outputs, i_nb_outputs * sizeof(output_t *) );
pp_outputs[i] = p_output;
}
if ( output_Init( p_output, i_maddr, i_port ) < 0 )
return NULL;
return p_output;
}
/*****************************************************************************
* output_Init
*****************************************************************************/
int output_Init( output_t *p_output, in_addr_t i_maddr, uint16_t i_port )
{
p_output->i_sid = 0;
p_output->i_depth = 0;
p_output->pi_pids = NULL;
p_output->i_nb_pids = 0;
p_output->i_nb_errors = 0;
p_output->i_cc = rand() & 0xffff;
p_output->i_pat_cc = rand() & 0xf;
p_output->i_pmt_cc = rand() & 0xf;
p_output->i_pat_version = rand() & 0xff;
p_output->i_pmt_version = rand() & 0xff;
p_output->p_pat_section = NULL;
p_output->p_pmt_section = NULL;
p_output->i_ref_timestamp = 0;
p_output->i_ref_wallclock = mdate();
p_output->i_maddr = i_maddr;
p_output->i_port = i_port;
if ( (p_output->i_handle = net_Open(p_output)) < 0 )
{
p_output->i_maddr = 0;
return -1;
}
return 0;
}
/*****************************************************************************
* output_Close
*****************************************************************************/
void output_Close( output_t *p_output )
{
int i;
for ( i = 0; i < p_output->i_depth; i++ )
{
p_output->pp_blocks[i]->i_refcount--;
if ( !p_output->pp_blocks[i]->i_refcount )
block_Delete( p_output->pp_blocks[i] );
}
p_output->i_depth = 0;
p_output->i_maddr = 0;
close( p_output->i_handle );
}
/*****************************************************************************
* output_Flush
*****************************************************************************/
static void output_Flush( output_t *p_output )
{
struct iovec p_iov[NB_BLOCKS + 1];
uint8_t p_rtp_hdr[RTP_SIZE];
int i;
p_iov[0].iov_base = p_rtp_hdr;
p_iov[0].iov_len = sizeof(p_rtp_hdr);
rtp_SetHdr( p_output, p_rtp_hdr );
for ( i = 1; i < NB_BLOCKS + 1; i++ )
{
p_iov[i].iov_base = p_output->pp_blocks[i - 1]->p_ts;
p_iov[i].iov_len = TS_SIZE;
}
if ( writev( p_output->i_handle, p_iov, NB_BLOCKS + 1 ) < 0 )
{
struct in_addr s;
s.s_addr = p_output->i_maddr;
msg_Err( NULL, "coundn't writev to %s:%u (%s)", inet_ntoa( s ),
p_output->i_port, strerror(errno) );
}
for ( i = 0; i < NB_BLOCKS; i++ )
{
p_output->pp_blocks[i]->i_refcount--;
if ( !p_output->pp_blocks[i]->i_refcount )
block_Delete( p_output->pp_blocks[i] );
}
p_output->i_depth = 0;
}
/*****************************************************************************
* output_Put : called from demux
*****************************************************************************/
void output_Put( output_t *p_output, block_t *p_block )
{
p_block->i_refcount++;
p_output->pp_blocks[p_output->i_depth] = p_block;
p_output->i_depth++;
if ( p_output->i_depth >= NB_BLOCKS )
output_Flush( p_output );
}
/*****************************************************************************
* net_Open
*****************************************************************************/
static int net_Open( output_t *p_output )
{
int i_handle = socket( AF_INET, SOCK_DGRAM, 0 );
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(p_output->i_port);
sin.sin_addr.s_addr = p_output->i_maddr;
if ( connect( i_handle, (struct sockaddr *)&sin, sizeof( sin ) ) < 0 )
{
struct in_addr s;
s.s_addr = p_output->i_maddr;
msg_Err( NULL, "couldn't connect to %s:%u (%s)", inet_ntoa( s ),
p_output->i_port, strerror(errno) );
close( i_handle );
return -1;
}
if ( IN_MULTICAST( ntohl(p_output->i_maddr) ) )
{
int i = i_ttl;
setsockopt( i_handle, IPPROTO_IP, IP_MULTICAST_TTL,
(void *)&i, sizeof(i) );
}
return i_handle;
}
/*****************************************************************************
* rtp_SetHdr
*****************************************************************************/
/*
* Reminder : RTP header
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X| CC |M| PT | sequence number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| synchronization source (SSRC) identifier |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
| contributing source (CSRC) identifiers |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
static void rtp_SetHdr( output_t *p_output, uint8_t *p_hdr )
{
mtime_t i_timestamp = p_output->i_ref_timestamp
+ (mdate() - p_output->i_ref_wallclock) * 9 / 100;
p_hdr[0] = 0x80;
p_hdr[1] = 33;
p_hdr[2] = p_output->i_cc >> 8;
p_hdr[3] = p_output->i_cc & 0xff;
p_hdr[4] = (i_timestamp >> 24) & 0xff;
p_hdr[5] = (i_timestamp >> 16) & 0xff;
p_hdr[6] = (i_timestamp >> 8) & 0xff;
p_hdr[7] = i_timestamp & 0xff;
p_hdr[8] = ((uint8_t *)&i_ssrc)[0];
p_hdr[9] = ((uint8_t *)&i_ssrc)[1];
p_hdr[10] = ((uint8_t *)&i_ssrc)[2];
p_hdr[11] = ((uint8_t *)&i_ssrc)[3];
p_output->i_cc++;
}
/*****************************************************************************
* util.c
*****************************************************************************
* Copyright (C) 2004 the VideoLAN team
* $Id: util.c 9 2007-03-15 16:58:05Z cmassiot $
*
* Authors: Christophe Massiot <massiot@via.ecp.fr>
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdarg.h>
#include <sys/time.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "dvblast.h"
/*****************************************************************************
* Local declarations
*****************************************************************************/
#define MAX_MSG 1024
/*****************************************************************************
* msg_Info
*****************************************************************************/
void msg_Info( void *_unused, const char *psz_format, ... )
{
va_list args;
char psz_fmt[MAX_MSG];
va_start( args, psz_format );
snprintf( psz_fmt, MAX_MSG, "dvblast info: %s\n", psz_format );
vfprintf( stderr, psz_fmt, args );
}
/*****************************************************************************
* msg_Err
*****************************************************************************/
void msg_Err( void *_unused, const char *psz_format, ... )
{
va_list args;
char psz_fmt[MAX_MSG];
va_start( args, psz_format );
snprintf( psz_fmt, MAX_MSG, "dvblast error: %s\n", psz_format );
vfprintf( stderr, psz_fmt, args );
}
/*****************************************************************************
* msg_Warn
*****************************************************************************/
void msg_Warn( void *_unused, const char *psz_format, ... )
{
va_list args;
char psz_fmt[MAX_MSG];
va_start( args, psz_format );
snprintf( psz_fmt, MAX_MSG, "dvblast warning: %s\n", psz_format );
vfprintf( stderr, psz_fmt, args );
}
/*****************************************************************************
* msg_Dbg
*****************************************************************************/
void msg_Dbg( void *_unused, const char *psz_format, ... )
{
va_list args;
char psz_fmt[MAX_MSG];
va_start( args, psz_format );
snprintf( psz_fmt, MAX_MSG, "dvblast debug: %s\n", psz_format );
vfprintf( stderr, psz_fmt, args );
}
/*****************************************************************************
* mdate
*****************************************************************************/
mtime_t mdate( void )
{
struct timeval tv_date;
/* gettimeofday() could return an error, and should be tested. However, the
* only possible error, according to 'man', is EFAULT, which can not happen
* here, since tv is a local variable. */
gettimeofday( &tv_date, NULL );
return( (mtime_t) tv_date.tv_sec * 1000000 + (mtime_t) tv_date.tv_usec );
}
/*****************************************************************************
* msleep
*****************************************************************************/
void msleep( mtime_t delay )
{
struct timespec ts_delay;
ts_delay.tv_sec = delay / 1000000;
ts_delay.tv_nsec = (delay % 1000000) * 1000;
nanosleep( &ts_delay, NULL );
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment