8
4

More than 5 years have passed since last update.

- [Adroid の Camera2 API と MediaCodec を使って カメラの画像をH264形式で転送する

Last updated at Posted at 2019-10-05

概要

下記の記事では、Android 端末の画面をH264形式で転送している。
それなら、メラの画面も転送できるだろうと、試してみた。

Androidの画面をPCにミラーリングするソフトを作る

下図のように、Android 端末を サーバーにして、H264 で転送する。

クライアントアプリとして ffplay を使用する。
ffplay は、FFmpegライブラリを使用したメディアプレーヤーです。

h264_stream_system_overview.png

ffplay Documentation

MediaCodec

MediaCodec は 低レベルなビデオやオーディオのエンコーダ
とデコーダを提供するクラスです。

ビデオのエンコーダは下記の形式に対応している。

MediaCodec クラス概要 和訳

カメラの画像をH264形式で転送する

アプリは下図の構成となる。
h264_stream_app_overview.png

  • カメラデバイス
  • ImageReader
  • ImageFormatConverter
  • MediaCodec
  • Server

MediaCodec を使ってH264形式にする

下記を参考にした。
いずれもSurface入力です。

Camera22 では、Surface入力はうまくいかず。

Buffer入力にする。

reference : createInputSurface

reference : getInputBuffer

MediaCodecの使い方は、コールバックを使う非同期型と使わない同期型の2つがある。
非同期型は非常に遅かった。

onInputBufferAvailable と onOutputBufferAvailable を
同じスレッドで処理すると、一方を処理するときにもう一方をブロックするようです。

同期型にする。

Android MediaCodec slower in async-mode than in synchronous mode?

reference : MediaCodec.Callback

MediaCodec のBuffer入力とBuffer出力

Buffer入力はカメラからの画像データの用意が出来たところで
InputBuffer を取得する

    // 画像データ
    byte[] frame;
    long timestamp;

    ByteBuffer inputBuffer = null;
    int index = -1;
    try {
            index = mediaCodec.dequeueInputBuffer(INPUT_TIMEOUT);
            if (index>=0) {
                    inputBuffer = mediaCodec.getInputBuffer(index);
            }
    } catch (IllegalStateException e) {
    }

    if(inputBuffer == null) return;

    int size = frame.length;
    inputBuffer.clear();
    inputBuffer.put(frame, 0 ,size);
    mediaCodec.queueInputBuffer(index, 0, size, timestamp, 0);

Buffer出力はトリガーとなるイベントがないので、
無限ループで処理する。
データの用意が出来たところで
OutputBuffer からバイト列を取得する。

     MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();

// outputBuffer を取得する
    int index = -1;
    ByteBuffer outputBuffer = null;
    try {
            // timeout: 500ms
            index = mediaCodec.dequeueOutputBuffer(bufferInfo, 500000);
            if (index >= 0) {
                    outputBuffer = mCodec.getOutputBuffer(index);
            }
    } catch (IllegalStateException e) {
    }

    if(outputBuffer == null) return null;

// バイト列を取得する
    int size = bufferInfo.size ;
    byte[] outData = new byte[size];

    outputBuffer.position(0);
    outputBuffer.get(outData);

    mediaCodec.releaseOutputBuffer(index, false);

ImageFormatConverter

ImageReader の出力は YUV420_888 形式。

MediaCodec の入力は YUV420Planar 形式。

画像形式を変換する。

ネットを調べると、苦戦しているようです。

下記の記事で紹介されているネイティブコードを使用した。

ネイティブコードを使用する方法は、NDK を使うので、敷居が高い。
久しぶりでもあり環境設定やビルドを通るまでに時間がかかった。

プロジェクトは、カメラの準備からファイルへの書き出しまでのデモになっている。
最低限必要なところを切り出して、
Android ライブラリにした。
プロジェクトはこちら。
https://github.com/ohwada/Android_Samples/tree/master/ImageFormatConverter

Server で画像を転送する

ポート番号を指定して ServerSocket を生成する。
クライアントからの接続を待ち受ける。
接続されたらMediaCodec を起動する。
H264 バイト列を連続して送信する。

ffplay で再生する

ffplayをダウンロードする。

Download ffplay

コマンドラインで実行する。
動作確認する。

バージョン表示
$ ffplay -version
動画ファイルの再生
$ ffplay ファイルパス

Android端末からのビデオストリームを再生する

$ ffplay -i tcp://IPアドレス:ポート/

画像の大きさ

MediaCodecに入力する画像の大きさは
MediaFormat#createVideoFormat で指定したコンテンツの大きさよりも小さいか同じである必要がある。
大きいと Buffer Overflow になる。

ImageReader の画像の大きさは、イメージセンサと同じにして、
取り出した後で縮小するのが一般的であるが。
YUV420Planar 形式の縮小は、道が遠そうなので、
ImageReaderを生成するときに必要な大きさを指定する方法とした。

公式ドキュメントによれば、推奨の解像度は下記のとおり。
コンピュータディスプレイでお馴染みのVGAサイズ(640x480)はなが、パソコンで再生するなら、このくらいの大きさは欲しい。

種別 解像度 アスペクト比 備考
SD (低画質) 176 x 144 1.22
SD(高画質) 480 x 360 1.33
HD 720p 1280 x 720 1.77 機種依存

guide : Supported media formats - Video support

下記の記事によれば、FullHD(1920×1080) 30fps くらいまでなら問題なさそう。

Nexus5 で試したところ、
VGA(640x480) 10fps と HD(1920×960) 10fps ともに ffplayでスムーズに再生できた。

MediaCodecの制限について調べてみた

画像の大きさにより、再生した画像がおかしいものがある。
SD(高画質) 480 x 360 のときだけ、画像が緑になる。

ffplay_on_mac_480x360.png

ビデオエンコーダーの種類

  • H264
    上記のように ffplay でスムーズに再生できた。

  • H263 

Android にて下記のエラーとなる。
公式ドキュメントによれば Android 7.0 以降では 任意 (optional) ということなので、
使うなということだな。

SoftMPEG4Encoder: Failed to initialize the encoder
[OMX.google.h263.encoder] ERROR(0x80001001)

  • VP8   ffplay にて下記のエラーとなる。 比較的新しい形式なのと、H264と同程度の品質なので、 対応していないようだ。

Invalid data found when processing input0

wikipedia : VP8

サンプルコードをgithub に公開した。
https://github.com/ohwada/Android_Samples/tree/master/Camera222

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4