玩式草子─ソフトウェアとたわむれる日々

第95回「らじる☆らじる」HLS経由で

当初、今回は前回の続きでFail2banの話題を取りあげるつもりでしたが、今月の頭からNHKのネットラジオ「らじる☆らじる」の配信形式が変更され、本連載で以前紹介したスクリプトでは録音できなくなったので、急遽そのための対応方法を紹介することにしました。

本連載ではNHKのインターネットラジオ「らじる☆らじる」をLinuxで録音する話題を何度か取りあげてきました。⁠らじる☆らじる」はNHKのラジオ番組をインターネットで同時配信する仕組みで、2011年にMMSとRTMPの2種類で配信が初まりました。

これを受け、当初はMMS配信をmplayerで録音するようなスクリプトを紹介しそのスクリプトを中心に予約録音するようなツール類を作りました。

「らじる☆らじる」の開始当時はWindows Media Playerで利用できるMMSの方が広く使われていたものの、マイクロソフト社によるMMSの開発は終了していたこともあり、配信方法の軸足は次第にRTMPの方に移動し、2015年の9月にはMMS配信が終了しました。そのため本連載でも、録音用スクリプトをrtmpdumpを使うように変更する話題を紹介しました。

それから2年経った今年の9月、今度はRTMP形式による配信が終了して、HLS(HTTP Live Stream)形式に変更され、rtmpdumpを使っていたスクリプトでは録音できなくなりました。そこで今回は、ffmpegを使ってHLS形式に対応することにしました。

RTMPからHLSへの移行

前節でも触れたように、RTMPはMacromedia社がフラッシュビデオのストリーミング配信用に開発した独自プロトコルで、2000年代初期のストリーミング配信に広く用いられていました。

RTMPは独自プロトコルを利用しているため、ブラウザから直接利用することはできません。そこで開発されたのがFlashPlayerプラグインです。

FlashPlayerには、動画再生機能だけでなく、ゲームやインタラクティブにやりとりするページを作るためのさまざまな機能が詰め込まれており、そのような高機能性が災いして、それら機能のセキュリティホールを悪用するマルウェアがしばしば流行しました。加えて、インターネットの中心がデスクトップPCからスマホ等のモバイルデバイスに移行するにつれ、メモリや電池を浪費しがちなFlashPlayerは敬遠されるようになりました。

加えて、新しく策定されたHTML5の仕様では、FlashPlayerが提供していた機能の多くが仕様の中に取り込まれ、ブラウザと標準化されたプロトコルのみで動画再生やリッチコンテンツを提供できるようになり、FlashPlayerの必要性は低くなりました。

そのような環境変化を踏まえ、Adobe社自身、2020年でFlashPlayerのサポート終了を宣言しています。今回、⁠らじる☆らじる」がRTMP配信を終了したのも、そのような状況を踏まえてのことでしょう。

一方、新しく採用されたHLS(HTTP Live Streaming)は、Apple社が開発したストリーミング配信用のプロトコルで、動画や音声のデータを10秒程度の塊に分割し、それらをHTTP経由で順番に転送することでストリーミング再生を実現しています。

HLSの場合、その名の通り、データをやりとりするのはHTTPで、RTMPのように専用のサーバやクライアントを用意する必要はなく、標準的なWeb環境のみでストリーミング再生ができることもマルチデバイス対応が求められる「らじる☆らじる」には魅力的だったのでしょう。

実のところ、HLSへの移行は今年の9月になって急に行なわれたわけではなく、1年ほど前から始まったカルチャーラジオ等の過去の番組を配信する「ストリーミング配信」はHLS形式になっていました。最近ではこの機能が「聞き逃し番組」の再視聴として範囲を広げ、多くの番組が放送終了後一ヶ月程度の間HLS形式でストリーミング配信されており、⁠らじる☆らじる」のHLS化もその流れの一部と考えた方がよさそうです。

HLSについて

さて、それではこのHLS形式についてもう少し詳しく見て行きましょう。前節で触れたように、HLSでは動画ファイルを10秒程度の塊に分割し、それらを順に送信することでストリーミング配信を実現しています。このような処理を実現するには、分割された動画ファイルを順序正しく送受信することが重要になります。

「複数の動画ファイルを指定した順に連続して再生する」という処理は、各種メディアプレイヤーが利用している「プレイリスト」と同じだし、同じ形式にしておけばメディアプレイヤーがHLS形式に対応するのも簡単だろう、HLSの開発者たちはこう考えて、分割したデータの再生順をM3Uと呼ばれるプレイリストのフォーマットで記述することにしました。

