音声はまだでけんけど…音声に対応する気が無いとい言う方もできるかも(汗)
普通のアプリでタイムシフト録画動作をさせようとすると幾つか問題があります。
- MediaCodecでのエンコード処理には遅延がある
- MediaMuxerでのファイルへの書き出し処理には遅延がある
- 通常のアプリではアプリ終了後5秒程度しか生きていられない
- フレームデータを一定以上古いものまたは容量を超えた時に古いものから順に消去しないと行けない
1つ目2つ目がどれぐらい掛かるかはほとんど誰にも分からへんけど、最近の端末だと最後にエンコーダーのキューに投入してからだいたい2-3秒で書き出しが終わります。
と言う事は3つ目の事を考慮すると、通常のアプリでは精々1-2秒しか過去に戻れない可能性が高いです。それ以上になったり、端末の性能が低かったり保存先の書き込み速度が遅ければ最後まで書ききれない内にアプリが終了させられてしまいます。つまり、通常の動画プレイヤーでは再生できないファイルが生成されることになります。
Android5以降、特にAndroid6や7では積極的にアプリを停止させようとするので普通のアプリ内でタイムシフト録画を実装するにはよろしくありません。
という事で、Service…それもforeground serviceとして実装することにします。
4つ目は言い換えると、過去のフレームデータをどのように保持しておくかということになります。これは主に次の3点を考慮しないといけないです。
- 生データを保持するかエンコードしてから保存するか
- どこに保持するか
- 過去データのライフタイム管理の仕組み
例えばフルHDでRGBAの映像データだと1フレーム当たり1920x1080x4≒8MB/フレーム≒475MB/sにもなります。
こんなものを何十秒も何分も保存できるようなAndroid端末はありませーん(。・_・。)
という事でエンコードしてから保存することに決定(^o^)v
どこに保持するか…これは悩ましいですが、数秒程度であればメモリ内に保持できなくもありません。でも上限の制約が厳しく越えてしまうとOOMでずっこけてしまいます。しかもずっこけた時に過去データが全て失われてしまいます。となるとストレージ…といってもAndroidなので普通はハードディスクなんぞ付いていませんのでフラッシュメモリ上に記録することになるでしょう。
今回はアプリの外部ストレージ上のキャッシュが使用可能であればそこへ、外部ストレージ上のキャッシュが使用できなければ内部ストレージのキャッシュ領域へ保存することにしました。外部ストレージがSD/SDXC/SDXCカードの場合には使用するSD/SDXC/SDXCカードによっては書き込み速度が足らなくなる可能性があります。キャッシュ領域をユーザーが選択できるようにしておいたほうが親切かもしれないです。
でもって最後は過去データのライフタイム管理の仕組み…こういうデータ管理を行うための方法はいくつもあります。
でも自分で作るまでもなくJavaに都合のよいクラスがあります。キミの名は?じゃじゃーん^^ LinkedHashMapといいまーす。
実はLinkedHashMapはJavaでメモリキャッシュやディスクキャッシュでよく使われるクラスなのですエッヘン
LinkedHashMapをメモリキャッシュやディスクキャッシュで使う場合には普通はLRU方式で使います。
LRU(Least Recently Used)…つまりもっとも使われなかったもの=最後に使われてからもっとも長い時間経過したものを破棄する時に使用するリソースの管理方式です。別な言い方をすると頻繁にアクセスするものほどデータの順が後ろに来て、リソースの容量を超えた時に前の方から削除されていきます。
今回はタイムシフト録画なのでアクセスする度に順番が入れ替わってもらっては困ります。時にMediaMuxerでの書き出し処理はpresentationTimeUsがモノトニックなタイムスタンプでないと、こんなものは受け取れるかぁとちゃぶ台をひっくり返します?。
ところでLinkedHashMapには次の5つのコンストラクタがあります。
1 2 3 4 5 |
LinkedHashMap(); LinkedHashMap(int initialCapacity); LinkedHashMap(int initialCapacity, float loadFactor); LinkedHashMap(Map< ? extends K,? extends V> m); LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder); |
この内最初の3つはLRUになってしまいます。3つ目はソースのMapと同じ順に並んだ挿入順のLinkedHashMapが得られますが普通のMap自体が順不定なので今回の用途には向きません。
という事で4つ目のコンストラクタを使います^^
4つ目のコンストラクタは、第3引数accessOrderでデータの並び方を指定することが出来るのです\(^o^)/
1 |
LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder); |
accessOrderにtrueを渡すと最初の3つのコンストラクタと同様にアクセス順…最後にアクセスした要素が一番後ろ、頻繁にアクセスしたもの程後ろ、あまりアクセスしない要素は前にきます。
一方accessOrderにfalseを渡すと挿入順に要素が並びます。つまり時系列順に前から順に並びます。
まぁ、Javaには他にも似たような挙動をするコンテナークラス…例えばQueue系のクラスもあるのでそっちを使っても全然構いません。
じゃぁなんでLinkedHashMapといい出したのかっちゅうと、
実はLinkedHashMapはJavaでメモリキャッシュやディスクキャッシュでよく使われるクラスなのです
ちゅうことでLRUなディスクキャッシュクラスをベースに改変して作ったからです。ねずみ年生まれなのでチュ(*ノω・*)テヘ
ディスクキャッシュの実装は色々あるので詳しくはぐるぐる先生に聞いておくれ。自分はAndroid4.1.1のDiskLruCacheを元に、キーをStringからlong(Long)に変更したり、先頭要素の取得メソッドを追加したりしとります。
今回は前フリだけでおしまーい。アクセス数が多ければ続きを書くかも。
ということで、あけおめでさよならぁ(^_^)/~
コメント
[…] でサービスにせなあかんかはその1を見てな。 […]