Raspberry Pi ZeroでBluetooth A2DP接続可能なFMトランスミッタを拵える、試み

- 購入品

こいつ買いました。ヒートシンクはケースを固定してから貼り付けたほうが良いでしょう。ネジ穴に遊びがあるので、先にヒートシンクを固定するとケースのネジが入らなくなることもあります。HDMIケーブルはMiniでした。

 

- 準備

Rasbian BusterのイメージをSDに焼いてブートします。GUIが立ち上げるのでマウスを接続して、またあるときはキーボードを接続してネットワークの設定を終えます。

メインメニューからRasbianの設定つうとこで、次回ブート以降はCUISSH有効としておいてからリブート。こっから先は全てSSH経由で行います。

 

- 注意

あちこちのサイトにGPIOに20cmの電線つなげと書いてますが、やっちゃ駄目です。久しぶりに太字にしたな。もう一度。やっちゃ駄目です。

野良FM局として認められる出力を簡単に超えちゃいます。アンテナは接続せず、受信側のラジオのアンテナを近づけてテストしましょう。

asliceofraspberrypi.blogspot.com

もうひとつ、テストが終わったらraspiはshutdownするかrebootしましょう。じゃないと搬送波が出力されっぱなしです。

私は最終的にFMトランスミッタとしての使用を諦めました。そうで無い方はバンドパスフィルタを入れたほうが安全でしょう。GPIOを矩形波で駆動しているので奇数次の高調波がモリモリ出てます。飽くまでも自分で何やってるか分かってる人が自己責任でやっちゃってつかあさい。人に迷惑をかける前にバカは帰ってください。

 

- 情報収集

お題目のようなことは誰かが何処かでもうやっちまってるんでパクりゃあええんです、パクりゃあ。

mickey-happygolucky.hatenablog.com

割と分かりやすかった。誰にでもわかるBluetooth A2DPの拵え方。PulseaudioでSinkを作ってます。で、ほぼこの通りにA2DPレシーバを設定して、pifmで飛ばしてみたのだが、あまり芳しくなかった。Pulseaudioのバッファリングに問題があるのだろうと予想するが、どもっちゃうんだ、吃音、stutter。

実際、/etc/pulse/daemon.confのrealtime-scheduling、resample-method、avoid-resampling、default-fragments、default-fragment-size-msecをいじくり回すとやや改善するんだが、数十秒に一度程度の頻度で吃音しよる。

Pulseaudioって便利に使ってるけど、造りが大仰な分レスポンス悪いイメージだよねえ。しかし、我々にはALSAがあるさ。しかーし、

stackoverflow.com

質問は2012年、回答が2014年、フォローが2017年というご長寿で且つ示唆に富むスレッド。回答の中にあったリンクが面白かったので読んでみよう。

From the desk of James - Powered by SJPPLOG

 読んだ?隔靴掻痒感がひっしひしと伝わってくる。ともかく、bluez4から5にバージョンアップした際にA2DPシンク対応は外されたらしい。んで、話にには続きがあって、

From the desk of James - Powered by SJPPLOG

/etc/bluetooth/audio.confのSocketってどういう意味だよ、ってんで分かんねえんだよ、資料は何処だよ、つって騒いでいる。bluezはそもそもIntel主導で開発が始まり、その後Nokiaに渡り、と出生がややこしい。んで、必要な資料さえままならない。

加えて、SourceとSinkの考えが混乱している。Sinkが欲しいのに、audio.confにはSourceと書かなきゃならない理由がはっきり書いてあった。頭おかしい。

で、時は流れて。bluealsa (かつてはbluez-alsa)が生まれた。

qiita.com

このとおりにやって上手く行く人は上手く行くでしょう。そうじゃない方は恐らく、bluetoothctl showとやってもSinkが列挙されていないのではないでしょうか。手持ちのスマートフォンからRaspiのbluetoothが見えていて、connectボタンを押せるのに、接続されたかと思うとすぐに切断される。bluealsaのサービスがデフォルトではSinkが有効になっていないんです〜、おー怖っ。/lib/systemd/system/bluealsa.serviceをさっさと書き換えましょう。

 

[Unit]
Description=BluezALSA proxy
Requires=bluetooth.service
After=bluetooth.service

[Service]
Type=simple
User=root
ExecStart=/usr/bin/bluealsa -p a2dp-sink

 

 - 使えるのか?

