使用mp4parser,我该如何处理从Uri和ContentResolver获取的视频?

16

背景

我们希望让用户从任何应用程序中选择视频,然后将视频剪辑为最长为5秒。

问题

对于获取要选择的Uri,我们已经找到了有效的解决方案(该解决方案在这里可用)。

至于剪辑本身,我们无法找到任何具有宽容许可证的好库,除了一个名为"k4l-video-trimmer"的库。例如,库"FFmpeg"被认为不允许使用GPLv3,这要求使用它的应用程序也是开源的。此外,据我所知,它需要相当多的空间(约9MB)。

不幸的是,这个库(k4l-video-trimmer)非常古老,多年没有更新,所以我不得不分叉它(这里)来处理它。它使用一个名为"mp4parser"的开源库进行剪辑。

问题在于,这个库似乎只能处理文件,而不能处理UriInputStream,所以即使选择无法到达的项目(如普通文件)或甚至具有无法处理的路径的路径时,示例也可能会崩溃。我知道在许多情况下可以获取文件的路径,但在许多其他情况下是不可能的,并且我也知道可以仅复制文件(这里),但这不是一个好的解决方案,因为即使已经访问到文件,它也可能很大并占用很多空间。

我的尝试

库使用文件的两个位置:

  1. 在"K4LVideoTrimmer"文件中,在"setVideoURI"函数中,只需获取文件大小即可显示。在这里,基于Google的文档,解决方案非常简单:

    public void setVideoURI(final Uri videoURI) {
        mSrc = videoURI;
        if (mOriginSizeFile == 0) {
            final Cursor cursor = getContext().getContentResolver().query(videoURI, null, null, null, null);
            if (cursor != null) {
                int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
                cursor.moveToFirst();
                mOriginSizeFile = cursor.getLong(sizeIndex);
                cursor.close();
                mTextSize.setText(Formatter.formatShortFileSize(getContext(), mOriginSizeFile));
            }
        }
     ...
    
    在“TrimVideoUtils”文件中,在调用“genVideoUsingMp4Parser”函数的“startTrim”中,它通过以下方式调用“mp4parser”库:

  2. Movie movie = MovieCreator.build(new FileDataSourceViaHeapImpl(src.getAbsolutePath()));
    

    它说他们使用来自“mp4parser”库的FileDataSourceViaHeapImpl来避免在Android上出现OOM,因此我决定继续使用它。

    问题是,它有四个构造函数,每个都需要一些文件变体:File、filePath、FileChannel、FileChannel+fileName。

    问题

    1. 有没有办法克服这个问题?

    也许可以实现FileChannel并模拟真实的文件,通过使用ContentResolverUri来完成?我猜这可能是可能的,即使意味着需要在需要时重新打开InputStream...

    如果您想查看我所做的工作,可以在此处克隆项目https://github.com/AndroidDeveloperLB/VideoTrimmer/。只需知道它不会进行任何修剪,因为“K4LVideoTrimmer”文件中的代码已被注释掉:

    //TODO handle trimming using Uri
    //TrimVideoUtils.startTrim(file, getDestinationPath(), mStartPosition, mEndPosition, mOnTrimVideoListener);
    
    1. 也许有一个更好的替代这个修剪库的选择,同样是宽容的(比如Apache2/MIT许可证)?一个没有这个问题的选择?或者甚至是Android框架自身的东西?我认为MediaMuxer类可以帮助解决这个问题(就像这里所写的那样),但我认为它可能需要API 26,而我们需要处理API 21及以上的版本...

    编辑:

    我认为通过使用不同的修剪方案,我已经找到了解决方案,并在这里写了一些内容,但不幸的是它无法处理一些输入视频,而mp4parser库可以处理它们。

    请告诉我是否可以修改mp4parser以处理这样的输入视频,即使它来源于Uri而不是文件(而不是仅将其复制到视频文件中的变通方法)。


您提到的MediaMuxer类支持API 18及以上版本[https://developer.android.com/reference/android/media/MediaMuxer]。而答案https://dev59.com/XZ_ha4cB1Zd3GeqP7vZh#44653626是指Lollipop,即API21。我相信您可以轻松使用它!此外,如果您有URI,那么在其周围创建一个File对象并将其传递给库进行处理有什么问题吗?您尝试过这个吗?如果您尝试过,那么响应/错误/问题是什么? - Rahul Shukla
@RahulShukla 我找到的代码有效(顺便说一下,它是来自API 18,而不是链接中的21),但只适用于某些输入文件。对于其他一些文件,它会抛出异常,并且我在这里写了关于它的问题:https://dev59.com/L-k5XIcBkEYKwwoY597M。你可以在这里检查我为此制作的POC:https://github.com/AndroidDeveloperLB/VideoTrimmer。至于使用Uri中的File,这并不总是可能的。例如,来自Google Drive应用程序的示例。我的当前解决方案同时使用了两种方法,但仍然可能失败 :( - android developer
2个回答

4

首先需要说明的是:我不熟悉mp4parser库,但你的问题看起来很有趣,所以我查了一下。

我认为你应该看看其中一个被代码注释标记为“主要用于测试”的类InMemRandomAccessSourceImpl。要从任何URI创建一个Movie,代码应该如下:

try {
    InputStream  inputStream = getContentResolver().openInputStream(uri);
    Log.e("InputStream Size","Size " + inputStream);
    int  bytesAvailable = inputStream.available();
    int bufferSize = Math.min(bytesAvailable, MAX_BUFFER_SIZE);
    final byte[] buffer = new byte[bufferSize];

    int read = 0;
    int total = 0;
    while ((read = inputStream.read(buffer)) !=-1 ) {
        total += read;
    }
    if( total < bytesAvailable ){
        Log.e(TAG, "Read from input stream failed")
        return;
    }
    //or try inputStream.readAllBytes() if using Java 9
    inputStream.close();

    ByteBuffer bb = ByteBuffer.wrap(buffer);
    Movie m2 = MovieCreator.build(new ByteBufferByteChannel(bb),
        new InMemRandomAccessSourceImpl(bb), "inmem");

} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

我认为,你想要实现的目标与解析器所采用的方法存在一定的冲突。它依赖于本地文件以避免大量的内存开销,并且只有在整个数据集可用时才可以进行字节的随机访问,这与流式处理的方法不同。

在将缓冲区提供给解析器之前,至少需要缓冲所需剪辑的数据量。如果您正在寻找抓取短片段并且缓冲不太麻烦,那么这可能对您可行。如果从InputStream中读取出现问题,尤其是对于远程内容,您可能会遇到I / O异常之类的问题,而在现代系统上的文件中您并不期望出现这种情况。

还需要考虑MemoryFile,它提供了一个基于ashmem的文件对象。我认为这也可以被纳入考虑。


1
拥有缓冲区是有意义的,修剪应该使用一些内存(不需要大量内存,但仍然......)。这似乎很合理,但我该怎么做呢?对于给定的Uri和/或创建InputStream的方式,您是否有有效的解决方案?不要假设任何关于给定输入的东西。这意味着它可能具有非常高的质量和/或长时间,并且我们可能会将其修剪为极短的持续时间或巨大的持续时间(请查看修剪的真实函数的参数)... - android developer
在你的问题中提到了2个不同的地方:1. 在“K4LVideoTrimmer”文件中; 2. “TrimVideoUtils”。你已经贴出了一个解决方案,用Uri替换1。上面的代码示例应该为你提供了一个解决方案,从Uri和内容解析器开始解决2。但是我同意,高质量和长时间的视频需要考虑如何缓存。MemoryFile可以作为一个灵活的中介,当你从InputStream中读取时。在低内存情况下,则可以启用系统设置来清除文件。这个任务可以进一步优化,使用环形缓冲区和/或分段阅读。 - dr_g
"k4l-video-trimmer" 库使用 "mprparser",它需要文件使用。第二种解决方案可以处理 Uri,这里提到了:https://dev59.com/L-k5XIcBkEYKwwoY597M,但似乎不支持某些视频(在我的情况下,那些带有“audio/ac3”的视频)。目前,您可以检查我的 Github 存储库,它使用了两种方法(第二种是第一种的后备):https://github.com/AndroidDeveloperLB/VideoTrimmer。关于 MemoryFile,您试过了吗?它会使用很多内存吗?请分享代码。您可以使用我的存储库进行此操作。 - android developer
@androiddeveloper 你最终做了什么? - HB.
@HB。我使用了我的解决方案,希望没有问题。后来我们放弃了它,与它无关。但是如果您知道如何做这些事情,请告诉我。我可以帮助您的是,通常您可以找到路径,即使使用SAF也可以访问文件(如果它们是文件)。链接:https://dev59.com/Guk5XIcBkEYKwwoY49wn#57424828 - android developer
显示剩余2条评论

0
下面的代码片段展示了如何使用Mp4Parser中的IsoFile打开MediaStore Uri。因此,您可以看到如何从Uri获取FileChannel。
public void test(@NonNull final Context context, @NonNull final Uri uri) throws IOException
{
    ParcelFileDescriptor fileDescriptor = null;

    try
    {
        final ContentResolver resolver = context.getContentResolver();
        fileDescriptor = resolver.openFileDescriptor(uri, "rw");

        if (fileDescriptor == null)
        {
            throw new IOException("Failed to open Uri.");
        }

        final FileDescriptor  fd          = fileDescriptor.getFileDescriptor();
        final FileInputStream inputStream = new FileInputStream(fd);
        final FileChannel     fileChannel = inputStream.getChannel();

        final DataSource channel = new FileDataSourceImpl(fileChannel);
        final IsoFile    isoFile = new IsoFile(channel);

        ... do what you need ....
    }
    finally
    {
        if (fileDescriptor != null)
        {
            fileDescriptor.close();
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接