Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ExoPlayer Custom Renderer #1422

Closed
dhruvsingla273 opened this issue Jun 3, 2024 · 24 comments
Closed

ExoPlayer Custom Renderer #1422

dhruvsingla273 opened this issue Jun 3, 2024 · 24 comments

Comments

@dhruvsingla273
Copy link

Hi All,

I am trying to create a player in which I want to do processing on frames of the video.
So, I need to get each frame of the video, do some processing on it, and have to then render it to the Screen.
I searched but couldn't find a compelling method that can be fast enough to render.

Can someone guide me for this how to make the custom renderer.

@droid-girl
Copy link
Contributor

Hi @dhruvsingla273,
You can use .setVideoEffects in ExoPlayer to achieve this:

  1. Create a custom effect by extending BaseGlShaderProgram. You can find a few examples in the effects module, for example, ColorLutShaderProgram
  2. Do frame modifications in drawFrame method in your custom shader program
  3. in ExoPlayer, use setVideoEffects and set your custom effect.

I think this should help you to achieve your goal.

@dhruvsingla273
Copy link
Author

Hi @droid-girl ,
Thanks for the reply.
Sorry for not being clear about the processing part in the last question.
Actually I need to apply a TFlite model to each of the frames and I think shaders can't do that.
I tried using MediaCodec to custom extract the frames and then apply the model and render them (https://github.com/duckyngo/Fast-Video-Frame-Extraction).
It worked but I need to do it within ExoPlayer.

I tried making a custom Renderers Factory from MediaCodecVideoRenderer, but the ByteBuffer I got was empty as I got to know that Media Codec runs in Surface mode and hence it is not storing anything in buffers to improve codec performance. I could try to set MediaCodec to byteBuffer mode to get the byteBuffer but I read it is very slow. (Also can you tell how to set this mode change for MediaCodec in ExoPlayer)

Could you suggest a different approach?

@dhruvsingla273
Copy link
Author

I have one more question.
If I can get a steady stream of a Bitmaps or ByteBuffer with my final frame data,
can I somehow use ExoPlayer to render them using the player?
Means I won't be having all the frames at a single time, I can have like say 2 sec of frames and then continue processing other frames and keep passing to ExoPlayer to render.

I know it might be confusing, please ask any clarification on the question you may need.
Thanks again

@dhruvsingla273
Copy link
Author

Hi @droid-girl
Have you found any way to integrate TFlite model?

@droid-girl
Copy link
Contributor

Hi @dhruvsingla273 ,
Let me first provide a short summary on the approach you can take to integrate with TFLite:

  1. Extend [BaseGlShaderProgram] as recommended in the earlier comment (https://github.com/androidx/media/blob/release/libraries/effect/src/main/java/androidx/media3/effect/BaseGlShaderProgram.java)
  2. in drawFrame method you want to read frame to be able to pass it through TFLite:
    ByteBuffer pixelBuffer = ByteBuffer.allocateDirect(width * height * 4);
    Bitmap bitmap;
    int texId;
    try {
      int[] boundFramebuffer = new int[1];
      GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, boundFramebuffer, /* offset= */ 0);

      int fboId = GlUtil.createFboForTexture(inputTexId);
      GlUtil.focusFramebufferUsingCurrentContext(fboId, width, height);
      GLES20.glReadPixels(
          /* x= */ 0,
          /* y= */ 0,
          width,
          height,
          GLES20.GL_RGBA,
          GLES20.GL_UNSIGNED_BYTE,
          pixelBuffer);
      GlUtil.checkGlError();
      bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
      bitmap.copyPixelsFromBuffer(pixelBuffer);

  1. Process bitmap in TFLite
  2. Pass the processed output image
      texId =
          GlUtil.createTexture(
              outputBitmap.getWidth(),
              outputBitmap.getHeight(),
              /* useHighPrecisionColorComponents= */ false);
      GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
      GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
      GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
      GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT);
      GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);
      GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, outputBitmap, /* border= */ 0);
  1. Pass it to your glProgram
    glProgram.setSamplerTexIdUniform("uTexSampler", texId, /* texUnitIndex= */ 0);

  2. Use setVideoEffects in ExoPlayer to set your custom effect