bluetooth A2DP経由でpifmへ流し込んでFM放送できます。でも使えません。pifmが音痴なんです。CPUに負荷が掛かるとdistortion、歪が耳障りなレベルで発生します。

Clock output & FM transmitter - Raspberry Pi Forums

pulseaudioの時は吃音がひどくて歪には気が回りませんでしたが、確認したら音痴でドモリでした。

音楽を聞かないのならありかも。

 

 

 


金属バットで殴られた

ええ歳こいてM1観てたんですわ、ええ。敗者復活戦からがっつり、ビール呑みながら。いやあビックリしたわ、金属バット。おもれーやん。

がっつり泉州よりの関西弁に斬新なツッコミ。「おらおらよいっしょー」で終わる漫才初めて観た。知らんかったわこんなん居ったって。関西に住んでればローカルの番組で見知る機会もあっただろうにと思うと悔やまれる。しかも堺出身ときてるし、知ってれば応援してただろうと思うとこれまた悔やまれる。

あき竹城とみちょぱイジりも最高に良かった。「ギャルのネエチャンなんか云うてくんなはれやー」だと。

豊橋市・豊橋市民のクソなところ、クソである理由

ここへ移り住んでからはや三年になるが、未だに慣れないし、他所で経験しないような嫌な思いをする。水が合わないでは済まされない、ここに跋扈する人間の邪悪さが原因だと最近気づいた。

ごみゼロ運動

Wikipediaによれば、豊橋市で1975年に始まった運動でその後全国に拡がる、とある。これが全く余計なお世話。ゴミ分別が面倒になっただけで誰も得をしない。分別したプラスティックは結局熱量を稼ぐために可燃ごみと一緒に焼却されていると聞く。運動を始めた当人らは良いことをしたと満足だろうし、そこに悪意がなかったことも想像できる。しかし、「世のために良いことをしよう」というのと「世のために良いことをさせよう」は大きく違う、いやむしろ全く逆だろう。

ゴミを減らしたければ粛々とやれば良いのだ。家族や周りに啓蒙するのも良いだろう。もったいないからね、ごみは減らそうね、と。

私が正しいと思ってることだし、論理的に考えても正しいから、周りの人にも強制しよう、となってくると面倒くさい。要するに「私」が正しいことを貫くために「他人」に不自由や面倒を押し付けることを良しとしている。ここがクソだ。蒙昧且つ横暴だ。

一体誰が始めたのか。多分青年会議所だ。クソ田舎町の社長のボンが考えそうなことだ。烏合の衆以下。単に馬鹿なのは犯罪ではない。人に迷惑さえ掛けなければ馬鹿でも阿呆でもよろしい。影響力の有る行動力の有る馬鹿は困る。しかもそういう人たちは大抵自覚がない。

そもそもプラスティックは再生できないと考えたほうが良い。PPだPEだPETだPOMだを一般主婦は選別できない。選別されてないプラスティックゴミは熱源にしかなりようがない。

生活すりゃあゴミが出るんだよ。そんなにゴミが嫌なら自ら焼却炉に飛び込んでくれ。

クソなメンタリティ

豊橋市民は横着な人間が多い。ちょっとスーパーに買い物に行くのに自動車を利用する。街なかのスーパーは駐車場が限られているので駐車待ち時間が生じることも有る。んで、横着な豊橋市民はちょいと路駐するわけだ。割と迷惑な場所に平気で。

これだけでも歩行者の私としては邪魔だなあと感じる。先日もそうだった。買い物をしていると店内放送で「路駐の取り締まりに来ているので表に駐車している方はお車を移動させて下さい」とやっておる。犯罪の幇助だ。一言言う必要が有るだろう。

サービスカウンターで先ほどの放送に不満を述べると責任者が出てきた。「なぜ先のような放送をするのか」と問うた。もちろん「なぜ」が知りたいわけではない、「ばかやろう、そんな放送すんじゃねえ」が真意だ。責任者曰く「なぜと申されましても…」だと。馬鹿だ盆暗だトンチキだ。路上駐車をする客の便宜を図るのは犯罪の幇助であること、路上駐車に周辺住民・歩行者は迷惑していること、地域と共になどと社是を掲げているが矛盾していることを伝えて帰った。しかし、この責任者はすみませんの一言もないし、頭を下げることもなかった。お前さんの言うことは訊いてはやるがしらねえよということか。

