Ubuntu16.04(kernel4.4)でPT2動作

* (参考) [PT2で地デジと衛星放送を録画](https://tech.nosuz.jp/2016/05/record-braodcast-by-pt2/)
* (参考) [Linux/テレビ関連/PT2](http://baalzephon.no-ip.org/tech/index.php?Linux%2F%E3%83%86%E3%83%AC%E3%83%93%E9%96%A2%E9%80%A3%2FPT2)


## 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
```

自動ロードを禁止

```shell-session:/etc/modprobe.d/blacklist.conf
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から呼ばれないので，パッチをあてる．

```cpp:driver/pt1_pci.c
--- 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の際に自動ビルドする方法は [こちら参照](https://tech.nosuz.jp/2016/05/record-braodcast-by-pt2/)

recpt1をリンク時にinlineの参照がないと怒られるので，inlineを削除．

```cpp:src/ushare.c
-inline void
+/*inline*/ void
 display_headers (void)
```

```cpp:src/trace.c
-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のhttp版パッチを適用してみた](http://papa3camel.blogspot.jp/2012/07/recpt1http.html)

```shell-session
$ recpt1 --b25 --strip --sid hd --http 8888
```

VLCからは http://サーバのIPアドレス:ポート番号/チャネル番号 を指定して接続する．

systemdに登録するためサービスを記述して登録 ([参考](http://qiita.com/DQNEO/items/0b5d0bc5d3cf407cb7ff)) 
**※recpt1はforkするので, type=forking にしないとうまく動作しないので注意**
((参考)[Systemd入門(4) - serviceタイプUnitの設定ファイル](http://enakai00.hatenablog.com/entry/20130917/1379374797))

```shell-session:/etc/systemd/system/recpt1-http.service
[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
```

```shell-session
$ sytemctl list-unit-files --type=services | grep recpt1
kuma-recpt1-http.service                   disabled

# 自動起動on
$ sudo systemctl enable hello
# 起動
$ sudo systemctl start hello
```

パッチはこちら

```cpp:src/recpt1.c
--- 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で再起動される．

```cpp:src/recpt1.c
        fprintf(stderr, "pid = %d\n", getpid());
+       FILE* fp = fopen("/tmp/recpt1.pid", "w");
+       fprintf(fp, "%d\n", getpid());
+       fclose(fp);
```
```shell-session:/etc/systemd/system/recpt1-http.service
[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で指定しても見つからない)