Shivam Verma
Shivam Verma

Reputation: 8023

Android MediaExtractor fails to return sample

I am trying to decode a video onto a surface in Android using MediaCodec and MediaExtractor.

To start with, I use the setDataSource(context, uri, null) method on the MediaExtractor object (extractor below) to set the data source uri. The uri comes from the Android file picker for videos. This call doesn't seem to throw any exceptions.

In the onInputBufferAvailable() call from the decoder, I then read a new sample from the extractor but the extractor.readSampleData(..) call randomly fails to read the correct data and starts returning buffers of size -1. Ideally, the size should only be -1 when the whole file has been read. Moreover, once the extractor starts returning -1, it never returns the correct sample however many times I call extractor.advance().

I also observe this warning message in the logs whenever this issue happens:

W/NuMediaExtractor: read on track 0 failed with error -2147483646

Code:

object : MediaCodec.Callback() {
    override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
        val buffer = codec.getInputBuffer(index)!!
        try {
            val size = extractor.readSampleData(buffer, 0)
            if (size > 0) {
                decoder.queueInputBuffer(
                    index,
                    0,
                    size,
                    extractor.sampleTime,
                    sampleFlags
                )
                extractor.advance()
            } else if (size == 0) {
                Timber.d("Size 0 sample received from extractor")
            } else if (size == -1) {
            // Size is -1 when no more samples are available                     
        } catch (exception: Exception) {
             Timber.e(exception)
        }
    }

This doesn't happen all the time but frequent enough. So far, I have only observed this on the Android 9 OS.

Edit 1: To prevent a race condition, I save the free input buffers into a blocking queue. A while loop then picks up these buffers from the queue.

    private val inputBuffersQueue: BlockingQueue<InputBufferData> = LinkedBlockingQueue()


    private fun feedSamplesToDecoder(extractor: MediaExtractor, trimStartUs: Long, trimEndUs: Long) {
        inputHandler.post {
            while (!wasEOSInputBufferFed) {
                val inputBuffer = inputBuffersQueue.take()
                val decoder = inputBuffer.codec
                val index = inputBuffer.index
                val sampleSize = extractor.readSampleData(inputBuffer.codec.getInputBuffer(inputBuffer.index)!!, 0)
                if (sampleSize > 0) {
                    val sampleTime = extractor.sampleTime
                    lastSampleTimestampUs = sampleTime
                    val sampleFlags = extractor.sampleFlags
                    decoder.queueInputBuffer(
                        index,
                        0,
                        sampleSize,
                        sampleTime - trimStartUs,
                        sampleFlags
                    )
                    extractor.advance()
                } else if (sampleSize == 0) {
                    Timber.d("Size 0 sample received from extractor")
                } else {
                    decoder.queueInputBuffer(
                        index,
                        0,
                        0,
                        0,
                        BUFFER_FLAG_END_OF_STREAM
                    )

                }
            }
        }
    }

Upvotes: 2

Views: 1452

Answers (2)

Pointer Null
Pointer Null

Reputation: 40391

First search google for "NuMediaExtractor: read on track 0 failed with error -2147483646". I see 2 results: A, B

They write about bug fixed in Android 10. Something about too big bitrate and need to re-encode video.

Still I'd suggest to find possible workaround. Instead of using setDataSource(Context, Uri, Map), try to call setDataSource(MediaDataSource), implement your own MediaDataSource, just 2 methods: getSize where you return size of file, and readAt where you read from file completely entire requested size (don't return after partial reads that InputStream sometimes does; partial read is allowed only at end of file). You can debug this by loading fixed file using RandomAccessFile, and if you see that bug is resolved, you can implement own MediaDataSource reading from content:// scheme (which is returned by system file picker).

You can log reads in your MediaDataSource, and compare the log with successful run on another device where bug doesn't happen (test on same file). Maybe you'll see that Android requests different ranges on Android 9, or it requests same reads but still fails. Then that's Android bug and you probably can't fix that.

Hopefully this helps you to get closer to finding reason of problem, or even fix it.

Upvotes: 1

Darkman
Darkman

Reputation: 2991

I think the root problem of yours is the uri. My suggestion is to reparse it into something more common that widely accepted by many apps. For example is your uri looks like this (starts with content://) content://com.android.providers.downloads.documents/document/3356, try to change it into file:///storage/emulated/0/document/3356. com.android.providers.downloads.documents is a package name and document/3356 is a file path. You can get /storage/emulated/0 via Environment.getExternalStorageDirectory() method. Then use Uri.parse("string") to get a uri instance.

Uri uri = Uri.parse("file:///storage/emulated/0/document/3356");

Upvotes: 0

Related Questions