上はほんの一例だ。軽犯罪・他人への迷惑を屁とも思ってない市民、犯罪の幇助をする企業、まごころのない接客。どれもクソだが、この状況で上のような横柄な接客をするメンタリティに一番問題を感じる。

路上駐車は豊橋市特有ではないし、あまり無いだろうがそれを幇助するスーパーも余所にも有るかもしれない。ただ、この責任者の接客態度は豊橋市オリジナルだ。

ここ豊橋市では、上のような違和感と嫌悪感が一緒になったような思いを頻繁に経験させられる。ハナシ通じてんのかなあ、気味悪いなあ、馬鹿なのかなあといった感じだ。そうゆうヤツが多い理由があるはずだ。

全国比で外食が三割不味い

以前にも書いたが、

外食すると、値段の割にマズいというか、味としつらえの割に高価い。だいたい三割。

自家用車がないと非常に不便な町で、老若男女問わず、皆クルマを生活の軸にしておる。生活必需品としてのクルマなんだが、見えっ張りな県民性が災いし、必需品に贅沢品の予算を割いていると見られる。衣食住以外に「輪っぱ」の経費が同程度に掛かるわけだ。

チェーン店は別だが、ちくわシティーでは、個人経営の寿司、割烹、小料理なんかは、値段に「輪っぱ」が乗っていると思って良い。衣食住輪 ÷ 衣食住 ≒ 1.3、つまり、三割マズかったり、高価かったりな理由だ。

そんな店、潰れるだろ、と思うのは当然。だが、ここはちくわシティー。田舎には腕の良い料理人は寄り付かないので、旨いもんを喰う機会もない。歴史も伝統もなく、しつらえが理解できない。金は持ってても、舌も感性も貧乏なままなのだ。そのうえ見えっ張りなので、高価けりゃええだろってなもんで、割高な舌代に疑問も抱かず喜んで金を出す。

と、ここまでは前に書いたまんま。で、不味い店で嬉々として金銭を授受している奴らを見てふと考えた。なんでこんなに金持ってんだと。どうみても工場労働者、歳は20代前半。

答えはトヨタだ。一流企業の課長・部長並の賃金を職工にくれてやるからこうなる。若くてうまいもん喰う暇もチャンスも無いまま田舎町で無駄に歳を重ね、値ごろが判らないまま腹が膨れた分だと信じて払えるから払う。同情を禁じ得ない。

もうひとつの答えは街道筋だ。元々東海道沿いの宿場町として栄えた町だ。黙ってても暴利でも客は来る。不味いぞ暴利だぞと文句言われても知らん顔してれば良い。その客が二度と来ないだけで痛くも痒くもない。まあそういう商売をする土地柄と人柄なんだ。

上のふたつが奇跡のコラボ。暴利の店に金を払い続ける蒙昧な客。不味い店だらけな理由だ。

分断されたままの市街

まあ、地図を見て欲しい。


JR豊橋駅の東側が豊橋市民の考える繁華街なわけだが、西側にも線路に張り付くように飲食店が存在する。これは戦後のどさくさバラックの名残だ。概して衛生状態は悪く、真っ当な人間は近づかないほうが良いと個人的には思う。これより西は徒歩で行くような店舗・施設は無い。

駅東の繁華街は旧街道をおおよその北限とし、以北は豊川が大きな障害となって河川北側の下地と分断されている。地名から分かる通り元々は大水を逃がすためのバッファであり、人は住まわず田畑だけだった。近年はそこを開発して住宅地として販売しているようだ。住人は豊橋市民の自覚は少なく、アクセスの良い豊川市の商業施設へ乗用車で出入りする生活を送る。

JR豊橋駅、豊川の他にもうひとつ。水上ビルが町を分断している。牟呂用水を暗渠にし住居兼店舗を建設、市内に散見されていた闇市業者をそこへ入居させ大掃除をしたのだ。

暗渠が真下にあるのでネズミが多く、ビルの老朽化も有り、衛生状態は概して悪い。真っ当な人間は近づかないほうが良いと個人的には思う。これより南は住宅街、郊外型施設など。用事がない。

市内の繁華街に伸び代がない以上、水上ビルが町を分断しているとは言えないかもしれない。ただ観方を変えるとこの水上ビルは豊橋市の政治が分断されていることの象徴に見えてくる。

