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

### 概要

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

[Androidの画面をPCにミラーリングするソフトを作る](https://qiita.com/siy1121/items/71fadad0c3a1aa085867)

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

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

![h264_stream_system_overview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64075/66db8feb-148b-0833-4db3-b0f555b5a568.png)

[ffplay Documentation](https://ffmpeg.org/ffplay.html)

### MediaCodec

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

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

- [H264](https://ja.wikipedia.org/wiki/H.264)

- [H263](https://ja.wikipedia.org/wiki/H.263)

- [VP8](https://ja.wikipedia.org/wiki/VP8)

[MediaCodec クラス概要 和訳](https://qiita.com/imatomi/items/bd9d49cfb1f73383ca12)

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


アプリは下図の構成となる。
![h264_stream_app_overview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64075/bbf51bad-84fe-07fd-fb29-23c101ceadf8.png)

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

### MediaCodec を使ってH264形式にする

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

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

Buffer入力にする。

- [MediaCodec を利用した動画作成](
http://tokyo.supersoftware.co.jp/code/6104)

- [MediaCodec stuff](
https://bigflake.com/mediacodec/)

[reference : createInputSurface]
(https://developer.android.com/reference/android/media/MediaCodec.html#createInputSurface())

[reference : getInputBuffer](
https://developer.android.com/reference/android/media/MediaCodec.html#getInputBuffer(int))

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

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

同期型にする。

[Android MediaCodec slower in async-mode than in synchronous mode?](https://stackoverflow.com/questions/39999219/android-mediacodec-slower-in-async-mode-than-in-synchronous-mode)

[reference : MediaCodec.Callback](https://developer.android.com/reference/android/media/MediaCodec.Callback.html)

### 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 形式。

画像形式を変換する。

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

- [How to use Android MediaCodec encode Camera data(YUV420sp)](https://stackoverflow.com/questions/17232477/how-to-use-android-mediacodec-encode-camera-datayuv420sp)

- [MediaCodec and Camera: colorspaces don't match](
https://stackoverflow.com/questions/13703596/mediacodec-and-camera-colorspaces-dont-match)

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

- [Recording video using MediaCodec with Camera2 API](https://stackoverflow.com/questions/52289534/recording-video-using-mediacodec-with-camera2-api)

- [Camera2PreviewStreamMediaCodecVideoRecording]
(https://github.com/get2abhi/Camera2PreviewStreamMediaCodecVideoRecording)

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

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


###  Server で画像を転送する

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


### ffplay で再生する

ffplayをダウンロードする。

[Download ffplay](https://ffmpeg.org/download.html)

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

> バージョン表示
> $ 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](https://developer.android.com/guide/topics/media/media-formats#video-formats)

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

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

[MediaCodecの制限について調べてみた](https://serenegiant.com/blog/?p=1715)

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

![ffplay_on_mac_480x360.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64075/e8da00ad-d456-3ba3-5fce-058407dca99b.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](https://ja.wikipedia.org/wiki/VP8)

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