Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android MediaCodec realtime h264 encoding/decoding latency

I'm working with Android MediaCodec and use it for a realtime H264 encoding and decoding frames from camera. I use MediaCodec in synchronous manner and render the output to the Surface of decoder and everething works fine except that I have a long latency from a realtime, it takes 1.5-2 seconds and I'm very confused why is it so. I measured a total time of encoding and decoding processes and it keeps around 50-65 milliseconds so I think the problem isn't in them. I tried to change the configuration of the encoder but it didn't help and currently it configured like this:

val formatEncoder = MediaFormat.createVideoFormat("video/avc", 1920, 1080)
formatEncoder.setInteger(MediaFormat.KEY_FRAME_RATE, 30)
formatEncoder.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5)
formatEncoder.setInteger(MediaFormat.KEY_BIT_RATE, 1920 * 1080)
formatEncoder.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
val encoder = MediaCodec.createEncoderByType("video/avc")
encoder.configure(formatEncoder, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
val inputSurface = encoder.createInputSurface() // I use it to send frames from camera to encoder
encoder.start()

Changing the configuration of the decoder also didn't help me at all and currently I configured it like this:

val formatDecoder = MediaFormat.createVideoFormat("video/avc", 1920, 1080)
val decoder = MediaCodec.createDecoderByType("video/avc")
decoder.configure(formatDecoder , outputSurface, null, 0) // I use outputSurface to render decoded frames into it
decoder.start()

I use the following timeouts for waiting for available encoder/decoder buffers I tried to reduce their values but it didn't help me and I left them like this:

var TIMEOUT_IN_BUFFER = 10000L // microseconds
var TIMEOUT_OUT_BUFFER = 10000L // microseconds

Also I measured the time of consuming the inputSurface a frame and this time takes 0.03-0.05 milliseconds so it isn't a bottleneck. Actually I measured all the places where a bottleneck could be, but I wasn't found anything and I think the problem is in the encoder or decoder itself or in their configurations, or maybe I should use some special routine for sending frames to encoding/decoding..

I also tried to use HW accelerated codec and it's the only thing that helped me, when I use it the latency reduces to ~ 500-800 milliseconds but it still doesn't fit me for a realtime streaming.

It seems to me that the encoder or decoder buffers several frames before start displaying them on the surface and eventually it leads to the latency and if it really so then how can I disable bufferization or reduce the time of it?

Please help me I'm stucking on this problem for about half a year and have no idea how to reduce the latency, I'm sure that it's possible because popular apps like Telegram, Viber, WhatsApp etc. work fine and without latency so what's the secret here?

UPD 07.07.2021:

I still haven't found a solution to get rid of the latency. I've tried to change h264 profiles, increase and decrease I-frame inteval, bitrate, framerate, but result the same, the only thing that hepls a little to reduce the latency - downgrade the resolution from 1920x1080 to e.g. 640x480, but this "solution" doesn't suit me because I want to encode/decode a realtime video with 1920x1080 resolution.

UPD 08.07.2021:

I found out that if I change the values of TIMEOUT_IN_BUFFER and TIMEOUT_OUT_BUFFER from 10_000L to 100_000L it decreases the latency a bit but increases the delay of showing the first frame quite a lot after start encoding/decoding process.

like image 476
easy_breezy Avatar asked Jun 14 '21 20:06

easy_breezy


2 Answers

It's possible your encoder is producing B frames -- bilinear interpolation frames. They increase quality and latency, and are great for movies. But no good for low-latency applications.

  • Key frames = I (interframes)
  • Predicted frames = P (difference from previous frames)
  • Interpolated frames = B

A sequence of frames including B frames might look like this:

IBBBPBBBPBBBPBBBI
         11111111
12345678901234567

The encoder must encode each P frame, and the decoder must decode it, before the preceding B frames make any sense. So in this example the frames get encoded out of order like this:

1 5 2 3 4 9 6 7 8 13 10 11 12 17 17 13 14 15  

In this example the decoder can't handle frame 2 until the encoder has sent frame 5.

On the other hand, this sequence without B frames allows coding and decoding the frames in order.

IPPPPPPPPPPIPPPPPPPPP

Try using the Constrained Baseline Profile setting. It's designed for low latency and low power use. It suppresses B frames. I think this works.

mediaFormat.setInteger(
        "profile",
         CodecProfileLevel.AVCProfileConstrainedBaseline); 
like image 93
O. Jones Avatar answered Oct 21 '22 02:10

O. Jones


I believe android h264 decoder have latency (at-least in most cases i've tried). Probably that's why android developers added PARAMETER_KEY_LOW_LATENCY from API level 30. However I could decrease the delay some frames by querying for the output some more times. Reason: no idea. It's just result of boring trial and errors

int inputIndex = m_codec.dequeueInputBuffer(-1);// Pass in -1 here bc we don't have a playback time reference

if (inputIndex >= 0) {
    ByteBuffer buffer;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
        buffer = m_codec.getInputBuffer(inputIndex);
    } else {
        ByteBuffer[] bbuf = m_codec.getInputBuffers();
        buffer = bbuf[inputIndex];
    }
    buffer.put(frame);

    // tell the decoder to process the frame
    m_codec.queueInputBuffer(inputIndex, 0, frame.length, 0, 0);
}
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();

int outputIndex = m_codec.dequeueOutputBuffer(info, 0);
if (outputIndex >= 0) {
    m_codec.releaseOutputBuffer(outputIndex, true);
}
outputIndex = m_codec.dequeueOutputBuffer(info, 0);
if (outputIndex >= 0) {
    m_codec.releaseOutputBuffer(outputIndex, true);
}
outputIndex = m_codec.dequeueOutputBuffer(info, 0);
if (outputIndex >= 0) {
    m_codec.releaseOutputBuffer(outputIndex, true);
}
like image 36
Mohammad Avatar answered Oct 21 '22 02:10

Mohammad