誰が豊橋市の影のドンかと問えば誰もがジンノだと応えるだろう。このジンノが牟呂用水を引っ張ってきた神野金之助の末裔であり、コングロマリット・サーラの本丸だ。

無能な市議会議員は事有る度にサーラ参りをしてお伺いを立てる。豊橋市民の代弁をすべき議員が職務を全うしていないのだ。これが私の言う政治の分断だ。結果、市民図書館は意地悪な場所に、駅南のホールの出来は微妙以下の失敗作と悪手は枚挙に暇がない。責任を問うことは難しいだろうが、全く無関係ということもないだろう。*1

美意識の欠如

目ぼしいところは市内くまなく歩いたが魅力的な町並みがない。なんというか「雑」なのだ。いろいろ雑。人口密度も低く開発を邪魔する遺跡の類もないのだからスッキリ小奇麗な町並みをつくるのは難しくないだろうに。「シンプルなのにいろいろ雑」という奇跡的な居心地の悪さなのだ。

とそんなことは意に掛けない豊橋市民の美意識はどうなっているのか。格別に低い。どこの街にもローカルで活躍するデザイナーが居て、店舗や看板などの意匠を手がけているのが世間の常だが、ここ豊橋市には殊更レベルの低いデザイナーが居てしかも幅を効かしている。察かに低レベルで人に依っては嫌悪感すら感じるデザインであるのに街のあちこちで見る。私は吐き気がするので目を逸らすことにしている。

また、老朽化したビルの壁面を美大生やボランティアに頼んで壁画を描く、というのも余所の街でも見かける光景だ。ここ豊橋市で見かけるそういう壁画はほぼ同じ素人画家が描いており、これがまた酷い。私は吐き気がするので目を逸らすことにしている。

なぜこんなことになったのか。答えは簡単だ。豊橋市民には美意識が欠如しているからだ。

*1:などと悪口書いてる間に死んでしまったらしい。なむ。

貧者のcomskip (3) - ffmpegのshowinfoがウソつきな件

無音検出とシーンチェンジをffmpegのフィルターでこなしてたんだが、showinfoフィルターのptsとpts_time、TBが整合しない例が出てきた。ffprobeやffmpegではTBは90Kと表示されている。

  Duration: 01:54:07.91, start: 52853.131111, bitrate: 15803 kb/s
  Program 151 
    Metadata:
      service_name    : ?BS?D+F|?1
      service_provider: ?|����D+F|
    Stream #0:0[0x140]: Video: mpeg2video (Main) ([2][0][0][0] / 0x0002), yuv420p(tv, bt709, top first), 1440x1080 [SAR 4:3 DAR 16:9], 29.97 fps, 29.97 tbr, 90k tbn, 59.94 tbc
    Stream #0:1[0x141]: Audio: aac (LC) ([15][0][0][0] / 0x000F), 48000 Hz, stereo, fltp, 199 kb/s


で、showinfoの方は、

[silencedetect @ 0x33d4880] silence_start: 59698.4
[Parsed_showinfo_2 @ 0x3621600] n: 979 pts:10745785784 pts_time:59698.8 pos:1352
3154900 fmt:rgb24 sar:4/3 s:1440x1080 i:P iskey:0 type:P checksum:094E5428 plane
_checksum:[094E5428] mean:[138] stdev:[65.5]
[Parsed_showinfo_2 @ 0x3621600]   side data - pan/scan
[Parsed_showinfo_2 @ 0x3621600]   side data - stereoscopic information: type - 2D
[Parsed_showinfo_2 @ 0x3621600]   side data - unknown side data type 17 (6188 bytes)
[Parsed_showinfo_2 @ 0x3621600]   side data - unknown side data type 16 (8 bytes)
[Parsed_showinfo_2 @ 0x3621600]   side data - unknown side data type 12 (8 bytes)
[silencedetect @ 0x33d4880] silence_end: 59699.3 | silence_duration: 0.959667

となる。検算してみると、pts÷pts_time=TBNになるはずなんだが、

10745785784 ÷ 59698.8 = 180000.029883348


となり、倍の値が出てしまう。ptsが嘘なのか、TBが嘘なのか分からない。しょうが無いので、自分で計算することにした。ffmpegのログファイルを喰わせて、最後のshowinfoのptsとpts_timeで計算する。

TB=$(grep Parsed_showinfo "$TEMP" | grep checksum | tail -n 1 | sed -r 's/^.*pts:\s?(.*) pts_time:\s?([0-9.]*) .*$/\1 \2/g' | awk '{printf "%d", $1 / $2}')

