Как я могу обрезать видео из Uri, включая файлы, которые может обрабатывать библиотека `mp4parser`, но вместо этого используя платформу Android?

Задний план

Последние несколько дней я работал над созданием настраиваемой, более обновленной версии библиотеки для обрезки видео, < strong>здесь (на основе этой библиотеки )

Эта проблема

Хотя по большей части мне удалось сделать его настраиваемым и даже преобразовать все файлы в Kotlin, у него была серьезная проблема с самой обрезкой.

Предполагается, что ввод всегда является файлом, поэтому, если пользователь выбирает элемент в средстве выбора приложений, который возвращает Uri, происходит сбой. Причиной этого является не только сам пользовательский интерфейс, но и библиотека, которую он использует для обрезки (mp4parser< /strong>) предполагает ввод только файла (или пути к файлу), а не 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)

Что я нашел

  1. Сообщил о проблеме здесь. Я не думаю, что он получит ответ, так как библиотека не обновлялась годами...
  2. Глядя на исключение, я попытался также обрезать без звука. Это работает, но это нехорошо, потому что мы хотим нормально обрезать.
  3. Подумав, что этот код может быть основан на чьем-то коде, я попытался найти оригинальный. Я обнаружил, что он основан на каком-то старом коде 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, чтобы избежать реального сбоя.

Я попытался найти более новые версии этого кода (предполагая, что он был исправлен позже), но безуспешно.

  1. Поискав в Интернете и здесь, я нашел только один похожий вопрос, здесь, но это совсем не то же самое.

Вопросы

  1. Можно ли использовать платформу Android для обрезки таких проблемных файлов? Может есть более новая версия кода обрезки видео? Меня интересует, конечно, только чистая реализация обрезки видео, как и функция, которую я написал выше, "genVideoUsingMuxer".

  2. В качестве временного решения можно ли обнаружить проблемные входные видео, чтобы я не позволил пользователю начать их обрезать, поскольку я знаю, что они не будут работать?

  3. Может быть, есть другая альтернатива обоим из них, которые имеют разрешительную лицензию и не раздувают приложение? Для mp4parser я написал отдельный вопрос здесь.


person android developer    schedule 07.02.2019    source источник


Ответы (1)


  1. Почему это происходит?

audio/ac3 — неподдерживаемый MIME-тип.

MediaMuxer.addTrack() (собственный) вызывает MPEG4Writer.addSource(), который печатает это сообщение журнала перед возвратом ошибки.

ИЗМЕНИТЬ

Моя цель состояла не в том, чтобы дать ответ на каждый из ваших подвопросов, а в том, чтобы дать вам некоторое представление о фундаментальной проблеме. Выбранная вами библиотека основана на компоненте Android MediaMuxer. По какой-то причине разработчики MediaMuxer не добавили поддержку именно этого аудиоформата. Мы знаем это, потому что программное обеспечение распечатывает явное сообщение об этом, а затем немедленно выдает IllegalStateException, упомянутое в вашем вопросе.

Поскольку проблема касается только определенного аудиоформата, когда вы предоставляете вход только для видео, все работает нормально.

Чтобы решить эту проблему, вы можете либо изменить библиотеку, чтобы обеспечить недостающую функциональность, либо найти новую библиотеку, которая лучше соответствует вашим потребностям. sannies/mp4parser может быть одной из таких альтернатив, хотя и имеет другие ограничения (если я правильно помню, для находиться в оперативной памяти во время процесса мастеринга). Я не знаю, поддерживает ли он ac3 явно, но он должен предоставить структуру, в которую вы можете добавить поддержку произвольных типов mime.

Рекомендую дождаться более полного ответа. Могут быть гораздо лучшие способы сделать то, что вы пытаетесь сделать. Но очевидно, что используемая вами библиотека просто не поддерживает все возможные типы MIME.

person greeble31    schedule 07.02.2019
comment
Откуда ты знаешь, что он не поддерживается? Что я могу с этим поделать? Есть ли альтернативы? Почему WhatsApp и Google Photos обрезают его? Почему я могу воспроизводить видео нормально? Это нормально, чтобы преобразовать его, если это необходимо. Возможно ли это или может быть есть другое альтернативное решение? Если нет, есть ли способ обнаружить это с самого начала, чтобы мне не пришлось сообщать пользователю, что я не могу обрезать только после того, как он предпринял для этого шаги? - person android developer; 08.02.2019
comment
На самом деле он использовал его в исходном коде: github.com/titansgroup/k4l-video-trimmer/blob/develop/ и он вылетает с тем же файлом, но мне не удалось его использовать , поскольку требовалось, чтобы я дал ему объект File (или путь), даже если все, что у меня есть, это uri, который не может быть настоящим файлом. Я даже писал об этом здесь: github.com/sannies/mp4parser/issues/357 . Итак, для фактической обрезки я использовал то, что уже предоставляет фреймворк... Поскольку я думаю, что написал для этого запутанный фон, я обновил вопрос. - person android developer; 09.02.2019
comment
Ну да, но он использует MediaMuxer для записи (поэтому вы и получили свою ошибку), тогда как вместо этого он мог писать через mp4parser. - person greeble31; 09.02.2019
comment
Я не могу использовать mp4parser, пока не могу понять, как обрабатывать Uri и InputStream, а не только File. Я действительно пытался. Образец разбился, потому что он не мог обработать Uri (я выбрал файл с помощью приложения «Файлы» Google...). Знаете ли вы, как пользоваться обоими мирами: (обращаться с проблемным файлом, и даже если мы используем Uri, чтобы добраться до него)? - person android developer; 09.02.2019
comment
Ну, это отдельный вопрос, которым я не занимался. Но я думаю, что вы, возможно, упустили мою мысль - вы записываете свой вывод в File. mp4parser отлично работает с файлами. - person greeble31; 09.02.2019
comment
На выходе всегда файл. Я про ввод. Ввод может быть просто Uri, из которого вы можете открыть InputStream. Это потому, что есть много приложений, которые предлагают Uri вместо файла, даже если за кулисами это файл. - person android developer; 09.02.2019
comment
Мы говорим о разных целях, мой друг. Я говорю о выводе. Ваш сбой произошел из-за того, что класс, который вы выбрали для записи файла, не поддерживал определенный тип mime. Я просто указываю, что вы могли бы использовать mp4parser для выполнения записи файла, так как ваш вывод - File. Вопрос о том, как адаптировать mp4parser к входу URI, полностью актуален, но не имеет отношения к этому вопросу. - person greeble31; 09.02.2019
comment
Опять же, это неправильно. Я не могу использовать mpparser, потому что он не может обрабатывать ввод как Uri вместо файла. Выход всегда является файлом в обоих случаях. Это тот же вопрос. Я хочу обрезать видео в файл, и мне нужно обработать его, будь то из файла или Uri. Библиотека mp4parser не работает с Uri. Только файл. Если вы знаете, как с этим справиться (без копирования видео в обычный файл), пожалуйста, напишите. - person android developer; 09.02.2019
comment
Возможно, это неясно, но вы можете прочитать образцы с помощью MediaExtractor, а затем записать их с помощью mp4parser. Таким образом, вопрос о том, как ввести Uri в mp4parser, никогда не возникает. - person greeble31; 09.02.2019
comment
Давайте продолжим обсуждение в чате. - person android developer; 09.02.2019