• スポンサードリンク

Android用にタイムシフト録画クラスを作ってみた〜その4〜

Android MediaCodec

久々の長編や^^ 大した事ないのを長々と書いとるとも言う^^;
…実は、タイムシフト録画サービスは1つ1つのコード・テクニックは初心者レベルなのばっかりやから、あんまり何書いてええか良う分からへんねん。

前回はフォアグラウンドサービスにせにゃならんという話でおしまいでした。
最後に「上の内容をみて鋭い人は実装するものが増えた事に気づいたはず。さぁていったいなんでしょうか?」なんてことを書きましたが分かったでしょうか?

色々ありますが、フォアグラウンドサービスと直接関係するもの、それはステータスバーに通知を出すことです^^
という事でまずはステータスバーに通知を出してフォアグラウンドサービスにしてやるぜぇー(^o^)/

ステータスバーに通知を出してフォアグラウンドサービスにする

ステータスバーに通知を出すにはNotificationManagerちゅうのを使いまチュウ。

今回はタイムシフト用のサービスを後で使いまわしやすいようにモジュールに入れて継承せずにそのまま使えるようにしたかったので、バインドする時にメインActivityのクラスをIntentに入れて渡すようにしています。
継承しても良ければabstractのサービスを作って置いて先程のcreatePendingIntentメソッドをabstractにして、使用するアプリ内でその抽象サービスを継承したクラスを作ってそこでcreatePendingIntentメソッドを実装してあげればよかです。
でも継承するようにすると、モジュールのAndroidManifest.xmlでサービスを追加できんくなるんですよねぇ。

普段は自分も継承して使うタイプの作り方をしますが、今回は

  • モジュールをbuild.gradleに追加するだけで完結して使えるようにしたい
  • つまりモジュールのAndroidManifest.xmlにサービスを記述せんとあかん
  • abstractなサービスはAndroidManifest.xmlに追加でけん
  • 実はjava.lang.ClassはSerializableやからIntentで送れるねん
  • ローカルサービスやねん
  • こんなこともできるねん

という事で、こんな仕様にしてみました。

やっとることは至って簡単。
Context#getSystemService(NOTIFICATION_SERVICE)を呼んでNotificationManagerインスタンスを取得してゴニョゴニョするだけです。
メッセージを変える時はもう一度#showNotificationを呼ぶだけです。
でも終了する際にはちゃんと後始末しておきましょうね。

あっ後、フォアグラウンドサービスにする時にはNotificationインスタンスを引数にしてService#startForegroundを呼ぶのですが、この時に一緒にid(0以外の整数)を渡さないといけません。まぁなんでもいいんですが自分は文字列リソースのidを使うことが多いです。
上のコードだと、最初に定義しているNOTIFICATIONフィールド(= R.string.time_shift)がそれです。

もういっぺん状態遷移

前にも載せたけどもういっぺん載せときます。

  • STATE_UNINITIALIZED
  • STATE_INITIALIZED
  • STATE_PREPARING
  • STATE_READY
  • STATE_BUFFERING
  • STATE_RECORDING
  • STATE_RELEASING

