Reputation: 8023
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
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
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