1

I am encoding audio and video using the MediaCodec. I start a coroutine that runs the encoding using Dispatchers.IO:

CoroutineScope(Dispatchers.IO).launch {
    videoEncoder.setCallback(object : MediaCodec.Callback() {
        override fun onOutputBufferAvailable(mc: MediaCodec, bufferId: Int, bufferInfo: MediaCodec.BufferInfo) {
           // The thread here is "main" even though a coroutine was launched using Dispatchers.IO
           Log.i("encoder", "Thread name: " + Thread.currentThread().name)
        }
    }
}

I am using the asynchronous callback of MediaCodec to receive the encoded data. But the code inside onOutputBufferAvailable runs on the "main" thread, which is bad. Is there a way to force the encoder to run on the IO thread. Unfortunately, creating another coroutine inside onOutputBufferAvailable is not an option as creating a new coroutine each time the callback is called is way too expensive and will degrade performance.

Johann
  • 27,536
  • 39
  • 165
  • 279

2 Answers2

1

If your main concern is to run the code in the thread other than "main", but you don't necessarily need a coroutine context, then you can create a Handler running on your background thread and then pass it to setCallback() method.

Also, please note that coroutines are very lightweight. You can run thousands or even millions of coroutines concurrently and nothing bad really happens. I'm not sure about creating a coroutine from outside of the coroutine context - it may involve some latency. I suggest testing the performance first, if you did not do it yet.

broot
  • 21,588
  • 3
  • 30
  • 35
  • Found that solution just after posting. Still, it is bound to happen that some third party API doesn't offer a way to run it on a different thread, so knowing how to deal with it is still good to know. – Johann May 28 '21 at 06:51
1

Well, that's something that happens from time to time. You don't actually have a guarantee where the callback will be called. IMHO the best way here will be to guide coroutine. I would prefer using a suspendCoroutine block with a Continuation.

Here you go a simple example.

In your case you can do next:

suspendCoroutine<Int> { cont ->
            videoEncoder.setCallback(object : MediaCodec.Callback() {
                override fun onOutputBufferAvailable(
                    mc: MediaCodec,
                    bufferId: Int,
                    bufferInfo: MediaCodec.BufferInfo
                ) {
                    // The thread here is "main" even though a coroutine was launched using Dispatchers.IO
                    cont.resume(bufferId)
                }
            })
        }

P.S. Pay attention that you will wait until a callback will be triggered.

P.P.S. feel free to adjust the Dispatchers stuff within your needs, provide any.

Yurii Tsap
  • 3,554
  • 3
  • 24
  • 33