Androidアプリ公開の裏に隠されたバックエンド秘話 〜ffmpegでライブ動画いじり〜
はじめまして! 株式会社Flattに今年からインターンとして入り、バックエンド側の開発をしている、五十嵐将と申します。 普段はGoを書いていますが、この記事では一切Goはでてきません。
このブログはこれで2記事目なわけでして、記念すべき1記事目は今月初旬のPinQul Androidアプリのリリースについてだったわけですが、 アプリ開発だけでなくてこれにあわせてバックエンド側も頑張ったということで、動画の変換処理について書こうと思います。
PinQulの動画保守業務について
PinQulは、株式会社Flattのライブコマースサービスです。
このサービスでは現在、週に1つくらいのペースでライブを放送していますが、ユーザーの声もあり、ライブ放送終了後もそのままの形のライブ放送を一部公開しています。
つまり、生放送中にライブ動画が止まらない・遅れないような対策の他に、過去のアーカイブ動画を保存しておき、いつでも再生できる状態にしておくような保守作業も我々の仕事となっています。
PinQulの動画配信について
動画配信にはHLS形式を採用しています。 これの中身は、細かく分けられたMPEG2-TS形式の動画ファイル群とM3Uプレイリストファイルからなっていて、 アプリのプレーヤーにこのプレイリストファイルのありかを渡すと、プレイリストに書いてある動画が取得され再生される、という流れです。
この個々の動画ファイルが少々よくない性質を持っていたため、次のような問題が起きていました。
Androidアプリのリリースに当たって発覚した問題
前述したとおり、今月4月7日、PinQul Androidアプリがリリースされました🎉
このアプリでも、過去のライブ動画が再生できるような構成になっているのですが、 iOSアプリでは正常に再生できている動画が、Androidでは再生できないという問題が起きていました。 具体的には、初めの数秒はなんとか再生されるのですが、それ以降は完全に動画が止まってしまっていたのでした。
原因
確認のため、有名どころのオープンソース動画プレイヤーVLCでも再生を試しましたが、こちらも一切再生されないような状況でした。 このことから、Androidアプリというより、配信方法に問題がある可能性を考え、実際そのとおりでした。
実はこの細切れ動画のMPEG2-TSファイルは、それぞれ自分が動画全体の中で何秒からの位置にあるかを情報として持っています。
これはffmpegの中に含まれるツール、ffprobeを叩くと確認できます。
$ ffprobe media.mp4.ts ffprobe version 3.4.2 Copyright (c) 2007-2018 the FFmpeg developers built with gcc 7.3.0 (GCC) === 省略 === Input #0, mpegts, from 'media.mp4.ts': Duration: 00:00:03.02, start: 31.158000, bitrate: 2754 kb/s
これは正常な、全体の中で31.158秒付近から始まる(start:のあたりを見てください)動画ファイルなわけですが、 問題があったものでは、すべての動画ファイルでstart時間がそろってしまっていました。
つまり、そもそも動画ファイルがよくないはずなのに、なぜかSafariブラウザを含むiOSアプリでは正常に再生できてしまっている、というわけでした。
これはもとを辿れば配信のために動画を整形しているところが悪いわけで、そちらも今はもう直っているのですが、過去の動画に関してはどうにかしてAndroidで再生させられるようにしなければなりません。
対応
少しソースを見失ってしまったのですが、この問題について検索した結果、
- ffmpegで動画をMPEG2-TSに分割する際にinitial_offsetを指定することによってstart timeを指定時間にずらせるため、それで個々の動画のstartを合わせてしまう方法
- 細切れの動画を一度まとめて1動画ファイルにしてしまい、また分割する方法
を見つけました。
先に1.の方を見つけたので試してみました。 このとき、どうやってinitial_offsetに指定すべき値を個々の動画に対してとるのかという話があるのですが、 これは下の回答にあるような、ffprobeの魔法の使い方をすることによってとってくるような、実質shellscriptなRubyコードで行いました。
この方法によって、確かに動画はAndroid、VLCとも再生できるようにはなりました。しかしながら品質はあまりよくありませんでした。 というのも、やはりdurationのとり方が荒いのでしょうか、再生中に個々の動画の間の切れ目が分かるような断絶が入ってしまって、見ていて心地よいものではありませんでした。
そして調査を続けた結果見つけたのが、2.にあげた、まとめて分割する方法です。 実はMPEG2-TSは、単純にあのcatコマンドでつなげてしまうだけで、再生可能な動画ファイルになってしまうことが判明しました。
これが分かれば簡単です。 動画一覧をとってきて、catで順番につなげ、それをまたffmpegで分割するRubyスクリプトを書き捨て実行すると、すべての動画がAndroidでなめらかに再生可能になりました!
まだストリームの連続性が不完全で、VLCだとコンソールにWarningが出るなどもあり、これでいいのかという話もあるのですが、 まあ元の動画は残っているし、待ちに待ったAndroidリリースですので良しとしました。 また問題があれば対応したいと思います。
働く仲間、募集中!
Flattではサービスを一緒に開発してくれるエンジニア(に限らず)を募集中です!
また、以下のフォームから、もっと気軽にお茶ができるので、ぜひどうぞ!