なんでこないなことにしたかっちゅうと、MediaCodec/MediaMuxerの状態遷移があるからです。MediaCodec/MediaMuxerは融通がきかないので手順から外れると直ぐに例外を投げてきよります。実際にどんな感じに動く(はず)なのかを書いてみましょう。

  1. クライアント:startService
  2. サービス  :サービスが生成・起動(STATE_UNINITIALIZED)
  3. クライアント:サービスをバインド要求
  4. サービス  :onBind(STATE_INITIALIZED)
  5. サービス  :ステータスバーに通知を表示してフォアグラウンドサービスへ
  6. クライアント:映像エンコード準備要求(STATE_PREPARING)
  7. サービス  :ディスクキャッシュ・MediaCodecのエンコーダーを初期化(初期化が終わればSTATE_READY)
  8. クライアント:タイムシフト開始要求(STATE_BUFFERINGへ)
  9. クライアント:映像入力用Surfaceを取得
  10. クライアント:映像入力開始
  11. サービス  :入力された映像をエンコードしてワーカースレッド上でディスクキャッシュへ書き込む
    最初のフレームエンコード時にMediaFormatが生成されるので、後ほどMediaMuxerを初期化するときまで保持しておく必要があります。
  12. クライアント:録画開始要求
  13. サービス  :MediaMuxerを初期化。(別の)ワーカースレッド上でディスクキャッシュからエンコード済みの映像を取り出してMediaMuxerを介してファイルへ書き出す(STATE_RECORDING)
  14. クライアント:録画終了要求
  15. サービス  :(STATE_BUFFERING)
  16. クライアント:タイムシフト終了要求
  17. サービス  :(STATE_READY)
  18. サービス  :映像のエンコード・ディスクキャッシュへの書き込みを終了
  19. クライアント:サービスをアンバインド
  20. サービス  :(STATE_RELEASING)
  21. サービス  :ファイルへの書き込みが終わればstopSelf
  22. サービス  :後始末してフォアグラウンドサービスを解除

多少は前後する場合もあるけど、おおむねこんな感じに実行されます。なげぇよ(。・_・。)

これをサービス内で実行するだけでタイムシフト録画が出来るようになりますキッパリ(笑) 簡単だよね:p)

とりあえず対応するパブリックメソッドを書いてみましょう。

なっ、簡単やろ?普通にMediaCodecを使うときと変わらへん。
違うのはMediaCodecのエンコーダーからエンコード済みのデータを受け取った時に、

  1. MediaMuxerへ直接書き込むか、
    (これは普通のMediaCodex/MediaMuxerの使い方)
  2. TimeShiftDiskCacheへ書き込む→TimeShiftDiskCacheの一番古いものを取り出してMediaMuxerへ書き込む

基本的にはこれだけの違いしかないねん。こんな簡単やったらコード載せんでも自分で作れるやろ?

あかん?

MediaCodecの準備はいつもどおりこんな感じ。いつもと違うのはTimeShiftDiskCache用のキャッシュディレクトリを処理をせなあかん所やな。

下のコードのMediaReaper.ReaperListener#writeSampleDataの中でTimeShiftDiskCacheへ書き込んどります。

でもって録画開始も普段とたいしてかわらへん。今回はSDカードへの書き込む時のメソッドも書いたからちょっと長ごなっとるけど。
普通に録画する時はエンコードの開始とエンコード済みのフレームデータをMediaMuxerで書く出す処理を同時に開始させるけど、今回はエンコードは既に開始しとるけん、MediaMuxerへの書き出し処理だけを開始すればええねん。

実際のMediaMuxerへの書き出し処理はここな。Runnableとして実装して録画開始時にThreadのコンストラクタへ渡してスレッド上で実行してもらうねん。このRecordingTask#runループ内でTimeShiftDiskCacheの一番古いものを取り出してはMediaMuxerへ書き込んでおりまする。

ほらな?載せんでもつくれたやろ。
載ってないメソッドやフィールドは名前で想像するか小人さんに手伝ってもらってな^^ もしかしたら夜中にプログラムの女神様が手伝ってくれるかもしれんし(*ノω・*)テヘ

そや、MediaReaperはお初やから載せとこ。MediaCodecのエンコーダーを使う時に、何度もおんなじこと書くのに飽きてきたのでヘルパー用のクラスを作ってん。とは言うても中身は過去に何度も載せとるしGithub上にも晒してあるのをコピペしただけやけどな。

無駄に長くなってもうたわ(汗) 次も大したことないけどクライアント側を少し載せておしまいにするわ。
という事で今日はさよならサヨナラ(^.^)/~~~

« »

  • スポンサードリンク

%d人のブロガーが「いいね」をつけました。