Фон
За последние несколько дней я работал над созданием настраиваемой, более обновленной версии библиотеки для обрезки видео, здесь (на основена этой библиотеке )
Проблема
Хотя по большей части мне удалось сделать ее настраиваемой и даже преобразовать все файлы в KotlinУ него была серьезная проблема с самой отделкой.
Предполагается, что ввод всегда является файлом, поэтому, если пользователь выбирает элемент из списка приложений, который возвращает Uri, происходит сбой.Причина этого не только в самом пользовательском интерфейсе, но и в том, что библиотека, которую он использует для обрезки ( mp4parser ), предполагает ввод только File (или filepath), а не Uri(писал об этом здесь ).Я попробовал несколько способов, чтобы получить вместо него Uri, но не получилось.Также писал об этом здесь .
Вот почему я использовал решение, которое я нашел в StackOverflow ( здесь ) для самой обрезки.Хорошая вещь об этом - то, что это тихо и использует только платформу 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
я написал отдельный вопрос, здесь .