概要
下記の記事では、Android 端末の画面をH264形式で転送している。
それなら、メラの画面も転送できるだろうと、試してみた。
下図のように、Android 端末を サーバーにして、H264 で転送する。
クライアントアプリとして ffplay を使用する。
ffplay は、FFmpegライブラリを使用したメディアプレーヤーです。
MediaCodec
MediaCodec は 低レベルなビデオやオーディオのエンコーダ
とデコーダを提供するクラスです。
ビデオのエンコーダは下記の形式に対応している。
カメラの画像をH264形式で転送する
- カメラデバイス
- ImageReader
- ImageFormatConverter
- MediaCodec
- Server
MediaCodec を使ってH264形式にする
下記を参考にした。
いずれもSurface入力です。
Camera22 では、Surface入力はうまくいかず。
Buffer入力にする。
[reference : createInputSurface]
(https://developer.android.com/reference/android/media/MediaCodec.html#createInputSurface())
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 形式。
画像形式を変換する。
ネットを調べると、苦戦しているようです。
下記の記事で紹介されているネイティブコードを使用した。
-
[Camera2PreviewStreamMediaCodecVideoRecording]
(https://github.com/get2abhi/Camera2PreviewStreamMediaCodecVideoRecording)
ネイティブコードを使用する方法は、NDK を使うので、敷居が高い。
久しぶりでもあり環境設定やビルドを通るまでに時間がかかった。
プロジェクトは、カメラの準備からファイルへの書き出しまでのデモになっている。
最低限必要なところを切り出して、
Android ライブラリにした。
プロジェクトはこちら。
https://github.com/ohwada/Android_Samples/tree/master/ImageFormatConverter
Server で画像を転送する
ポート番号を指定して ServerSocket を生成する。
クライアントからの接続を待ち受ける。
接続されたらMediaCodec を起動する。
H264 バイト列を連続して送信する。
ffplay で再生する
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でスムーズに再生できた。
画像の大きさにより、再生した画像がおかしいものがある。
SD(高画質) 480 x 360 のときだけ、画像が緑になる。
ビデオエンコーダーの種類
-
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
サンプルコードをgithub に公開した。
https://github.com/ohwada/Android_Samples/tree/master/Camera222