Here is just a high level overview of what needs to be done

@dhruvsingla273
Copy link
Author

Thanks for the response
I will try this approach and let you know.

Meanwhile
If I can get a steady stream of a Bitmaps or ByteBuffer with my final frame data (using a custom decoder from MediaCodec),
can I somehow use ExoPlayer to render them using the player?
Means I won't be having all the frames at a single time, I can have like say 2 sec of frames and then continue processing other frames and keep passing to ExoPlayer to render.

Do you have idea how can we do this?

@droid-girl
Copy link
Contributor

@tonihei could you help with the last question?

@tonihei
Copy link
Collaborator

tonihei commented Jun 19, 2024

That's not possible at the moment because there is no mode in which you can render existing decoded frames from ByteBuffers. ExoPlayer supports image playback, as a series of Bitmaps, but this sounds very inefficient as you have to move the fully decoded buffers around. I think it's usually preferable to do all processing on the GPU? I think that's the approach @droid-girl explained above.

@dhruvsingla273
Copy link
Author

Hi @droid-girl, using the effect method I was able to get the bitmap at each frame,
But I am not able to render anything on the surface.
So my surface is coming as black.
I even tried just passing the as it is bitmap without any change to the surface but it is not rendering.

Any idea why?

@droid-girl
Copy link
Contributor

It is hard to say without looking at the code and debugging it.
Please check if you have any GL errors and if you correctly bind processed frames

@dhruvsingla273
Copy link
Author

So, i was experimenting with the .setVideoEffect
I am able to render the original video.
Whenever I use my model it takes some milli seconds to run

And then the player crashes/stops at sometimes after 10/4/5 sec of playback

The code:
`
@OverRide
public void drawFrame(int inputTexId, long presentationTimeUs)
throws VideoFrameProcessingException {
try {
long time = System.currentTimeMillis();

        int fboId = GlUtil.createFboForTexture(inputTexId);
        GlUtil.focusFramebufferUsingCurrentContext(fboId, width, height);
        GLES20.glFinish();

        GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, pixelBuffer);

        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboId);
        GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, 3, 0);
        GlUtil.checkGlError();

        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        bitmap.copyPixelsFromBuffer(pixelBuffer);

        output = model(bitmap, interpreter);
        Bitmap bitmap1 = output.getBitmap();
        pixelBuffer.position(0);

        int texId = GlUtil.createTexture(outputwidth, outputheight, false);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap1, 0);

        glProgram.use();
        glProgram.setSamplerTexIdUniform("uTexSampler", texId, 0);
        glProgram.bindAttributesAndUniforms();
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        GlUtil.checkGlError();
        System.out.println("save + read time for each frame with model " + (System.currentTimeMillis() - time));

    } catch (GlUtil.GlException e) {
        throw new VideoFrameProcessingException(e, presentationTimeUs);
    }
}`

This is the error log I get:
at androidx.media3.exoplayer.ExoPlayerImplInternal.doSomeWork(ExoPlayerImplInternal.java:1112) at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:544) at android.os.Handler.dispatchMessage(Handler.java:102)  at android.os.Looper.loop(Looper.java:255)  at android.os.HandlerThread.run(HandlerThread.java:67)  Caused by: android.media.MediaCodec$CodecException: Error 0xfffffff3 at android.media.MediaCodec.releaseOutputBuffer(Native Method) at android.media.MediaCodec.releaseOutputBufferInternal(MediaCodec.java:3568) at android.media.MediaCodec.releaseOutputBuffer(MediaCodec.java:3542) at androidx.media3.exoplayer.mediacodec.SynchronousMediaCodecAdapter.releaseOutputBuffer(SynchronousMediaCodecAdapter.java:163) at androidx.media3.exoplayer.video.MediaCodecVideoRenderer.renderOutputBufferV21(MediaCodecVideoRenderer.java:1666) at androidx.media3.exoplayer.video.MediaCodecVideoRenderer.renderOutputBuffer(MediaCodecVideoRenderer.java:1627) at androidx.media3.exoplayer.video.MediaCodecVideoRenderer.processOutputBuffer(MediaCodecVideoRenderer.java:1356) at androidx.media3.exoplayer.mediacodec.MediaCodecRenderer.drainOutputBuffer(MediaCodecRenderer.java:2010) at androidx.media3.exoplayer.mediacodec.MediaCodecRenderer.render(MediaCodecRenderer.java:827) at androidx.media3.exoplayer.video.MediaCodecVideoRenderer.render(MediaCodecVideoRenderer.java:940)  at androidx.media3.exoplayer.ExoPlayerImplInternal.doSomeWork(ExoPlayerImplInternal.java:1112)  at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:544)  at android.os.Handler.dispatchMessage(Handler.java:102)  at android.os.Looper.loop(Looper.java:255)  at android.os.HandlerThread.run(HandlerThread.java:67) 

