web-dev-qa-db-ja.com

SurfaceTextureのonFrameAvailable()メソッドの呼び出しが常に遅すぎます

次のMediaExtractorの例を機能させようとしています。

http://bigflake.com/mediacodec/ --ExtractMpegFramesTest.Java(4.1、API 16が必要)

私が抱えている問題は、outputSurface.awaitNewImage(); mFrameSyncObject.wait(TIMEOUT_MS)呼び出しがタイムアウトするたびにスローされるRuntimeException( "frame wait timed out")を常にスローするようです。 _TIMEOUT_MS_を何に設定しても、onFrameAvailable()は常に呼び出されます直後タイムアウトが発生します。私は50msと30000msで試しましたが、同じです。

スレッドがビジー状態の間はonFrameAvailable()呼び出しを実行できないようです。タイムアウトが発生してスレッドコードの実行が終了すると、onFrameAvailable()呼び出しを解析できます。

誰かがこの例をうまく機能させることができましたか、またはMediaExtractorがGLテクスチャでどのように機能するかを知っていますか?

編集:API 4.4と4.1.1を備えたデバイスでこれを試しましたが、両方で同じことが起こります。

編集2:

Faddenのおかげで4.4で動作するようになりました。問題は、ExtractMpegFramesWrapper.runTest()と呼ばれるth.join();メソッドがメインスレッドをブロックし、onFrameAvailable()呼び出しが処理されないことでした。 th.join();にコメントすると、4.4で動作します。おそらく、ExtractMpegFramesWrapper.runTest()自体はさらに別のスレッドで実行されるはずだったので、メインスレッドはブロックされませんでした。

4.1.2でcodec.configure()を呼び出すときに小さな問題もあり、エラーが発生しました。

_A/ACodec(2566): frameworks/av/media/libstagefright/ACodec.cpp:1041 CHECK(def.nBufferSize >= size) failed.
A/libc(2566): Fatal signal 11 (SIGSEGV) at 0xdeadbaad (code=1), thread 2625 (CodecLooper)
_

呼び出しの前に以下を追加することで解決しました:

_format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0);
_

ただし、4.1.1(Galaxy S2 GT-I9100)と4.1.2(Samsung Galaxy Tab GT-P3110)の両方で現在発生している問題は、両方ともすべてのフレームで常にinfo.sizeを0に設定していることです。ログ出力は次のとおりです。

_loop
input buffer not available
no output from decoder available
loop
input buffer not available
no output from decoder available
loop
input buffer not available
no output from decoder available
loop
input buffer not available
no output from decoder available
loop
submitted frame 0 to dec, size=20562
no output from decoder available
loop
submitted frame 1 to dec, size=7193
no output from decoder available
loop
[... skipped 18 lines ...]
submitted frame 8 to dec, size=6531
no output from decoder available
loop
submitted frame 9 to dec, size=5639
decoder output format changed: {height=240, what=1869968451, color-format=19, slice-height=240, crop-left=0, width=320, crop-bottom=239, crop-top=0, mime=video/raw, stride=320, crop-right=319}
loop
submitted frame 10 to dec, size=6272
surface decoder given buffer 0 (size=0)
loop
[... skipped 1211 lines ...]
submitted frame 409 to dec, size=456
surface decoder given buffer 1 (size=0)
loop
sent input EOS
surface decoder given buffer 0 (size=0)
loop
surface decoder given buffer 1 (size=0)
loop
surface decoder given buffer 0 (size=0)
loop
surface decoder given buffer 1 (size=0)
loop
[... skipped 27 lines all with size=0 ...]
surface decoder given buffer 1 (size=0)
loop
surface decoder given buffer 0 (size=0)
output EOS
Saving 0 frames took ? us per frame // edited to avoid division-by-zero error
_

したがって、画像は保存されません。ただし、同じコードとビデオが4.3で機能します。私が使用しているビデオは、「H264-MPEG-4 AVC(avc1)」ビデオコーデックと「MPEGAAACオーディオ(mp4a)」オーディオコーデックを含む.mp4ファイルです。

他のビデオフォーマットも試しましたが、どちらも4.3で動作するのに対し、4.1.xではさらに早く死んでしまうようです。

編集3:

あなたが提案したように私はしました、そしてそれはフレーム画像を正しく保存するようです。ありがとうございました。

KEY_MAX_INPUT_SIZEに関しては、設定しないか、0、20、200、... 200000000に設定しようとしましたが、すべてinfo.size = 0と同じ結果になりました。

レイアウトでレンダリングをSurfaceViewまたはTextureViewに設定できなくなりました。私はこの行を置き換えてみました:

_mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId());
_

これで、surfaceTextureは私のxml-layoutで定義されたSurfaceTextureです:

_mSurfaceTexture = textureView.getSurfaceTexture();
mSurfaceTexture.attachToGLContext(mTextureRender.getTextureId());
_

しかし、2行目のgetMessage()==nullで奇妙なエラーがスローされます。ある種のビューに描画させる他の方法は見つかりませんでした。フレームを保存する代わりに、Surface/SurfaceView/TextureViewに表示するようにデコーダーを変更するにはどうすればよいですか?

23
manixrock

SurfaceTextureの動作方法により、これを正しく行うのは少し難しいです。

docs は、frame-availableコールバックが「任意のスレッドで呼び出される」と言います。 SurfaceTextureクラスには、初期化時に次のことを行うコードが少しあります( 行318 ):

_if (this thread has a looper) {
    handle events on this thread
} else if (there's a "main" looper) {
    handle events on the main UI thread
} else {
    no events for you
}
_

フレームで使用可能なイベントは、通常のLooper/Handlerメカニズムを介してアプリに配信されます。このメカニズムは単なるメッセージキューです。つまり、スレッドはLooperイベントループに留まり、到着するのを待つ必要があります。問題は、awaitNewImage()でスリープしている場合、Looperキューを監視していないことです。そのため、イベントが到着しますが、誰にも表示されません。最終的にawaitNewImage()がタイムアウトし、スレッドはイベントキューの監視に戻り、保留中の「新しいフレーム」メッセージをすぐに検出します。

したがって、トリックは、フレームで使用可能なイベントがawaitNewImage()にあるスレッドとは異なるスレッドに到着することを確認することです。 ExtractMpegFramesTestの例では、これは、ExtractMpegFramesWrapperを持たない新しく作成されたスレッド(Looperクラスを参照)でテストを実行することによって実行されます。 (何らかの理由で、CTSテストを実行するスレッドにはルーパーがあります。)フレーム使用可能イベントはメインUIスレッドに到着します。

更新(「編集3」の場合):「サイズ」フィールドを無視することが役に立ったのは少し悲しいですが、4.3より前では予測が困難ですデバイスがどのように動作するか。

justだけでフレームを表示したい場合は、SurfaceまたはSurfaceViewから取得したTextureViewMediaCodecデコーダーconfigure()呼び出しに渡します。そうすれば、SurfaceTextureをいじる必要はまったくありません。フレームをデコードするとフレームが表示されます。例については、 Grafika の2つの「ビデオの再生」アクティビティを参照してください。

本当にSurfaceTextureを通過したい場合は、CodecOutputSurfaceを変更して、pbufferではなくウィンドウサーフェスにレンダリングする必要があります。 (オフスクリーンレンダリングが行われるため、ヘッドレステストでglReadPixels()を使用できます。)

15
fadden