M3U形式を採用することで、メディアプレイヤーの側にとっても、⁠指定されたURLから得たファイルがプレイリストだった場合、そのプレイリストに記述された順番に分割された動画ファイルを入手、再生する」という処理を追加するだけでストリーミング再生に対応できることになります。

少し先回りになりますが、HLS形式のNHK FM(東京)の配信元は⁠https://nhkradioakfm-i.akamaihd.net/hls/live/512290/1-fm/1-fm-01.m3u8なので、まずはこのファイルをwgetで取り寄せてみましょう。なお、このファイルの拡張子は"m3u8"となっていますが、これは「M3U形式のファイルの中身をUTF-8で記述している」ことを意味します。もっとも、⁠らじる☆らじる」の場合は以下に見る通りファイルの中身には英数字しか使っていないため、文字コードの種類による影響はありません。

$ wget https://nhkradioakfm-i.akamaihd.net/hls/live/512290/1-fm/1-fm-01.m3u8
 --2017-09-27 08:21:09--  https://nhkradioakfm-i.akamaihd.net/hls/live/512290/1-fm/1-fm-01.m3u8
Resolving nhkradioakfm-i.akamaihd.net... 61.213.189.240, 61.213.189.250
Connecting to nhkradioakfm-i.akamaihd.net|61.213.189.240|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 965 [application/x-mpegURL]
Saving to: ‘1-fm-01.m3u8’

1-fm-01.m3u8             100%[================================>]     965  --.-KB/s    in 0s         
2017-09-27 08:21:09 (138 MB/s) - ‘1-fm-01.m3u8’ saved [965/965]

このファイルの中身は以下の通り、再生すべき動画ファイルが順番に並んでいるだけです。

$ cat 1-fm-01.m3u8 
#EXTM3U
#EXT-X-VERSION:2
#EXT-X-TARGETDURATION:11
#EXT-X-MEDIA-SEQUENCE:222425
#EXTINF:10,
1-fm-20170831T143716-01-111/425.ts
#EXTINF:10,
1-fm-20170831T143716-01-111/426.ts
#EXTINF:10,
1-fm-20170831T143716-01-111/427.ts
#EXTINF:10,
1-fm-20170831T143716-01-111/428.ts
....

ここで指定されている "425.ts" 等のファイルは、それぞれが10秒ごとに分割された動画ファイル(⁠⁠らじる☆らじる」の場合は音声のみですが)で、MPEG2 TS(Transport Stream)という形式になっています。試しに、そのうちの1つのファイルだけをダウンロードしてみます。

$ wget https://nhkradioakfm-i.akamaihd.net/hls/live/512290/1-fm/1-fm-20170831T143716-01-111/439.ts
--2017-09-27 08:26:42--  https://nhkradioakfm-i.akamaihd.net/hls/live/512290/1-fm/1-fm-20170831T143716-01-111/439.ts
Resolving nhkradioakfm-i.akamaihd.net... 61.213.189.240, 61.213.189.250
Connecting to nhkradioakfm-i.akamaihd.net|61.213.189.240|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 87232 (85K) [video/MP2T]
Saving to: ‘439.ts’

439.ts                   100%[================================>]  85.19K  --.-KB/s    in 0.007s    
2017-09-27 08:26:42 (12.4 MB/s) - ‘439.ts’ saved [87232/87232]

このファイルは、86KBほどのMPEG TSファイルです。

$ ls -lh 439.ts 
-rw-r--r--+ 1 kojima users 86K  9月 27日  08:26 439.ts
$ file 439.ts
439.ts: MPEG transport stream data

このファイルのみをsmplayerで再生することも可能で、ファイルのプロパティを表示させると「10秒の長さでAAC形式の音声を持つ動画ファイル」なことがわかります。

図1 ダウンロードしたデータの断片1つを再生
図1 ダウンロードしたデータの断片1つを再生

一方、先に紹介したM3UファイルのURLを直接指定してsmplayerを起動すると、smplayerは指定されたURLがプレイリストであると判断して、その記述に従って分割されたファイルを次々とダウンロードし、再生してくれます。