貧者のcomskip (2) - CMの断片をかき集める

ffmpegのフィルターでほぼシーンを分割することに成功したが、ややオーバキルなことには触れた。断片をかき集めることに諦めて止めたことにも触れた。でもやっぱり止めたん止めた。短い断片のシーンチェンジ率が跳ね上がってあまり宜しくなかったのだ。仕方がない。

CMは概ね15秒、30秒、60秒、120秒の長さが有るようだ。5秒とかは番宣。で、15秒のCMの長さの分布を取ってみると、3σで0.08455329秒だった。つまり長さtは、99.97%の確率で、14.91544671 < t < 15.08455329であるということだ。2フレームちょいの誤差が有るということだ。

で、隣り合う15秒より短い断片をいくつか足しあわせて、上の条件に合うものを併合すれば良いような気がする。前回の手続きを下のように改訂する。

  1. 無音部分の開始時間と終了時間を取得する
  2. シーンチェンジの時間を取得する
  3. シーンチェンジが無音期間中だったらこれを編集点とする
  4. 15秒より短い断片を検査し、併合する
  5. 30秒より短い断片を併合するが、断片の併合で生じた断片は対象外とする
  6. 60秒より短い断片を併合するが、断片の併合で生じた断片は対象外とする
  7. 加えて、編集点と編集点の間のシーンチェンジの回数を数え、編集点の時間間隔で割り算しこれをシーンチェンジ率とする


上の方法でアニメ8話分のデータをとってみた。楽勝っぽいやろ。多分同じ番組だったら楽勝だな。自前のパーセプトロンマシンがそう語っておる。というか、この例に限れば、ldaもqdaもsvmも要らない。それぞれにしきい値を与えれば済む。もう少し凝って、上に開いた放物線を書いてしきい値としてもうまく行くだろう。


要するに、

  • 本編のシーンチェンジは、10秒に1回前後
  • 本編が長くなるに連れてシーンチェンジ率は値に幅が出てくる
  • 本編に比べCMは高いシーンチェンジ率を持つ


ということだ。そらそうだわな、金払って15秒しか無いのに言いたいことはたくさん有るとき、シーンの繋ぎは増えるわな。本編は番組のテンポっつうものが支配しているが、緩急もつけまっせつうことか。

貧者のcomskip (1) - パラメータ多すぎてわかんねえ

erikkaashoek:Comskipを使ってはみたものの、パラメータが多すぎて調整しきれないし、吊るしだと誤爆しまくりだし、具合が良くない。コマーシャルとはナンゾやと考えこんでしまった。

私の場合、録画するのは自称公共放送のネコとか紀行物とか、民法BSの紀行物、たまにアニメとか。このうち、自称公共放送のやーつは最適解が見つかり、自動でカット・エンコしている。で、他をどうするかなんだが、いろいろ試したが誤爆が多い、カットする場所が不正確だとかで使いたくない。じゃ、ツモルしか無いねと。

  1. 先ずは編集点の検出が必要。本編とCMの境目、CMとCMの境目の編集点を検出する
  2. 編集点を区切りとして、録画素材の特徴量を抽出する
  3. 特徴量を判断材料にして、本編なのかCMなのか判別する


なんだ、簡単ではないか。

編集点の検出にはffmpegのフィルターを使う。ビデオフィルターと音声フィルターの両方を使う。シーンチェンジの出力からpts_timeを引っ張ってそのまま時刻として使っていたが、桁が足りないことに気づいて、ptsからTB決め打ちで計算させている。フレームアキュレートなはずだ。

$FFMPEG -i "$TS" -filter:v "select='gt(scene,0.375)',showinfo" -filter:a "silencedetect=noise=0.0001:duration=0.05" -f null - 2>


シーンチェンジと無音部分を検出している。無音で且つシーンチェンジしたら、そこが編集点じゃねえかっつうことだ。無音は-80dB、0.05秒を基準としたが今のところうまく行っている。行儀よく編集するとそのくらいの無音が入るんだと思う*1ffmpegの出力はシーンチェンジも無音検出も一緒くたに吐いてくる。これを一時ファイルに置いて、そこからシーンチェンジを検証していく。

  1. 無音部分の開始時間と終了時間を取得する
  2. シーンチェンジの時間を取得する
  3. シーンチェンジが無音期間中だったらこれを編集点とする
  4. 加えて、編集点と編集点の間のシーンチェンジの回数を数え、編集転換の時間間隔で割り算しこれをシーンチェンジ率とする


