メインコンテンツへスキップ
見出し画像

Linuxサーバーを音楽プレイヤー化 ~24H稼働なら音響システム化してしまえ~

    こんにちはRcatです。
    本日より17日のGWが始まりました!!本当にやりたいことは別にあるんですが、突発でいいこと思いついたので、先にこっちやります。

    数年前にこちらの記事で書いているように、linuxのサーバーが24H稼働しているのですが、普通のパソコンなのでイヤホンジャックついてるんですよね。
    まあサーバーとして使う以上全く使ってないんですが、私は部屋にいる間は音楽かけっぱなしにしてます。スマホは通知音などが混ざることもあるので、基本使っていません。なので、別に機器があるわけですがよく考えたら代用できそうなものがここにあるじゃないですか。

    というわけで、今回やるのはディスプレイなどのguiを一切使っていない状態で、コマンドラインから指示してサーバー本体から音を出すというやり方です。

    ちなみに今使ってるのは3年位前に作ったこちらです。
    SDカード経由なので追加とかが面倒なんですよね。
    そしてLinuxサーバーはPCでイヤホンジャックが余ってると…あれ、Linuxを音楽プレイヤー化した方が管理楽じゃない??というわけです。

    ちなみに5年前のもありますw何台あるんだw

    次回はこちら


    はじめに

    利用規約

    情報や作品の活用時は事前に利用規約をご確認ください。

    コメントについて

    利用規約のガイドラインを確認の上コメントしてください


    概要

    概要

    今回はmplayerを使ってやっていきます。
    理由は簡単。とりあえずよくわからなかったらチャッピー(CHatGPT)に聞く。んで教えてもらったのがmplayerということ。

    教えてもらった通りに動くか、そして必要な動作が可能かまずは確認していきます。

    最終的な完成予想

    他の記事を見ている方はもうお分かりだと思いますが、WEBサーバーにして外部の機器で操作できるようにします。
    音楽の追加はファイルをアップロードすればできますし、専用のページを作ればプレイリストやイコライザーの設定なんかもできそうです。


    mplayerを試す

    オーディオが再生できるか確認

    デフォルトで音楽を再生するコマンドが入っているので、1回それでたら試してみましょう。

    $ aplay test.wav
    再生中 WAVE 'test.wav' : Signed 16 bit Little Endian, レート 44100 Hz, ステレオ

    このコマンドがWAVしか再生できないみたいなので、適当に変換して持ってきてください。再生できればOKです。
    今回は、このコマンドやる前に対策しちゃったので確認してませんが多分再生できないかと思います。

    今回はディスプレイが物理的にも仮想的にも繋がっていないので、音響関係のデバイスが無効になっていたはずです。確かに、ディスプレイもないのに、急に本体から音が出たら普通に怖いですよね。
    ただ、少し調べるとシェル経由でも音が出せるみたいなので対策していきます。
    対策ですが、ユーザーをaudioグループに参加させるだけでした。

    $ cut -d: -f1 /etc/group
    $ sudo usermod -aG audio rcat
    <一旦ログアウトする>
    $ groups
    rcat adm cdrom sudo audio dip plugdev lpadmin lxd sambashare docker

    まず、一番最初のコマンドを実行すると、このコンピューターに定義されているグループ一覧が表示されます。その中にaudioがあるはず。
    あった場合はユーザーをグループに追加してあげます。この場合はユーザー名がrcatですね。
    最後にグループを確認します。なお、ログアウトしないと反映されないので、一旦繋ぎ直してください。

    この状態でもう一度コマンドを実行すると音楽が再生されると思います。
    なお、どのデバイスで出力されるかはお使いのコンピューターによって変わると思いますので、音が出ない場合はどのデバイスがデフォルトなのか確認する必要があると思います。

    私の場合はイヤホンジャックに接続していればそこから出てきましたし、Bluetoothを接続していればそっちに転送されました。
    Linuxって全部設定しないと動かないっていうイメージがあったんですが、もう接続するだけで勝手にデフォルト切り替わってるんですね。

    今回はイヤホンジャックに延長ケーブルをつないでアンプまで伸ばします。Bluetoothのレシーバーは品質が悪いのしかないので、ノイズが乗るんですよね…。電源もいるし。

    インストール

    オーディオが確認できたら、インストールです

    sudo apt install mplayer

    できたら再生。ここで注意が2つあります。
    1つ目はビデオ出力をオフにしないと画面がないので落ちます。
    2つ目が、ボリュームを指定しないとめちゃくちゃ音が割れた状態で出てきました。
    なので、最低限以下がデフォルトになりそうですね。

    $ mplayer -vo null -volume 100 夢が覚めても.m4a

    実行すると、再生の状態みたいなのがデフォルトで表示されました。
    矢印キーを間違って押すと、スキップとかされたので標準入力である程度制御できるって事が分かります。

    コマンドラインオプションについて確認する

    コマンドラインオプションについてはこちらから確認することができます。

    コマンドラインでは、初期のボリュームとイコライザーの設定を行います。

    音量の設定 1-100
    -volume 20 
    イコライザーの設定(各周波数に対応する部分に-12から12までの数値で入れる)
    -af equalizer=31.25Hz:62.50Hz:125.00Hz:250.00Hz:500.00Hz:1.00kHz:2.00kHz:4.00kHz:8.00kHz:16.00kHz

    キーボードの操作についても一応調べました。
    操作は以下の通りです。まあ、今回は使いませんが

    • 音量上下
      0と9または*と/

    • 一時停止
      Pまたはスペース

    • 停止(コマンド終了)
      Qまたはエスケープ

    • スキップ(別に要らない)
      左右の矢印で10秒

    • ミュート
      M

    コマンド操作について確認する

    もう少し確認を進めると-slaveで起動した場合はコマンドを受け付けるようになるみたいです。
    なので、Pythonから制御する時は標準入力でこのコマンドを与えていきます。
    コマンド一覧を確認してみました。

    mplayer -input cmdlist
    radio_step_channel   Integer
    radio_set_channel    String
    radio_set_freq       Float
    radio_step_freq      Float
    seek                 Float [Integer]
    edl_loadfile         String
    edl_mark
    audio_delay          Float [Integer]
    speed_incr           Float
    speed_mult           Float
    speed_set            Float
    quit                 [Integer]
    stop
    pause
    frame_step
    pt_step              Integer [Integer]
    pt_up_step           Integer [Integer]
    alt_src_step         Integer
    loop                 Integer [Integer]
    sub_delay            Float [Integer]
    sub_step             Integer [Integer]
    osd                  [Integer]
    osd_show_text        String [Integer] [Integer]
    osd_show_property_te String [Integer] [Integer]
    osd_show_progression
    volume               Float [Integer]
    balance              Float [Integer]
    use_master
    mute                 [Integer]
    contrast             Integer [Integer]
    gamma                Integer [Integer]
    brightness           Integer [Integer]
    hue                  Integer [Integer]
    saturation           Integer [Integer]
    frame_drop           [Integer]
    sub_pos              Integer [Integer]
    sub_alignment        [Integer]
    sub_visibility       [Integer]
    sub_load             String
    sub_remove           [Integer]
    vobsub_lang          [Integer]
    sub_select           [Integer]
    sub_source           [Integer]
    sub_vob              [Integer]
    sub_demux            [Integer]
    sub_file             [Integer]
    sub_log
    sub_scale            Float [Integer]
    ass_use_margins      [Integer]
    get_percent_pos
    get_time_pos
    get_time_length
    get_file_name
    get_video_codec
    get_video_bitrate
    get_video_resolution
    get_audio_codec
    get_audio_bitrate
    get_audio_samples
    get_meta_title
    get_meta_artist
    get_meta_album
    get_meta_year
    get_meta_comment
    get_meta_track
    get_meta_genre
    switch_audio         [Integer]
    switch_angle         [Integer]
    switch_title         [Integer]
    tv_start_scan
    tv_step_channel      Integer
    tv_step_norm
    tv_step_chanlist
    tv_set_channel       String
    tv_last_channel
    tv_set_freq          Float
    tv_step_freq         Float
    tv_set_norm          String
    tv_set_brightness    Integer [Integer]
    tv_set_contrast      Integer [Integer]
    tv_set_hue           Integer [Integer]
    tv_set_saturation    Integer [Integer]
    forced_subs_only     [Integer]
    dvb_set_channel      Integer Integer
    switch_ratio         [Float]
    vo_fullscreen        [Integer]
    vo_ontop             [Integer]
    file_filter          Integer
    vo_rootwin           [Integer]
    vo_border            [Integer]
    screenshot           [Integer]
    panscan              Float [Integer]
    switch_vsync         [Integer]
    loadfile             String [Integer]
    loadlist             String [Integer]
    run                  String
    capturing
    change_rectangle     Integer Integer
    teletext_add_dec     String
    teletext_go_link     Integer
    overlay_add          String Integer Integer Integer Integer
    overlay_remove       Integer
    dvdnav               String
    menu                 String
    set_menu             String [String]
    help
    exit
    hide                 [Integer]
    get_vo_fullscreen
    get_sub_visibility
    key_down_event       Integer
    set_property         String String
    get_property         String
    step_property        String [Float] [Integer]
    seek_chapter         Integer [Integer]
    set_mouse_pos        Integer Integer
    af_switch            String
    af_add               String
    af_del               String
    af_clr
    af_cmdline           String String
    gui                  String

    例えばこんな感じで起動すると、キーボードショートカットが効かなくなりますが、上に書いてあるコマンドを入力してエンターを押すと作動します。

    $ mplayer -vo null -volume 20 夢が覚めても.m4a -af equalizer=1:3:0:1:2:4:5:6:5:0 -slave

    使えそうなのは以下

    • volume 音量 フラグ
      音量を設定する。フラグを省略すれば相対、1をつければ絶対音量を指定できる

    • pause
      一時停止ができる。もう1回送信すると解除

    • stop
      停止ができる

    • af_cmdline
      フィルターのコマンドラインを上書きできる
      例えばこんな感じ"af_cmdline equalizer 0:0:0:0:0:4:5:6:5:0"

    • seek
      再生位置を指定できる。0-100 %で音量と同じフラグ指定。

    ざっとこのくらい調べればやりたいことはできそう。
    というわけで、次はPythonから操作します。


    Pythonでmplayerをコントロール

    Pythonのインストール

    今回はLinuxを前提としてるので多分入ってると思いますが、もし入ってない方はまずはPythonの用意をしてください。
    なお、文法的に3.8とか9とかそれくらいでないと認識できない文法を使っていることがありますのでご注意ください。(私は3.10です)

    クラスを作成

    今回の音楽プレイヤーは、部品として使えるようにクラス化します。
    このクラスをサーバーの方でインスタンスして各cgiから叩くって感じですね。
    ※ソースコードはテキストで書かない方針なので、関数ごとの紹介になります。全体はダウンロードをご利用ください。

    再生関数はこんな感じにしました。

    画像

    普通にSubprocessで起動しただけ。
    引数は上で説明した通りのものにしています。
    再生と同時にイコライザは掛けるので、インスタンス変数に保持するようにしています。

    また、この後出てくる情報取得のために標準出力は監視します。
    どうしてもread()やreadline()を使うと、出力が来ていない場合に止まってしまうのでスレッドで分けます。応答の有無はバッファ用リストの長さ変化で検知する感じ。

    音量を設定するメソッドはこんな感じ
    標準入力に書き込めばいいので、こんな感じでコマンドを書きこみます。

    画像

    書き込み自体はいろんなところで使うのでこれも関数化。\n入れても入れなくてもいいように工夫。

    画像

    コマンドの応答を取得するのはこちら。例えばget_time_lengthコマンドは、再生しているデータは何秒なのか取得できます。
    応答は"ANS_LENGTH=224.86"のような感じなので、"="で切って最後を取ればOK。

    画像

    なお、コマンドを書きこんでもその関数から応答が直に返る仕組みではないため、事前に標準出力の行数をメモし、出力が増えたら返事が来たと判定して読み込みを行うことでフリーズを回避しています。

    ここまでの使い方

    from MusicServer import Mplayer
    P = Mplayer()
    P.Play("/Script/MusicServer/music/夢が覚めても.m4a")
    #ここで再生開始
    P.Set_Volume(30) #音量を30に設定
    P.Seek_Music(0.8) #再生位置を80%まで飛ばす

    音楽プレイヤークラスとして作りこむ

    ここからは、上記のクラスに対して機能を作りこんでいきます。

    仕様

    まずはどんな風な機能を盛り込みたいのか考えていきます。

    • 連続再生機能
      当たり前ですが、フォルダやプレイリストを指定して無限に再生するのが前提です。

      • ランダム再生機能
        基本的にはランダムに再生してもらうのが私は好きなのでこれは外せません。

      • 履歴式ランダム法
        これは昔の作品から取り入れているやり方なのですが、一度再生した曲は配列の中に残しておいて、ランダムでピックアップした時に配列の前にあるほど落選しやすくするという方法をとることで、ランダムになりつつもバランスを保つような再生方法です。これも外せません。

    • プレイリスト作成機能
      基本的にはフォルダから再生という感じなのですが、その中のファイル名を指定してプレイリストを作れるようにします。
      Arduinoの時からある機能なので、Cで作るよりかは簡単にできるはず。というか、よく昔C言語でプレイリストの編集とか書いたよな…あの時はチャットGPTなんていなかったし。

    • イコライザーの適用
      1度イコライザーの凄さを知ってしまうと、もう生の音楽って聞けないんですよね。特に中央域を減少(高音と低音をブースト)させることでこもった感じがなくなって聞き取りやすくなるのが好きです。

    ボリュームの指定

    これは上でも書きましたが一応載せなおします。

    画像

    再生位置の指定

    再生位置を指定してスキップする関数。
    私は100分率は0-1で扱う人間なのでここではそうしています。
    音量は0から100だって?まあ、あれはコマンドラインでも使うし、0から100という数値ってことで…。

    画像

    一時停止&停止

    これは一番簡単ですね。コマンド叩くだけです。
    ちなみにこの後連続再生機能なども盛り込んでおくので、その場合は停止を押したら全体が止まるようにフラグを立ててます。

    画像

    イコライザーの追加

    さて、ちょっと面倒くさいイコライザーのところです。

    ここではイコライザーの値をリスト、または区切られた文字列で受け取ります。名前も同時に受け取ることでイコライザーを登録する仕組みです。
    ちなみにすぐに有効化できるようにアクティブのフラグもつけています。

    画像

    イコライザーを適用する

    適用する方の関数はこちら
    上の方で紹介したイコライザーを上書きするコマンドを投げます。事前に登録してあることが前提で、インスタンス変数から値を引っ張ってきます。

    画像

    となると設定の保存などが今後必要ですね。作りましょう。

    再生関連

    再生関連を作り込んでいきます。
    ここは少し難しいですが、1つの関数でファイルを再生したり、フォルダを再生したり、プレイリストを再生したりできるようにします。

    画像

    というわけで作った関数はこちら。
    何やってるかと言うと、まずは指定されたものがファイルなのかフォルダなのかで分岐します。
    ファイルだった場合はそれを再生します。
    フォルダ立った場合はこの先のフォルダを再生する関数で出発。
    プレイリストだけは名前で指定するので、ファイルでもフォルダでもない。その場合はプレイリストのロードに入るという感じです。

    _Playは一番最初に紹介した再生する関数です。今後直接呼び出すことがないのでアンダーバーをつけました。


    フォルダを再生する関数がこちら
    気づいたら結構長くなってましたね。上の方はデータチェックなので、あまり気にしなくて大丈夫です。メインは再生のループですね。
    外部から操作を受け付けなければならないので、ここもスレッドを使って並列で実行してます。

    画像

    再生自体は一番下でストップフラグが立つまでずっと回り続けます。
    何の曲を再生するのかに関しては、チョイスの関数をその上で定義しています。

    そしてこのチョイスが結構大きな関数です。というのも3種類の選択方法が1つの関数にまとまっているからですね。
    まず最初に順番。これは単純にインデックスをプラスしていって、フォルダの中身を順番に再生するだけ。
    次にランダム。これが一番簡単でランダムライブラリのチョイスを使ってフォルダの中から1つ選択。
    最後に履歴式ランダム法。これは最初にランダムでピックアップした後、履歴にあるかどうか確認し、履歴にある場合は先頭にある方が高確率でドロップするという仕組みになっています。
    具体的には履歴のインデックスを取得した後、履歴の長さからランダムでインデックスをピックアップし、それよりも大きい場合はドロップといった感じですね。

    あとはこの時点ではまだできていませんが、プレイリストにも対応しています。
    プレイリストは有効になっている場合は、ピックアップされた。楽曲が入っているかどうか確認し、入っていない場合はもう1回選択し直すというのを行います。単純ですね。

    ループ中でも選択した曲を再生する

    ここまで作って思ったのが、ループの場合曲選んで再生できないということ。
    フォルダを指定して再生なので当たり前じゃ当たり前ですけど、使ってみてあれっと思いました。
    まあnoteに書いてる時はそれも作った後に書いてるんですに対策済みなんですが。

    というわけでこういう関数を追加します。これは何かというと、次に絶対この曲再生してくださいね。というのを追加します。
    そして今すぐ切り替えたい場合は、強制的にネクストで次の曲に行くという感じで指定した楽曲を再生します。

    画像

    必ず指定した楽曲が再生される仕組みは先ほどのチョイス関数にあります。
    これ一番最初に次の楽曲が指定されている場合は、それがフォルダ内に含まれる場合、必ずそれを返すという処理が最初に入ってます。
    これのおかげで指定した曲がランダム設定でも必ず返るという感じです。

    画像

    プレイリストの作成

    次にプレイリストを作ってみます。
    この作品のプレイリストは、曲のリストというわけではなくイコライザーや再生モードなどいくつかの設定も含むものとします。

    画像

    無難に名前とリスト化する曲を渡すと、現在のイコライザや再生モードを保持してデータを作成という感じにしてみました。
    このデータをインスタンス変数として保持し、ある場合はプレイリストが有効であるという扱いにします。つまり無効にする場合は空の辞書を代入します。

    ちなみにプレイリストの判定はチョイスの中の下記です。
    チョイスした曲がリストに無ければやり直しという単純な構造。

    画像

    タイマー設定

    次にタイマーの設定です。
    秒を指定して自動で再生が止まるようにします。

    画像

    再生停止はフォルダ再生限定で、再生中は曲の終わりを監視するループがあるのですが、そこにタイマー機能をねじ込みます。

    画像

    まとめ

    ちょっと長くなってしまったので、一旦ここで分けようと思います。
    今回はLinuxで音楽を再生するための事前準備とmplayerのテスト、そして制御プログラムのクラスを作りました。やはりLinuxでお勧めされるだけあって、プログラムからの制御がすごくしやすいですね。
    次回はWebサーバーを作って、そのcgiにこのクラスを導入していきます。

    それではまたお会いしましょう。


    配布情報

    現段階での配布はありません。
    次回以降、Webサーバー化した状態での配布を予定しています。

    次回はこちら


     
     
    モノづくりが趣味。 "売ってないなら自分で作ればいいじゃない"をコンセプトに、機械/電気/AI/プログラム/ネットワーク 全部やります。 記事執筆はAI不使用。 ご連絡や利用規約はプロフィール記事参照。

    あなたへのおすすめ