$ smplayer https://nhkradioakfm-i.akamaihd.net/hls/live/512290/1-fm/1-fm-01.m3u8
QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/runtime-kojima'
QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/runtime-kojima'
Debug: global_init
...
Debug: BaseGui::open: 'https://nhkradioakfm-i.akamaihd.net/hls/live/512290/1-fm/1-fm-01.m3u8'
Debug: Core::open: 'https://nhkradioakfm-i.akamaihd.net/hls/live/512290/1-fm/1-fm-01.m3u8'
...
Debug: Core::startMplayer: checking if stream is a playlist
Debug: Core::startMplayer: url path: '/hls/live/512290/1-fm/1-fm-01.m3u8'
Debug: Core::startMplayer: url_is_playlist: 1
Debug: Core::startMplayer: URL extension: "m3u8"
Debug: InfoReader::setPlayerBin: mplayerbin: "/usr/bin/mpv"
...
図2 M3UファイルのURLを指定したストリーミング再生
図2 M3UファイルのURLを指定したストリーミング再生

radiru_rec.pyスクリプトの修正

さて、HLSの扱い方がだいたいわかったので、録音用スクリプトを生成するradiru_rec.pyを修正して、HLSに対応してみましょう。今回はダウンロードにffmpegを使うことにしました。

まず、ffmpegでHLSをダウンロードできるか確認します。HLSで配信される音声データはAAC(Advanced Audio Codec)でエンコードされており、それをHTTPS経由でダウンロードすることになるため、受信側にもAACとSSLの機能が必要になります。

まずはffmpegの入力元(-i オプション)にNHK FMのURLを指定して、30秒分ほどデータをダウンロードしてみます。

$ ffmpeg -i https://nhkradioakfm-i.akamaihd.net/hls/live/512290/1-fm/1-fm-01.m3u8 -t 30 -movflags faststart -c copy -bsf:a aac_adtstoasc test.m4a
ffmpeg version 3.3.2 Copyright (c) 2000-2017 the FFmpeg developers
  built with gcc 7.1.0 (GCC)
  configuration: --prefix=/usr --enable-gpl --enable-version3 --enable-nonfree --disable-static --enable-shared --disable-debug --enable-libass --enable-libfdk-aac --enable-libfreetype --enable-libmp3lame --enable-libopus --enable-libtheora --enable-libvorbis --enable-libvpx --enable-libx264 --enable-libx265 --enable-openssl
...
[hls,applehttp @ 0x670340] Opening 'https://nhkradioakfm-i.akamaihd.net/hls/live/512290/1-fm/1-fm-20170831T143716-01-111/1946.ts' for reading
Input #0, hls,applehttp, from 'https://nhkradioakfm-i.akamaihd.net/hls/live/512290/1-fm/1-fm-01.m3u8':
  Duration: N/A, start: 13154.032222, bitrate: N/A
  Program 0 
    Metadata:
      variant_bitrate : 0
    Stream #0:0: Audio: aac (HE-AAC) ([15][0][0][0] / 0x000F), 48000 Hz, stereo, fltp
...
Press [q] to stop, [?] for help
[hls,applehttp @ 0x670340] Opening 'https://nhkradioakfm-i.akamaihd.net/hls/live/512290/1-fm/1-fm-20170831T143716-01-111/1947.ts' for reading
[hls,applehttp @ 0x670340] Opening 'https://nhkradioakfm-i.akamaihd.net/hls/live/512290/1-fm/1-fm-20170831T143716-01-111/1948.ts' for reading
[ipod @ 0x6c47e0] Starting second pass: moving the moov atom to the beginning of the file
size=     180kB time=00:00:29.99 bitrate=  49.0kbits/s speed= 192x    
video:0kB audio:176kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 1.971487%

上記コマンドラインのオプションとして指定している -t 30 は30秒間録音する指示、-movflags faststartは動画サイズや再生時間などのメタ情報を記録している"moov atom"と呼ばれる領域をファイルの先頭部分に移動させる指示、-bsf:a aac_adtstoasc はMPEG TS形式用のAACデータをMP4形式用に変換する指示です。こうしてダウンロードしたファイルは180KBほどのAACコーデックの音声データと認識されました。

$ ls -lh test.m4a 
-rw-r--r--+ 1 kojima users 180K  9月 27日  12:38 test.m4a
$ file test.m4a 
test.m4a: ISO Media, Apple iTunes ALAC/AAC-LC (.M4A) Audio