これだけだ。編集点の開始時刻、シーンチェンジ率(毎秒)、シーケンスの長さ(秒)を出力するスクリプトを書いた。恥を覚悟で晒しておく。こんな長いのん初めてで破綻寸前だわ。で、こんなんが出力される。どあたまはファイル名のハッシュ。他にもいろいろ出力されてるけど後述。

md5sum  start   position        duration        rate    max_amp min_amp mid_amp mean_norm       mean_amp        rms_amp max_delta       min_delta       mean_delta      rms_delta       freq    vol_adj content
...snip
734adedd96ed3dc88bcf77fcdf2fd699        1510.046956     .835475 150.016533      .286635 0.383292        -0.336926       0.023183        0.029047        -0.000001       0.042969        0.314083        0.000000        0.010624        0.019106        3396    2.609   "p"
734adedd96ed3dc88bcf77fcdf2fd699        1660.063489     .918476 14.981633       .066748 0.372015        -0.336926       0.017544        0.029173        0.000013        0.043015        0.312446        0.000000        0.015749        0.024716        4389    2.688   "c"
734adedd96ed3dc88bcf77fcdf2fd699        1675.045122     .926765 15.015000       .466200 0.372015        -0.336926       0.017544        0.028238        0.000026        0.041929        0.312446        0.000000        0.013598        0.020932        3813    2.688   "c"
734adedd96ed3dc88bcf77fcdf2fd699        1690.060122     .935072 14.981634       1.334967        0.372015        -0.336926       0.017544        0.028167        0.000030        0.041846        0.312446        0.000000        0.013357        0.020510        3744    2.688   "c"
734adedd96ed3dc88bcf77fcdf2fd699        1705.041756     .943361 15.015000       1.065601        0.322565        -0.336926       -0.007181       0.028635        0.000035        0.042165        0.312446        0.000000        0.014033        0.021466        3889    2.968   "c"
734adedd96ed3dc88bcf77fcdf2fd699        1720.056756     .951669 29.996633       .100011 0.322565        -0.336926       -0.007181       0.029899        0.000044        0.043420        0.312446        0.000000        0.015578        0.022999        4046    2.968   "c"
734adedd96ed3dc88bcf77fcdf2fd699        1750.053389     .968265 29.996633       1.033449        0.311811        -0.336926       -0.012558       0.029218        -0.000014       0.042867        0.303896        0.000000        0.016110        0.024188        4310    2.968   "c"
...snip


4コラム目が切り出したシーケンスの長さ。ほぼ15秒おきにCMが切り出されているのが分かる。いつも綺麗に決まるわけではなく、ややオーバーキル気味、つまり、本編内の編集点やCM内の編集点も拾ってしまう。これら断片化した、本来ひとつづきのシーケンスを元に戻す方法も勘案したが、結局ロゴ検出などに頼る方法しか思いつかなかった。ま、それ以上考えずに諦めた。

ひと捻り入れて10倍位早くなったが、スクリプトは遅いっす。シーケンス長とシーンチェンジ率だけでCMカットが出来ないか、というアイデアの検証なので、手戻りが早いほうが良いでしょう。

#!/bin/bash
FFMPEG="/home/poco/bin/ffmpeg";

TS=$1;
DIR=$(dirname "${TS}");
BASE=$(basename "${TS}" .ts);
EDLFILE="${DIR}/${BASE}.edl";
METAFILE="${DIR}/${BASE}.ffmeta";
OUTFILE="${DIR}/${BASE}_CUT.ts";
TEMP="${DIR}/${BASE}.ffout";
AUDIOFILE="${DIR}/${BASE}.sox";
CLEAN=false;
DEBUG=false;

