徒然なる日々を送るソフトウェアデベロッパーの記録(2)

技術上思ったことや感じたことを気ままに記録していくブログです。さくらから移設しました。

Pi3でインターネットラジオ選局機能を作る

出しつくされた感があるインターネットラジオ選局ソフトですが、
せっかく Pi3 に無線 LAN と Bluetooth が標準装備された
ので、簡単なものを作ってみます。

今日は選局機能を持つサーバプログラムを作ります。
(後日スマホからアクセス可能な web サーバを作り、
UNIX ドメインソケットで通信させます。)

  • コマンド

/tmp/player.sock に UNIX ドメインソケットを構築し、
コマンドを受信します。コマンドに対する応答はありません。

  1. play <playlist または URL>

指定されたインターネットラジオを選局し、bluetooth スピーカー
から音声を流します。

  1. stop

音声を停止します。

  1. exit

サーバプログラムを終了します。

  • プログラム

久々に perl で書いてみました。
デーモン化させるため、Proc::Daemon
(libproc-daemon-perlパッケージ)をインストールしています。
また選局は mplayer を使っているので、mplayer2 パッケージを
インストールしておく必要があります。

#!/usr/bin/perl
use IO::Socket::UNIX;
use Proc::Daemon;

# unset X parameter
$ENV{"DISPLAY"} = undef;

my $pidfile = "/run/lock/player.pid";
my $sockpath = "/tmp/player.sock";
my $pidmplayer = 0;

if (-f $pidfile) {
  writelog("pid file found; already invoked?");
  exit(0);
}


$SIG{'INT'} = \&sigint;
$SIG{'TERM'} = \&sigint;

unlink($sockpath);

# daemonize
Proc::Daemon::Init;

my $server = IO::Socket::UNIX->new(
  Type => SOCK_STREAM(), Local => $sockpath, Listen => 1
);

# main loop
while (my $conn = $server->accept()) {
  while (<$conn>) {
    s/^\s*(.*?)\s*$/$1/;
    next if $_ eq "";
    my @s = split(/\s+/, $_);

    if ($s[0] eq "play") {
      if ($pidmplayer != 0) {
         kill ('TERM', $pidmplayer);
        $pidmplayer = 0;
      }
      $s[0] = "/usr/bin/mplayer";
      my $j = join(" ", @s);
      $pidmplayer = fork();
      if ($pidmplayer == 0) {
        # child process
        # start pulseaudio with bluetooth support
        `pulseaudio --start`;
        `echo 'power on' | sudo bluetoothctl`;
        `echo 'connect ZZ:YY:XX:WW:UU:VV' | sudo bluetoothctl`;
        `echo  'set-default-sink 1' | pacmd`;
        exec "$j";
      }
    } elsif ($s[0] eq "stop") {
      if ($pidmplayer != 0) {
        kill('TERM', $pidmplayer);
        waitpid($pidmlayer, 0);
        $pidmplayer = 0;
      }
    } elsif ($s[0] eq "exit") {
      last processAbort;
    }
  }
}
processAbort:

# create UNIX domain socket
unlink($pidfile);

# stop mplayer
sub sigint {
  if ($pidmplayer != 0) {
    kill('INT', $pidmplayer);
    `pulseaudio -k`;
    $pidmplayer = 0;
  }
  exit(0);
}

# write PID file
sub writePidFile {
  local ($_) = @_;
  open PID, ">$pidfile";
  print PID $$;
  print PID "\n";
  close PID;
}

# write a log
sub writelog {
  local ($w) = @_;
  open LOG, ">>/tmp/player.log";
  print LOG $w;
  print LOG "\n";
  close LOG;
}

bluetoothctl を使うために sudo するので pi
ユーザのようにパスワード無しで root ユーザに
なれないと動きません。

また、指定可能な文字列を制限していないので、
セキュリティに対する一種の脆弱性が存在します。

まあ、所詮家の LAN で使うものだから堅いことはなし(笑)。