- (参考) PT2で地デジと衛星放送を録画
- (参考) Linux/テレビ関連/PT2
ic card readerのinstall
$ apt-get install pcscd pcsc-tools
$ pcsc_scan
:
Japanese Chijou Digital B-CAS Card (pay TV)
dvb版PT2ドライバを削除
$ lsmod | grep pt1
dvb_core 122880 1 earth_pt1
$ rmmod earth_pt1
自動ロードを禁止
blacklist earth_pt1
mercurial (hg) のinstall
$ apt-get install mercurial
driverの取得とビルド
$ hg clone http://hg.honeyplanet.jp/pt1.oyama/ pt1_driver_oyama
# あるいは $ wget http://hg.honeyplanet.jp/pt1.oyama/archive/tip.tar.bz2
- PT1/PT2ドライバ(旧) http://hg.honeyplanet.jp/pt1/
- TS split内蔵版 http://hg.honeyplanet.jp/pt1.oyama/
arib25フォルダを復活させる
$ hg revert -r c44e16dbb0e2 --all
$ hg revert -r tip --all
kernel4.2以降はvmalloc.hがio.hから呼ばれないので,パッチをあてる.
--- pt1_pci.c.org 2016-11-27 19:07:47.986658497 +0900
+++ pt1_pci.c 2016-11-27 19:13:06.821699691 +0900
@@ -15,4 +15,7 @@
#include <asm/uaccess.h>
#include <linux/version.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,2,0)
+ #include <linux/vmalloc.h>
+#endif
#include <linux/mutex.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,23)
ドライバ/arib25をインストール
$ cd driver
$ make
$ make install
$ modprobe pt1_drv
$ lsmod | grep pt1_drv
$ apt-get install libpcsclite-dev
$ cd ../arib25/
$ make
$ make install
※DKMSを使ってkernel version upの際に自動ビルドする方法は こちら参照
recpt1をリンク時にinlineの参照がないと怒られるので,inlineを削除.
-inline void
+/*inline*/ void
display_headers (void)
-inline void
+/*inline*/ void
start_log (void)
関連モジュールインストールして,recpt1をビルド
$ apt-get install ffmpeg libavformat-dev libdlna-dev libupnp-dev
$ cd ..
$ ./configure --with-b25-dir=/usr/local --enable-b25
$ cd src
$ make
$ make install
信号をチェックして録画をチェック
(channelは src/channels/sample.recpt1-channels-*
あたりを参考に)
$ checksignal 21
device = /dev/pt1video2
C/N = 31.414479dB^C
$ recpt1 --b25 --strip 21 10 test.mpg
enable B25 strip
pid = 27975
device = /dev/pt1video2
Recording...
Recorded 10sec
channel割り当て (@千葉)
BTN | CH | 放送局名 |
---|---|---|
1 | 27 | NHK総合 |
2 | 26 | NHK教育 |
3 | 30 | 千葉テレビ |
4 | 25 | 日テレ |
5 | 24 | TV Asahi |
6 | 22 | TBS |
7 | 23 | TV Tokyo |
8 | 21 | Fuji TV |
9 | 16 | Tokyo MX1 |
11 | 28 | 放送大学 |
http patchをあてる
$ recpt1 --b25 --strip --sid hd --http 8888
VLCからは http://サーバのIPアドレス:ポート番号/チャネル番号 を指定して接続する.
systemdに登録するためサービスを記述して登録 (参考)
※recpt1はforkするので, type=forking にしないとうまく動作しないので注意
((参考)Systemd入門(4) - serviceタイプUnitの設定ファイル)
[Unit]
Description = recpt1-http server
After = network.target
[Service]
ExecStart = /usr/local/bin/recpt1 --b25 --strip --sid hd --http 8888
ExecStop = /usr/bin/pkill recpt1
Restart = no
Type = forking
[Install]
WantedBy = multi-user.target
$ sytemctl list-unit-files --type=services | grep recpt1
kuma-recpt1-http.service disabled
# 自動起動on
$ sudo systemctl enable hello
# 起動
$ sudo systemctl start hello
パッチはこちら
--- recpt1.c.org 2016-11-27 21:52:51.502723260 +0900
+++ recpt1.c 2016-11-27 21:51:17.520258656 +0900
@@ -22,6 +22,8 @@
#include <netinet/in.h>
#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
#include <pt1_ioctl.h>
#include "config.h"
@@ -869,7 +871,7 @@
show_usage(char *cmd)
{
#ifdef HAVE_LIBARIB25
- fprintf(stderr, "Usage: \n%s [--b25 [--round N] [--strip] [--EMM]] [--udp [--addr hostname --port portnumber]] [--device devicefile] [--lnb voltage] [--sid SID1,SID2] [--es filename_suffix] [--start_time YYYYMMDDHHMISS] [--dlna] channel rectime destfile\n", cmd);
+ fprintf(stderr, "Usage: \n%s [--b25 [--round N] [--strip] [--EMM]] [--udp [--addr hostname --port portnumber]] [--http portnumber] [--device devicefile] [--lnb voltage] [--sid SID1,SID2] [--es filename_suffix] [--start_time YYYYMMDDHHMISS] [--dlna] channel rectime destfile\n", cmd);
#else
fprintf(stderr, "Usage: \n%s [--strip] [--EMM]] [--udp [--addr hostname --port portnumber]] [--device devicefile] [--lnb voltage] [--sid SID1,SID2] [--es filename_suffix] [--start_time YYYYMMDDHHMISS] [--dlna] channel rectime destfile\n", cmd);
#endif
@@ -892,6 +894,7 @@
fprintf(stderr, "--udp: Turn on udp broadcasting\n");
fprintf(stderr, " --addr hostname: Hostname or address to connect\n");
fprintf(stderr, " --port portnumber: Port number to connect\n");
+ fprintf(stderr, "--http portnumber: Turn on http broadcasting (run as a daemon)\n");
fprintf(stderr, "--device devicefile: Specify devicefile to use\n");
fprintf(stderr, "--lnb voltage: Specify LNB voltage (0, 11, 15)\n");
fprintf(stderr, "--sid SID1,SID2,...: Specify SID number in CSV format (101,102,...)\n");
@@ -1033,6 +1036,34 @@
return 0; /* success */
}
+//read 1st line from socket
+int read_line(int socket, char *p){
+ int len = 0;
+ while (1){
+ int ret;
+ ret = read(socket, p, 1);
+ if ( ret == -1 ){
+ perror("read");
+ // Connection reset by peer
+ if ( errno == 104 ){
+ return -1;
+ }
+ exit(1);
+ } else if ( ret == 0 ){
+ break;
+ }
+ if ( *p == '\n' ){
+ p++;
+ len++;
+ break;
+ }
+ p++;
+ len++;
+ }
+ *p = '\0';
+ return len;
+}
+
int
main(int argc, char **argv)
@@ -1074,6 +1105,7 @@
{ "udp", 0, NULL, 'u'},
{ "addr", 1, NULL, 'a'},
{ "port", 1, NULL, 'p'},
+ { "http", 1, NULL, 'H'},
{ "device", 1, NULL, 'd'},
{ "help", 0, NULL, 'h'},
{ "version", 0, NULL, 'v'},
@@ -1090,6 +1122,7 @@
boolean use_b25 = FALSE;
boolean use_udp = FALSE;
+ boolean use_http = FALSE;
boolean fileless = FALSE;
boolean use_stdout = FALSE;
boolean use_splitter = FALSE;
@@ -1097,6 +1130,7 @@
boolean use_dlna = FALSE;
char *host_to = NULL;
int port_to = 1234;
+ int port_http = 12345;
sock_data *sockdata = NULL;
char *device = NULL;
int val;
@@ -1105,7 +1139,7 @@
char *es_name_prefix = NULL;
char *start_time = NULL;
- while((result = getopt_long(argc, argv, "br:smn:ua:p:d:hvli:y:c",
+ while((result = getopt_long(argc, argv, "br:smn:ua:H:p:d:hvli:y:c",
long_options, &option_index)) != -1) {
switch(result) {
case 'b':
@@ -1125,6 +1159,11 @@
host_to = "localhost";
fprintf(stderr, "enable UDP broadcasting\n");
break;
+ case 'H':
+ use_http = TRUE;
+ port_http = atoi(optarg);
+ fprintf(stderr, "creating a http daemon\n");
+ break;
case 'h':
fprintf(stderr, "\n");
show_usage(argv[0]);
@@ -1195,6 +1234,234 @@
}
}
+ //http broadcasting
+ if(use_http){
+ fprintf(stderr, "run as a daemon..\n");
+ if(daemon(1,1)){
+ perror("failed to start");
+ exit(1);
+ }
+ fprintf(stderr, "pid = %d\n", getpid());
+
+ int connected_socket, listening_socket;
+ struct sockaddr_in sin;
+ unsigned int len;
+ int ret;
+ int sock_optval = 1;
+
+ listening_socket = socket(AF_INET, SOCK_STREAM, 0);
+ if ( listening_socket == -1 ){
+ perror("socket");
+ exit(1);
+ }
+
+ if ( setsockopt(listening_socket, SOL_SOCKET, SO_REUSEADDR,
+ &sock_optval, sizeof(sock_optval)) == -1 ){
+ perror("setsockopt");
+ exit(1);
+ }
+
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(port_http);
+ sin.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ if ( bind(listening_socket, (struct sockaddr *)&sin, sizeof(sin)) < 0 ){
+ perror("bind");
+ exit(1);
+ }
+
+ ret = listen(listening_socket, SOMAXCONN);
+ if ( ret == -1 ){
+ perror("listen");
+ exit(1);
+ }
+ fprintf(stderr,"listening at port %d\n", port_http);
+
+ /* initialize decoder */
+ if(use_b25) {
+ dec = b25_startup(&dopt);
+ if(!dec) {
+ fprintf(stderr, "Cannot start b25 decoder\n");
+ fprintf(stderr, "Fall back to encrypted recording\n");
+ use_b25 = FALSE;
+ }
+ }
+ //set rectime to the infinite
+ if(parse_time("-",&tdata) != 0){
+ return 1;
+ }
+
+ while(1){
+ struct hostent *peer_host;
+ struct sockaddr_in peer_sin;
+
+ len = sizeof(peer_sin);
+
+ connected_socket = accept(listening_socket, (struct sockaddr *)&peer_sin, &len);
+ if ( connected_socket == -1 ){
+ perror("accept");
+ exit(1);
+ }
+
+ int error;
+ char hbuf[NI_MAXHOST], nhbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
+ error = getnameinfo((struct sockaddr *)&peer_sin, sizeof(peer_sin), hbuf, sizeof(hbuf), NULL, 0, 0);
+ if(error) {
+ fprintf(stderr, "getnameinfo(): %s\n", gai_strerror(error));
+ exit(1);
+ }
+ error = getnameinfo((struct sockaddr *)&peer_sin, sizeof(peer_sin), nhbuf, sizeof(nhbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV);
+ if(error) {
+ fprintf(stderr, "getnameinfo(): %s\n", gai_strerror(error));
+ exit(1);
+ }
+ fprintf(stderr,"connect from: %s [%s] port %s\n", hbuf, nhbuf, sbuf);
+
+ int read_size;
+
+ char buf[256];
+ read_size = read_line(connected_socket, buf);
+ if ( read_size != -1 ){
+ fprintf(stderr,"request command is %s\n",buf);
+ char s0[256],s1[256],s2[256];
+ sscanf(buf,"%s%s%s",s0,s1,s2);
+ char delim[] = "/";
+ char *channel = strtok(s1,delim);
+ char *sidflg = strtok(NULL,delim);
+ if(sidflg)
+ sid_list = sidflg;
+ fprintf(stderr,"channel is %s\n",channel);
+
+ /* initialize splitter */
+ if(use_splitter) {
+ splitter = split_startup(sid_list);
+ if(splitter->sid_list == NULL) {
+ fprintf(stderr, "Cannot start TS splitter\n");
+ return 1;
+ }
+ }
+ char header[] = "HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\nCache-Control: no-cache\r\n\r\n";
+ ret = write(connected_socket, header, strlen(header));
+
+ //set write target to http
+ tdata.wfd = connected_socket;
+
+ //tune
+ if(tune(channel, &tdata, device) != 0){
+ fprintf(stderr, "Tuner cannot start recording\n");
+ continue;
+ }
+ /* prepare thread data */
+ tdata.queue = p_queue;
+ tdata.decoder = dec;
+ tdata.splitter = splitter;
+ tdata.sock_data = sockdata;
+
+ /* spawn signal handler thread */
+ init_signal_handlers(&signal_thread, &tdata);
+
+ /* spawn reader thread */
+ tdata.signal_thread = signal_thread;
+ pthread_create(&reader_thread, NULL, reader_func, &tdata);
+
+ /* spawn ipc thread */
+ key_t key;
+ key = (key_t)(getpid());
+
+ if ((tdata.msqid = msgget(key, IPC_CREAT | 0666)) < 0) {
+ perror("msgget");
+ }
+ pthread_create(&ipc_thread, NULL, mq_recv, &tdata);
+
+ /* start recording */
+ if(ioctl(tdata.tfd, START_REC, 0) < 0) {
+ fprintf(stderr, "Tuner cannot start recording\n");
+ exit(1);
+ }
+
+ fprintf(stderr, "Broadcasting...\n");
+
+ time(&tdata.start_time);
+
+ /* read from tuner */
+ while(1) {
+ if(f_exit)
+ break;
+
+ time(&cur_time);
+ bufptr = malloc(sizeof(BUFSZ));
+ if(!bufptr) {
+ f_exit = TRUE;
+ break;
+ }
+ bufptr->size = read(tdata.tfd, bufptr->buffer, MAX_READ_SIZE);
+ if(bufptr->size <= 0) {
+ if((cur_time - tdata.start_time) >= tdata.recsec && !tdata.indefinite) {
+ f_exit = TRUE;
+ enqueue(p_queue, NULL);
+ break;
+ }
+ else {
+ continue;
+ }
+ }
+ enqueue(p_queue, bufptr);
+
+ /* stop recording */
+ time(&cur_time);
+ if((cur_time - tdata.start_time) >= tdata.recsec && !tdata.indefinite) {
+ ioctl(tdata.tfd, STOP_REC, 0);
+ /* read remaining data */
+ while(1) {
+ bufptr = malloc(sizeof(BUFSZ));
+ if(!bufptr) {
+ f_exit = TRUE;
+ break;
+ }
+ bufptr->size = read(tdata.tfd, bufptr->buffer, MAX_READ_SIZE);
+ if(bufptr->size <= 0) {
+ f_exit = TRUE;
+ enqueue(p_queue, NULL);
+ break;
+ }
+ enqueue(p_queue, bufptr);
+ }
+ break;
+ }
+ }
+
+ /* delete message queue*/
+ msgctl(tdata.msqid, IPC_RMID, NULL);
+
+ pthread_kill(signal_thread, SIGUSR1);
+
+ /* wait for threads */
+ pthread_join(reader_thread, NULL);
+ pthread_join(signal_thread, NULL);
+ pthread_join(ipc_thread, NULL);
+
+ /* close tuner */
+ if(close_tuner(&tdata) != 0)
+ return 1;
+
+ //reset queue
+ destroy_queue(p_queue);
+ p_queue = create_queue(MAX_QUEUE);
+
+ /* close splitter */
+ if(use_splitter) {
+ split_shutdown(splitter);
+ }
+
+ }
+ /* close http socket */
+ close(tdata.wfd);
+
+ fprintf(stderr,"connection closed. still listening at port %d\n",port_http);
+ f_exit = FALSE;
+ }
+ }
+
if(argc - optind < 3) {
if(argc - optind == 2 &&
(use_udp|use_dlna|use_esout) ) {
さらにhttpの際のpidを /tmp/recpt1.pid に記録するように修正して,PIDFileを設定し,Restart=always にすると,落ちた時自動でsystemdで再起動される.
fprintf(stderr, "pid = %d\n", getpid());
+ FILE* fp = fopen("/tmp/recpt1.pid", "w");
+ fprintf(fp, "%d\n", getpid());
+ fclose(fp);
[Unit]
Description = recpt1-http server
After = network.target
[Service]
ExecStart = /usr/local/bin/recpt1 --b25 --strip --sid hd --http 8888
ExecStop = /usr/bin/pkill recpt1
ExeStopPost = /bin/rm /tmp/recpt1.pid
Restart = always
Type = forking
PIDFile = /tmp/recpt1.pid
#PrivateTmp = true
[Install]
WantedBy = multi-user.target
PrivateTmpを有効にすると,外部からアクセスできないので無効にする.
(PIDFileで指定しても見つからない)