Задний план
Последние несколько дней я работал над созданием настраиваемой, более обновленной версии библиотеки для обрезки видео, < strong>здесь (на основе этой библиотеки а>)
Эта проблема
Хотя по большей части мне удалось сделать его настраиваемым и даже преобразовать все файлы в Kotlin, у него была серьезная проблема с самой обрезкой.
Предполагается, что ввод всегда является файлом, поэтому, если пользователь выбирает элемент в средстве выбора приложений, который возвращает Uri, происходит сбой. Причиной этого является не только сам пользовательский интерфейс, но и библиотека, которую он использует для обрезки (mp4parser< /strong>) предполагает ввод только файла (или пути к файлу), а не Uri (об этом писал здесь). Я пробовал несколько способов, чтобы вместо этого получить Uri, но потерпел неудачу. Также писал об этом здесь.
Вот почему я использовал решение, которое нашел на StackOverflow (здесь strong>) для самой обрезки. Хорошая вещь в том, что он очень короткий и использует только саму структуру Android. Однако кажется, что некоторые видеофайлы всегда не удается обрезать. В качестве примера таких файлов в исходном репозитории библиотеки есть один, здесь (сообщено о проблеме здесь).
Глядя на исключение, вот что я получил:
E: Unsupported mime 'audio/ac3'
E: FATAL EXCEPTION: pool-1-thread-1
Process: life.knowledge4.videocroppersample, PID: 26274
java.lang.IllegalStateException: Failed to add the track to the muxer
at android.media.MediaMuxer.nativeAddTrack(Native Method)
at android.media.MediaMuxer.addTrack(MediaMuxer.java:626)
at life.knowledge4.videotrimmer.utils.TrimVideoUtils.genVideoUsingMuxer(TrimVideoUtils.kt:77)
at life.knowledge4.videotrimmer.utils.TrimVideoUtils.genVideoUsingMp4Parser(TrimVideoUtils.kt:144)
at life.knowledge4.videotrimmer.utils.TrimVideoUtils.startTrim(TrimVideoUtils.kt:47)
at life.knowledge4.videotrimmer.BaseVideoTrimmerView$initiateTrimming$1.execute(BaseVideoTrimmerView.kt:220)
at life.knowledge4.videotrimmer.utils.BackgroundExecutor$Task.run(BackgroundExecutor.java:210)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:458)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
Что я нашел
- Сообщил о проблеме здесь. Я не думаю, что он получит ответ, так как библиотека не обновлялась годами...
- Глядя на исключение, я попытался также обрезать без звука. Это работает, но это нехорошо, потому что мы хотим нормально обрезать.
- Подумав, что этот код может быть основан на чьем-то коде, я попытался найти оригинальный. Я обнаружил, что он основан на каком-то старом коде Google в приложении галереи, здесь, в классе под названием "VideoUtils.java" в пакете "Gallery3d". К сожалению, я не вижу никакой новой версии для него. Последнее, что я вижу, относится к Gingerbread, здесь< /а>.
Код, который я сделал из него, выглядит так:
object TrimVideoUtils {
private const val DEFAULT_BUFFER_SIZE = 1024 * 1024
@JvmStatic
@WorkerThread
fun startTrim(context: Context, src: Uri, dst: File, startMs: Long, endMs: Long, callback: VideoTrimmingListener) {
dst.parentFile.mkdirs()
//Log.d(TAG, "Generated file path " + filePath);
val succeeded = genVideoUsingMuxer(context, src, dst.absolutePath, startMs, endMs, true, true)
Handler(Looper.getMainLooper()).post { callback.onFinishedTrimming(if (succeeded) Uri.parse(dst.toString()) else null) }
}
//https://stackoverflow.com/a/44653626/878126 https://android.googlesource.com/platform/packages/apps/Gallery2/+/634248d/src/com/android/gallery3d/app/VideoUtils.java
@JvmStatic
@WorkerThread
private fun genVideoUsingMuxer(context: Context, uri: Uri, dstPath: String, startMs: Long, endMs: Long, useAudio: Boolean, useVideo: Boolean): Boolean {
// Set up MediaExtractor to read from the source.
val extractor = MediaExtractor()
// val isRawResId=uri.scheme == "android.resource" && uri.host == context.packageName && !uri.pathSegments.isNullOrEmpty())
val fileDescriptor = context.contentResolver.openFileDescriptor(uri, "r")!!.fileDescriptor
extractor.setDataSource(fileDescriptor)
val trackCount = extractor.trackCount
// Set up MediaMuxer for the destination.
val muxer = MediaMuxer(dstPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
// Set up the tracks and retrieve the max buffer size for selected tracks.
val indexMap = SparseIntArray(trackCount)
var bufferSize = -1
try {
for (i in 0 until trackCount) {
val format = extractor.getTrackFormat(i)
val mime = format.getString(MediaFormat.KEY_MIME)
var selectCurrentTrack = false
if (mime.startsWith("audio/") && useAudio) {
selectCurrentTrack = true
} else if (mime.startsWith("video/") && useVideo) {
selectCurrentTrack = true
}
if (selectCurrentTrack) {
extractor.selectTrack(i)
val dstIndex = muxer.addTrack(format)
indexMap.put(i, dstIndex)
if (format.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
val newSize = format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)
bufferSize = if (newSize > bufferSize) newSize else bufferSize
}
}
}
if (bufferSize < 0)
bufferSize = DEFAULT_BUFFER_SIZE
// Set up the orientation and starting time for extractor.
val retrieverSrc = MediaMetadataRetriever()
retrieverSrc.setDataSource(fileDescriptor)
val degreesString = retrieverSrc.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)
if (degreesString != null) {
val degrees = Integer.parseInt(degreesString)
if (degrees >= 0)
muxer.setOrientationHint(degrees)
}
if (startMs > 0)
extractor.seekTo(startMs * 1000, MediaExtractor.SEEK_TO_CLOSEST_SYNC)
// Copy the samples from MediaExtractor to MediaMuxer. We will loop
// for copying each sample and stop when we get to the end of the source
// file or exceed the end time of the trimming.
val offset = 0
var trackIndex: Int
val dstBuf = ByteBuffer.allocate(bufferSize)
val bufferInfo = MediaCodec.BufferInfo()
// try {
muxer.start()
while (true) {
bufferInfo.offset = offset
bufferInfo.size = extractor.readSampleData(dstBuf, offset)
if (bufferInfo.size < 0) {
//InstabugSDKLogger.d(TAG, "Saw input EOS.");
bufferInfo.size = 0
break
} else {
bufferInfo.presentationTimeUs = extractor.sampleTime
if (endMs > 0 && bufferInfo.presentationTimeUs > endMs * 1000) {
//InstabugSDKLogger.d(TAG, "The current sample is over the trim end time.");
break
} else {
bufferInfo.flags = extractor.sampleFlags
trackIndex = extractor.sampleTrackIndex
muxer.writeSampleData(indexMap.get(trackIndex), dstBuf,
bufferInfo)
extractor.advance()
}
}
}
muxer.stop()
return true
// } catch (e: IllegalStateException) {
// Swallow the exception due to malformed source.
//InstabugSDKLogger.w(TAG, "The source video file is malformed");
} catch (e: Exception) {
e.printStackTrace()
} finally {
muxer.release()
}
return false
}
}
Исключение выдается на val dstIndex = muxer.addTrack(format)
. На данный момент я завернул его в try-catch, чтобы избежать реального сбоя.
Я попытался найти более новые версии этого кода (предполагая, что он был исправлен позже), но безуспешно.
- Поискав в Интернете и здесь, я нашел только один похожий вопрос, здесь, но это совсем не то же самое.
Вопросы
Можно ли использовать платформу Android для обрезки таких проблемных файлов? Может есть более новая версия кода обрезки видео? Меня интересует, конечно, только чистая реализация обрезки видео, как и функция, которую я написал выше, "genVideoUsingMuxer".
В качестве временного решения можно ли обнаружить проблемные входные видео, чтобы я не позволил пользователю начать их обрезать, поскольку я знаю, что они не будут работать?
Может быть, есть другая альтернатива обоим из них, которые имеют разрешительную лицензию и не раздувают приложение? Для
mp4parser
я написал отдельный вопрос здесь.