【问题标题】:How can I trim a video from Uri, including files that `mp4parser` library can handle, but using Android's framework instead?如何从 Uri 修剪视频,包括 `mp4parser` 库可以处理的文件,但改用 Android 的框架?
【发布时间】:2019-07-01 13:50:23
【问题描述】:

背景

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

问题

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

它假设输入总是一个文件,所以如果用户从应用程序选择器中选择一个返回 Uri 的项目,它就会崩溃。造成这种情况的原因不仅仅是 UI 本身,还因为它用于修剪的库 (mp4parser) 假定仅输入文件(或文件路径)而不是 Uri(写了 here)。我尝试了多种方法让它获得 Uri,但失败了。还写了here

这就是为什么我使用我在 StackOverflow (here) 上找到的解决方案来进行修剪本身。它的好处是它很安静,并且只使用 Android 的框架本身。但是,似乎对于某些视频文件,它总是无法修剪它们。作为此类文件的示例,原始库存储库中有一个,here(问题报告为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. 报告了问题here。我不认为它会得到答案,因为图书馆多年没有更新......
  2. 查看异常,我也尝试进行无声修剪。这行得通,但这不是一件好事,因为我们要正常修剪。
  3. 考虑到这段代码可能是基于别人的代码,我试图找到原始代码。我发现它基于其画廊应用程序here 上的一些旧 Google 代码,位于“Gallery3d”包中名为“VideoUtils.java”的类中。可悲的是,我没有看到任何新版本。我看到的最新的是姜饼,here

我用它编写的代码如下所示:

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,但完全不一样。

问题

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

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

  3. 是否有其他替代方案可以替代这两者,具有许可许可证并且不会使应用程序膨胀?对于mp4parser,我写了一个单独的问题here

【问题讨论】:

    标签: android video-editing mediamuxer


    【解决方案1】:
    1. 为什么会这样?

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

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

    编辑

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

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

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

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

    【讨论】:

    • 你怎么知道不支持?我能做些什么呢?有没有其他选择? WhatsApp 和 Google Photos 怎么会修剪得很好?我怎么能正常播放视频?如果需要,可以转换它。有可能还是有另一种替代解决方案?如果没有,有什么方法可以从一开始就检测出来,这样我就不必告诉用户只有在他采取了措施之后我才能修剪?
    • 实际上它在原始代码中使用它:github.com/titansgroup/k4l-video-trimmer/blob/develop/… 并且它与同一个文件崩溃,但我没有使用它,因为它需要我给它一个 File 对象(或路径) ,即使我所拥有的只是一个不能成为真正文件的 uri。我什至在这里写过:github.com/sannies/mp4parser/issues/357。因此,对于实际的修剪,我使用了框架已经提供的内容......因为我认为我为此写了一个令人困惑的背景,所以我更新了这个问题。
    • 嗯,对,但它使用MediaMuxer 进行写作(这就是你得到错误的原因),而它本可以通过mp4parser 写作,而不是.
    • 我不能使用mp4parser,只要我不知道如何处理 Uri 和 InputStream 而不仅仅是 File。我真的试过了。该示例崩溃了,因为它无法处理 Uri(我使用 Google 的“文件”应用程序选择了该文件......)。你知道如何“享受两全其美:(处理有问题的文件,即使我们使用 Uri 来获取它)?
    • 嗯,这是一个我没有研究过的单独问题。但我认为您可能错过了我的观点——您正在将输出写入Filemp4parser 可以很好地处理文件。
    猜你喜欢
    • 1970-01-01
    • 2015-05-27
    • 1970-01-01
    • 2022-09-23
    • 1970-01-01
    • 2012-07-17
    • 2019-05-28
    • 2015-08-01
    • 1970-01-01
    相关资源
    最近更新 更多