Android MediaExtractor返回样本失败



我正在尝试解码视频到Android使用MediaCodecMediaExtractor的表面。

首先,我在MediaExtractor对象(下面的extractor)上使用setDataSource(context, uri, null)方法来设置数据源uri。uri来自视频的Android文件选择器。这个调用似乎没有抛出任何异常。

在解码器的onInputBufferAvailable()调用中,然后我从提取器读取一个新的样本,但extractor.readSampleData(..)调用随机地未能读取正确的数据并开始返回大小为-1的缓冲区。理想情况下,当读取整个文件时,大小应该仅为-1。此外,一旦提取器开始返回-1,无论我调用extractor.advance()多少次,它都不会返回正确的样本。

每当这个问题发生时,我也在日志中观察到这个警告消息:

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

代码:

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)
}
}

这种情况并不总是发生,但已经足够频繁了。到目前为止,我只在Android 9操作系统上观察到这种情况。

编辑1:为了防止竞争条件,我将空闲的输入缓冲区保存到阻塞队列中。然后,while循环从队列中取出这些缓冲区。
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
)
}
}
}
}

首先在谷歌上搜索"NuMediaExtractor: read on track 0 failed with error -2147483646"我看到两个结果:A, B

他们写了Android 10修复的bug。比特率太大,需要重新编码视频。

我仍然建议找到可能的解决办法。而不是使用setDataSource(Context, Uri, Map),尝试调用setDataSource(MediaDataSource),实现你自己的MediaDataSource,只有2个方法:getSize,你返回文件的大小,和readAt,你从文件中读取完全整个请求的大小(不返回部分读取InputStream有时做;部分读取只允许在文件末尾)。你可以通过使用RandomAccessFile加载固定文件来调试这个问题,如果你看到这个错误被解决了,你可以实现自己的MediaDataSource从content://scheme读取(这是由系统文件选择器返回的)。

您可以在MediaDataSource中记录读取,并将日志与在另一个没有发生错误的设备上成功运行的日志进行比较(在同一文件上测试)。也许你会看到Android在Android 9上请求不同的范围,或者它请求相同的读取,但仍然失败。这是Android的bug,你可能无法修复。

希望这能帮助你找到问题的原因,甚至解决它。

我认为你的根本问题是uri。我的建议是把它重新解析成更常见的东西,被许多应用程序广泛接受。例如,如果您的uri看起来像这样(以content://开始)content://com.android.providers.downloads.documents/document/3356,请尝试将其更改为file:///storage/emulated/0/document/3356com.android.providers.downloads.documents为包名,document/3356为文件路径。您可以通过Environment.getExternalStorageDirectory()方法获得/storage/emulated/0。然后使用Uri.parse("string")获取uri实例。

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

最新更新