@dhruvsingla273
Copy link
Author

Lets say it took 2 sec to process 10 frames,
so the progress bar of exoplayer (which displays the time) goes forward by 2 sec, when it should have stayed at 0 itself because not all the 60 frames for 2 sec are processed.

Can you help with this

@halx99
Copy link

halx99 commented Jul 9, 2024

Current ExoPlayer not support ByteBuffer mode, but it's possible to patch MediaVideoCodecRenderer.java: https://github.com/axmolengine/axmol/blob/dev/core/platform/android/java/src/org/axmol/lib/MediaCodecVideoRenderer.java#L16

@dhruvsingla273
Copy link
Author

@halx99 I already tried this approach, I am able to get a image there (have to set the surface to null to make it run in ByteBuffer mode)
But then I am unable to render the image/bitmap back to the surface because there is no surface

Do you have any solution for this?

@halx99
Copy link

halx99 commented Jul 10, 2024

You can render the bytebuffer(NV12 pixel data) to GLSurface with custom fragment shader, refer to: https://github.com/axmolengine/axmol/blob/dev/core/renderer/shaders/videoTextureNV12.frag

Edit: in some old device the decoded bytebuffer would be I420 pixel data, refer axmolengine/axmol#2050

@dhruvsingla273
Copy link
Author

dhruvsingla273 commented Jul 11, 2024

Okay will try that,

Does anyone knows in which format the buffer is stored when the encoding is mkv

@halx99
Copy link

halx99 commented Jul 11, 2024

Okay will try that,

Does anyone knows in which format the buffer is stored when the encoding is mkv

I guess bytebuffer mode always NV12, refer https://github.com/axmolengine/axmol/wiki/Media-Player

@dhruvsingla273
Copy link
Author

dhruvsingla273 commented Jul 14, 2024

@halx99 I tried to get the buffer at the processOutputBuffer function in MediaCodecVideoRenderer
So i set the surface to null and then get the bitmap.

But after the player tries to display the frame as there is no surface it is unable to render,
then the same buffer keeps coming in the processOutputBuffer.

@droid-girl
Copy link
Contributor

We are planning to merge a TFLite sample to platform-samples. Take a look for more details on the integration here

@dhruvsingla273
Copy link
Author

Thanks
I will surely take a look

@droid-girl
Copy link
Contributor

Thanks. Please update if you have any questions.

@google-oss-bot
Copy link
Collaborator

Hey @dhruvsingla273. We need more information to resolve this issue but there hasn't been an update in 14 weekdays. I'm marking the issue as stale and if there are no new updates in the next 7 days I will close it automatically.

If you have more information that will help us get to the bottom of this, just add a comment!

@google-oss-bot
Copy link
Collaborator

Since there haven't been any recent updates here, I am going to close this issue.

@dhruvsingla273 if you're still experiencing this problem and want to continue the discussion just leave a comment here and we are happy to re-open this.

@OctoberNicole

This comment was marked as off-topic.

@androidx androidx locked and limited conversation to collaborators Nov 4, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants