首页 > 解决方案 > 如何从 Uri 修剪视频,包括 `mp4parser` 库可以处理的文件,但使用 Android 的框架?

问题描述

背景

在过去的几天里,我一直致力于制作一个可定制的、更新版本的视频修剪库,在这里(基于这个库

问题

虽然在大多数情况下,我已经成功地使其可定制,甚至将所有文件都转换为 Kotlin,但修剪本身存在一个主要问题。

它假定输入始终是一个文件,因此如果用户从应用程序选择器中选择一个返回 Uri 的项目,它就会崩溃。这样做的原因不仅是 UI 本身,还因为它用于修剪的库 ( mp4parser ) 假定仅输入文件(或文件路径)而不是 Uri(在此处写到)。我尝试了多种方法让它获得 Uri,但失败了。也在这里写过。

这就是为什么我使用我在 StackOverflow(此处)上找到的解决方案来进行修剪本身。它的好处是它很安静,并且只使用 Android 的框架本身。但是,似乎对于某些视频文件,它总是无法修剪它们。作为此类文件的示例,原始库存储库中有一个,here (此处报告的问题)。

查看异常,这就是我得到的:

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 代码,在这里,在“Gallery3d”包中名为“VideoUtils.java”的类中。可悲的是,我没有看到任何新版本。我看到的最新的是姜饼,在这里

我用它制作的代码如下所示:

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. 在网上和here上搜索,我只发现了一个类似的问题,here,但完全不一样。

问题

  1. 是否可以使用Android的框架来修剪这些有问题的文件?也许有更新版本的视频代码修剪?我当然只对“genVideoUsingMuxer”的视频修剪的纯粹实现感兴趣,就像我上面写的函数一样。

  2. 作为临时解决方案,是否可以检测有问题的输入视频,这样我就不会让用户开始修剪它们,因为我知道它们会失败?

  3. 是否可能有另一种替代方案,具有许可许可证并且不会使应用程序膨胀?对于mp4parser,我在这里写了一个单独的问题。

标签: androidvideo-editingmediamuxer

解决方案


  1. 为什么会发生?

audio/ac3是不受支持的 mime 类型。

MediaMuxer.addTrack()(native) calls MPEG4Writer.addSource(),它在返回错误之前打印此日志消息。

编辑

我的目的不是为您的每个子问题提供答案,而是让您对基本问题有所了解。您选择的库依赖于 Android 的MediaMuxer组件。无论出于何种原因,MediaMuxer开发人员都没有添加对这种特定音频格式的支持。我们知道这一点,因为该软件会打印出明确的消息,然后立即抛出IllegalStateException您问题中提到的内容。

因为该问题仅涉及特定的音频格式,所以当您提供仅视频输入时,一切正常。

要解决此问题,您可以更改库以提供缺少的功能,或者找到更适合您需要的新库。sannies/mp4parser可能是这样一种选择,尽管它有不同的限制(如果我没记错的话,它要求在母带制作过程中所有媒体都在 RAM 中)。我不知道它是否明确支持 ac3,但它应该提供一个框架,您可以在其中添加对任意 mime 类型的支持。

我鼓励您等待更完整的答案。可能有更好的方法来做你想做的事情。但很明显,您使用的库根本不支持所有可能的 mime 类型。


推荐阅读