ffmpegでデータを正しく受信できたので、次はチャンネルの設定です。⁠らじる☆らじる」の各チャンネルのURLはconfig_web.xmlというファイルに記載されています。このファイルを見ると、札幌、仙台、東京、名古屋、大阪、広島、松山、福岡のそれぞれに、ラジオ第一と第二、FMの各URLが用意されているようです。

config_web.xmlファイル
<radiru_config><!-- お知らせ -->
<info>/radio/include/oshirase.txt</info>
<!-- 各地域のストリームURL -->
<stream_url>
  <data>
    <areajp>札幌</areajp>
    <area>sapporo</area>
    <apikey>700</apikey>
    <areakey>010</areakey>
    <r1hls>
      https://nhkradioikr1-i.akamaihd.net/hls/live/512098/1-r1/1-r1-01.m3u8
    </r1hls>
    <r2hls>
      https://nhkradioakr2-i.akamaihd.net/hls/live/511929/1-r2/1-r2-01.m3u8
    </r2hls>
    <fmhls>
      https://nhkradioikfm-i.akamaihd.net/hls/live/512100/1-fm/1-fm-01.m3u8
    </fmhls>
  </data>
  <data>
    <areajp>仙台</areajp>
...
    <r1hls>
      https://nhkradiohkr1-i.akamaihd.net/hls/live/512075/1-r1/1-r1-01.m3u8
    </r1hls>
    <r2hls>
      https://nhkradioakr2-i.akamaihd.net/hls/live/511929/1-r2/1-r2-01.m3u8
    </r2hls>
...

これらの情報が揃えば、後はradiru_rec.pyのrtmpdumpを使っていた部分をffmpegに直して、各チャンネルのURLを更新するだけです。ついでなのでラジオ第一は大阪放送局も指定できるようにしてみました。

206      if channel == 'r1':
207          url = 'https://nhkradioakr1-i.akamaihd.net/hls/live/511633/1-r1/1-r1-01.m3u8'
208      elif channel == 'r2':
209          url = 'https://nhkradioakr2-i.akamaihd.net/hls/live/511929/1-r2/1-r2-01.m3u8'
210      elif channel == 'fm':
211          url = 'https://nhkradioakfm-i.akamaihd.net/hls/live/512290/1-fm/1-fm-01.m3u8'
212      elif channel == 'r1_kansai': 
213          url = 'https://nhkradiobkr1-i.akamaihd.net/hls/live/512291/1-r1/1-r1-01.m3u8'
214      else:
215          print("channel set error:{0}".format(channel))
216          usage()
217          sys.exit(1)

実際に音声データをダウンロードする部分は、先に指定したオプションをそのまま使う形にしました。

229      lines.append('#!/bin/sh')
230      lines.append('m4afile={0}/{1}.m4a'.format(musicdir, title))
231      lines.append('( ffmpeg -i {0} -t {1} -movflags faststart -c copy -bsf:a aac_adtstoasc  $m4afile ) &'.format(url, sduration))
232      lines.append('sleep 1m')

rtmpdumpを使っていた旧バージョンでは、ダウンロードしたファイルはフラッシュビデオの形式になっていたため、録音終了後、ffmpegを使ってm4a形式に変換する処理が必要でした。一方、HLS経由でダウンロードしたファイルはそのままm4a形式になっているので、その処理を省くことができ、録音用スクリプトは少し短くなりました。

こうして修正したHLSに対応したradiru_rec.pyの新バージョンは、筆者のブログのページに添付しておきましたので、興味ある方はそちらの方から入手してください。


RTMPからHLSに変ってまず気づくことは、各番組の開始時刻が30秒程度遅れることです。RTMP以前に使っていたMMS経由の録音でもほぼ同じ程度の遅延が生じていたので、RTMP(Real Time Messaging Protocol)はその名の通り、リアルタイム性には優れていたのだなぁ、と、改めて感心しました。

また、RTMP配信のころは録音失敗がほとんど無かったのに対し、HLS配信になってからは週に1、2度は録音に失敗していることも気になります。これは、⁠⁠らじる☆らじる」を直接聞いている際にも「⁠メディアがダウンロードできません」旨のエラーになって接続が中断する例を何度か経験したので、録音スクリプトの問題ではなく、サーバの負荷や通信経路の輻輳等に起因する問題のように思います。この問題はクライアント側では如何ともし難いので、⁠らじる☆らじる」がHLS配信の経験を積み重ねて信頼性を改善していくことを期待しています。

おすすめ記事

記事・ニュース一覧