function validate(){

  # 無音のときにシーンチェンジしたらそこが境目
  silence_start=()
  silence_end=()
  silence_start=($(grep silence_start "$TEMP" | sed -r 's/^.*silence_start:\s?([0-9.]+).*$/\1/'));
  silence_end=($(grep silence_end "$TEMP" | sed -r 's/^.*silence_end:\s?([0-9.]+) .*$/\1/'));
  total_length=$(grep Duration "$TEMP" | awk '{print $2}' | sed 's/,//' | awk -F':' '{printf "%f", $1 * 3600 + $2 * 60 + $3}')
  $DEBUG && echo 'silence_start:' ${silence_start[*]}
  $DEBUG && echo 'silence_end:'   ${silence_end[*]}
  # startとendの数が同じかチェックする
  if [ ! "${#silence_start[@]}" = "${#silence_end[@]}" ]; then
    echo 'Not mached number of start/stop silence.'
    exit
  fi

  scene_change=($(grep Parsed_showinfo "$TEMP" | grep checksum | sed 's/^.*pts: *//' | awk '{printf "%f ", $1/90000}'));

  $DEBUG && echo 'scene_change:'  ${scene_change[*]}

  # 無音期間中にあるシーンチェンジを拾う
  valid=()
  n_scene_change=()
  r_scene_change=()
  scene_change_copy=("${scene_change[@]}") # for speeding up
  for index in ${!silence_start[*]}; do
    $DEBUG && echo $index: silence_start at ${silence_start[$index]}
    n_scene_change[$index]=0
    for i in `seq 0 ${#scene_change_copy[*]}`; do
      candy=${scene_change_copy[$i]}
      if [ -z $candy ]; then break; fi
      if [ `echo "$candy < ${silence_start[$index]}" | bc` == 1 ]; then
        unset scene_change_copy[$i]
        continue;
      fi
      if [ `echo "$candy > ${silence_end[$index]}" | bc` == 1 ]; then break; fi
      unset scene_change_copy[$i]
      $DEBUG && echo "  $candy"
      valid=( "${valid[@]}" $candy )
    done
    scene_change_copy=("${scene_change_copy[@]}")
    $DEBUG && echo "$index: silence_end at ${silence_end[$index]}"
  done

  # 前もってシーンの長さをチェックする
  valid=(0 "${valid[@]}" "$total_length") # 先頭と末尾に仕掛け
  scene_length=() # シーンの長さ
  $DEBUG && echo "number of validated scene change: ${#valid[*]}"
  novsc=${#valid[*]}
  merge=()
  let novsc--
  for index in `seq 1 $novsc`; do
    j=$index; i=$*2;
    $DEBUG && echo "$i: scene_start at ${valid[$i]}"
    sl=$(echo "${valid[$j]} - ${valid[$i]}" | bc)
    scene_length=("${scene_length[@]}" "$sl")
    $DEBUG && echo "  scene length: ${sl}"
    # 断片化したシーンを都合の良さそうな方へ統合する試み
    if [ $i -ge 2 ]; then
      k=$i; let k--;
      sl=$(echo "${scene_length[$k]} + ${scene_length[$i]}" | bc)
      $DEBUG && echo "  scene length: $sl"
      # echo "${scene_length[$i]} < 15"
      # echo "scale=6; ($sl / 15) < 0.1"
      if [ `echo "${scene_length[$i]} < 15" | bc` == 1 ]; then
        if [ `echo "($sl % 15) < 0.1" | bc` == 1 ]; then
        $DEBUG && echo "  scene marged: ${sl}"
        scene_length[$i]=$sl
        merge=("${merge[@]}" $k)
        fi
      fi
    fi
    $DEBUG && echo $i: scene_end at ${valid[$j]}
  done

  for index in ${merge[@]}; do
    unset scene_length[$index]
    unset valid[$index]
  done
  scene_length=("${scene_length[@]}")
  valid=("${valid[@]}")

  # 拾ったシーンでのシーンチェンジの回数を数える
  scene_change_copy=("${scene_change[@]}")
  n_scene_change=() # シーンチェンジの回数
  r_scene_change=() # シーンチェンジの頻度
  scene_length=() # シーンの長さ
  $DEBUG && echo "number of validated scene change: ${#valid[*]}"
  novsc=${#valid[*]}
  let novsc--
  for index in `seq 1 $novsc`; do
    j=$index; i=$*3;
    $DEBUG && echo "$i: scene_start at ${valid[$i]}"
    nosc=0
    for k in `seq 0 ${#scene_change_copy[*]}`; do
      candy=${scene_change_copy[$k]}
      if [ -z $candy ]; then break; fi
      if [ `echo "$candy < ${valid[$i]}" | bc` == 1 ]; then
        unset scene_change_copy[$k]
        continue;
      fi
      if [ `echo "$candy > ${valid[$j]}" | bc` == 1 ]; then break; fi
      unset scene_change_copy[$k]
      let nosc++
      $DEBUG && echo "  ${nosc}: $candy"
    done
    scene_change_copy=("${scene_change_copy[@]}")
    sl=$(echo "${valid[$j]} - ${valid[$i]}" | bc)
    rsc=$(echo "scale=6; ${nosc} / ${sl}" | bc)
    n_scene_change=( "${n_scene_change[@]}" $nosc )
    r_scene_change=( "${r_scene_change[@]}" $rsc )
    scene_length=( "${scene_length[@]}" $sl )
    $DEBUG && echo "  number of scene change: ${nosc}"
    $DEBUG && echo "  rate of scene change: ${rsc}"
    $DEBUG && echo "  scene length: ${sl}"
    $DEBUG && echo $i: scene_end at ${valid[$j]}
  done

  unset valid[${novsc}] # 仕掛けの回収
  valid=("${valid[@]}")
}

###function analyze(){
###
###}

# main
to=10
seek=0
# シーンチェンジと無音検出を同時に
if [ ! -e "$TEMP" ]; then
  $FFMPEG -i "$TS" -filter:v "select='gt(scene,0.375)',showinfo" -filter:a "silencedetect=noise=0.0001:duration=0.05" -f null - 2> "$TEMP"
fi
# 音声を別ファイルへ
if [ ! -e "$AUDIOFILE" ]; then
  $FFMPEG -i "$TS" -f sox "$AUDIOFILE"
fi
# シーンを検証
validate

valid=("${valid[@]}" "$total_length") # 再び末尾に仕掛け
$DEBUG && echo "number of validated scene change: ${#valid[*]}"
samples=()
length=()
scale=()
max_amp=()
min_amp=()
mid_amp=()
mean_norm=()
mean_amp=()
rms_amp=()
max_delta=()
min_delta=()
mean_delta=()
rms_delta=()
freq=()
vol_adj=()
novsc=${#valid[*]}
let novsc--
for index in `seq 1 $novsc`; do
  j=$index; i=$*4;
  if [ $index==$novsc  ]; then
    stats=($(sox -t sox "$AUDIOFILE" -n trim ${valid[$i]} -0 stat 2>&1 | sed -r 's/^.+:\s+([0-9.-]+)/\1/'))
  else
    stats=($(sox -t sox "$AUDIOFILE" -n trim ${valid[$i]} =${valid[$j]} stat 2>&1 | sed -r 's/^.+:\s+([0-9.-]+)/\1/'))
  fi
  max_amp=("${max_amp[@]}" ${stats[3]})
  min_amp=("${min_amp[@]}" ${stats[4]})
  mid_amp=("${mid_amp[@]}" ${stats[5]})
  mean_norm=("${mean_norm[@]}" ${stats[6]})
  mean_amp=("${mean_amp[@]}" ${stats[7]})
  rms_amp=("${rms_amp[@]}" ${stats[8]})
  max_delta=("${max_delta[@]}" ${stats[9]})
  min_delta=("${min_delta[@]}" ${stats[10]})
  mean_delta=("${mean_delta[@]}" ${stats[11]})
  rms_delta=("${rms_delta[@]}" ${stats[12]})
  freq=("${freq[@]}" ${stats[13]})
  vol_adj=("${vol_adj[@]}" ${stats[14]})
done


echo "# $TS"
echo "# md5sum  start   position        duration        rate    max_amp min_amp mid_amp mean_norm       mean_amp        rms_amp max_delta       min_delta       mean_delta      rms_delta       freq    vol_adj content"
md5=$(echo "$TS" | md5sum | awk '{print $1}')
let novsc--
for i in `seq 0 $novsc`; do
  position=$(echo "scale=6; ${valid[$i]} / $total_length" | bc)
  echo "$md5    ${valid[$i]}    $position       ${scene_length[$i]}     ${r_scene_change[$i]}   ${max_amp[$i]}  ${min_amp[$i]}  ${mid_amp[$i]}  ${mean_norm[$i]}        ${mean_amp[$i]} ${rms_amp[$i]}  ${max_delta[$i]}        ${min_delta[$i]}        ${mean_delta[$i]}       ${rms_delta[$i]}        ${freq[$i]}     ${vol_adj[$i]}  \"c\""
done

exit

*1:例外も有る、後日触れる。

*2: ${index} - 1

*3: ${index} - 1

*4: ${index} - 1