Here's my theory, based on a quick look through the framework code.
The Canvas lock operation is (eventually) calling into Surface::lock()
(code here). That has a piece of code that says:
if (!mConnectedToCpu) {
int err = Surface::connect(NATIVE_WINDOW_API_CPU);
This is connecting a "CPU producer", i.e. code that runs on the CPU and generates graphic data, to the producer side of the buffer queue that feeds into the Surface. That producer is not disconnected in unlockAndPost()
. You can actually find the disconnect call in the Surface destructor, which is a bit late for your purposes.
You can't have two producers on one buffer queue, so when you hand the Surface to the MediaCodec decoder it's unable to connect.
I believe you have a couple of options:
- Blank the surface with OpenGL ES. When you destroy the
EGLSurface
it will disconnect. This requires setting up EGL/GLES and getting the EGL release code right.
- Put up a blank rectangle using an approach other than drawing on the
SurfaceView
itself (h/t my office-mate).
For approach #2, you just need a second view (maybe an ImageView
) with the same position and dimensions as the SurfaceView
, and fill it with opaque black. The SurfaceView
layer is always under everything else (assuming you haven't configured it to be on top), so the UI elements will draw on top of it. When it's time to start the movie playing, you disable the other view.
Update: you can now see approach #1 in Grafika. In the "Play movie (SurfaceView)" activity, it creates an EGL context, clears the surface, and destroys the context. (It's necessary to destroy the EGL context and surface immediately to avoid the "two producer" problem.)