Monday, 18 January 2021

Running MediaMetadataRetriever getFrameAtIndex() with parallel coroutine workers is slower than running it with one coroutine

I'm trying to grab the frame from the video file using MediaMetadataRetriever with multiple parallel coroutine jobs, but the process takes more time comparing with running it with one coroutine.
I don't understand what is the problem, maybe MediaMetadataRetriever doesn't work for parallel execution and it blocks getFrameAtIndex method invocation for the same file? or problem is in the parallel file access?
My goal is to retrieve all bitmaps from ~1 second video file as fast as possible and thats why I decided to grab frames parallelly.

Here is time log with parallel and with one coroutine execution.

with one Coroutine

LOG_TAG: frame range 0..51  calculation for job_1 = 2206 ms
LOG_TAG_FINISHED: total grab time 2224

with 4 parallel coroutine

LOG_TAG: frame range 39..51  calculation for job_4 = 4484 ms
LOG_TAG: frame range 0..12   calculation for job_1 = 4524 ms
LOG_TAG: frame range 27..39  calculation for job_3 = 4634 ms
LOG_TAG: frame range 12..27  calculation for job_2 = 4716 ms
LOG_TAG_FINISHED: total grab time 4747

and the code

suspend fun startGrabbingFrames(src: String){

    val dataRetriever = MediaMetadataRetriever()
    dataRetriever.setDataSource(src)
    val totalFrames =
        dataRetriever.extractMetadata(METADATA_KEY_VIDEO_FRAME_COUNT)?.toLongOrNull() ?: 0L
    dataRetriever.release()

    val frameChunkSize = totalFrames / 4

    val offset1 = totalFrames - frameChunkSize * 2
    val offset2 = totalFrames - frameChunkSize

    val range1 = IntRange(0, frameChunkSize.toInt())
    val range2 = IntRange(frameChunkSize.toInt(), offset1.toInt())
    val range3 = IntRange(offset1.toInt(), offset2.toInt())
    val range4 = IntRange(offset2.toInt(), totalFrames.toInt()-1)
    val time = measureTimeMillis {
        val one = CoroutineScope(Dispatchers.Default).async {  grabFrames(src,range1,"job_1") }
        val two = CoroutineScope(Dispatchers.Default).async { grabFrames(src, range2,"job_2") }
        val three = CoroutineScope(Dispatchers.Default).async { grabFrames(src, range3,"job_3") }
        val four = CoroutineScope(Dispatchers.Default).async { grabFrames(src, range4,"job_4") }
        val frames1 = one.await()
        val frames2 = two.await()
        val frames3 = three.await()
        val frames4 = four.await()
    }
    println("LOG_TAG_FINISHED: total grab time $time")
}

and here is grabFrames method

private suspend fun grabFrames(src: String, range: IntRange, tag: String) = withContext(Dispatchers.Default) {
    val retriever = MediaMetadataRetriever()
    retriever.setDataSource(src)
    val frames = ArrayList<Bitmap?>()
    measureTimeMillis {
        for (i in range) {
            val frame = retriever.getFrameAtIndex(i)
            frames.add(frame)
        }
        retriever.release()
    }.also {
        Log.d("LOG_TAG", "frame range $range  calculation for $tag = ${it} ms")
    }
    frames
}

If I run it with only one coroutine it is almost 2 times faster, as shown in the log.

val one = CoroutineScope(Dispatchers.Default).async {  grabFrames(src,IntRange(0,totalFrames-1),"job_1") }
val fullFrames = one.await()

Also tried to make 4 copy of that file and created 4 MediaMetadataRetriever object for each file and run 4 parallel job but the result was same.



from Running MediaMetadataRetriever getFrameAtIndex() with parallel coroutine workers is slower than running it with one coroutine

No comments:

Post a Comment