Как с помощью mp4parser обрабатывать видео, взятые из Uri и ContentResolver?

Задний план

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

Эта проблема

Для выбора Uri у нас все работает нормально (решение доступно здесь).

Что касается самой обрезки, мы не смогли найти ни одной хорошей библиотеки с разрешающей лицензией, кроме одной под названием "k4l-video-trimmer" . Библиотека «FFmpeg», например, считается неразрешенной, поскольку она использует GPLv3, которая требует, чтобы приложение, использующее ее, также было с открытым исходным кодом. Кроме того, как я читал, он занимает довольно много (около 9 МБ).

К сожалению, эта библиотека (k4l-video-trimmer) очень старая и не обновлялась годами, поэтому мне пришлось ее разветвить (здесь), чтобы правильно с этим справиться. Для обрезки используется библиотека с открытым исходным кодом под названием "mp4parser".

Проблема в том, что эта библиотека, по-видимому, может обрабатывать только файлы, а не Uri или InputStream, поэтому даже образец может аварийно завершать работу при выборе элементов, которые недоступны, как обычный файл, или даже имеют пути, которые он не может обработать. . Я знаю, что во многих случаях можно получить путь к файлу, но во многих других случаях это невозможно, и я также знаю, что можно просто скопировать файл (здесь), но это не очень хорошее решение, так как файл может быть большим и занимать много места, даже если он уже доступен.

Что я пробовал

Есть 2 места, где библиотека использует файл:

  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));
            }
        }
     ...
    
  2. В файле «TrimVideoUtils», в «startTrim», который вызывает функцию «genVideoUsingMp4Parser». Там он вызывает библиотеку «mp4parser», используя:

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

    Там написано, что они используют FileDataSourceViaHeapImpl (из библиотеки "mp4parser"), чтобы избежать OOM на Android, поэтому я решил остаться с ним.

    Дело в том, что для него есть 4 CTORS, все ожидают какой-то вариации файла: File, filePath, FileChannel , FileChannel+fileName .

Вопросы

  1. Есть ли способ преодолеть это?

Может быть, реализовать FileChannel и смоделировать реальный файл, используя ContentResolver и Uri? Я думаю, это возможно, даже если это означает повторное открытие InputStream при необходимости...

Чтобы увидеть, что у меня получилось, вы можете клонировать проект здесь. Просто знайте, что он не выполняет обрезку, так как код для этого в файле «K4LVideoTrimmer» закомментирован:

//TODO handle trimming using Uri
//TrimVideoUtils.startTrim(file, getDestinationPath(), mStartPosition, mEndPosition, mOnTrimVideoListener);
  1. Возможно, есть лучшая альтернатива этой библиотеке обрезки, которая также является разрешающей (например, имеется в виду лицензия Apache2/MIT)? Тот, у которого нет этой проблемы? Или, может быть, даже что-то из самого фреймворка Android? Я думаю, что класс MediaMuxer может помочь (как написано < a href="https://stackoverflow.com/a/44653626/878126">здесь), но я думаю, что ему может понадобиться API 26, а нам нужно обрабатывать API 21 и над...

РЕДАКТИРОВАТЬ:

Я думал, что нашел решение, используя другое решение для самой обрезки, и написал об этом здесь< /a>, но, к сожалению, он не может обрабатывать некоторые входные видео, в то время как библиотека mp4parser может их обрабатывать.

Пожалуйста, дайте мне знать, возможно ли изменить mp4parser для обработки таких входных видео, даже если они из Uri, а не из файла (без обходного пути простого копирования в видеофайл).


person android developer    schedule 03.02.2019    source источник
comment
Упомянутый вами класс MediaMuxer поддерживается API 18 и выше [developer.android.com/reference /android/media/MediaMuxer]. И ответ stackoverflow.com/a/44653626/878126 относится к Lollipop, который является API21. Я считаю, что вы можете легко использовать это! Кроме того, если у вас есть URI, что плохого в создании вокруг него объекта File и передаче его в библиотеку для обработки? Вы пробовали это? Каков был ответ/ошибка/проблема, если да?   -  person Rahul Shukla    schedule 19.02.2019
comment
@RahulShukla Код, который я нашел, работал (и он из API 18, кстати, а не 21, как по ссылке), но только для некоторых входных файлов. Для некоторых других это вызвало исключение, и я написал об этом здесь: stackoverflow.com/q/54573454/878126. Вы можете проверить POC, который я сделал для этого здесь: github.com/AndroidDeveloperLB/VideoTrimmer . Что касается использования Файла из Uri, то это не всегда возможно. Пример из приложения Google Drive. В моем текущем решении используются оба метода, но все равно может произойти сбой :(   -  person android developer    schedule 19.02.2019


Ответы (2)


Прежде всего предостережение: я не знаком с библиотекой mp4parser, но ваш вопрос показался интересным, поэтому я посмотрел.

Я думаю, вам стоит взглянуть на один из классов, которые, как говорится в комментариях к коду, предназначены «в основном для тестирования». InMemRandomAccessSourceImpl. Чтобы создать фильм из любого URI, код будет следующим:

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 имеет проблемы, особенно если это удаленный контент, тогда как вы действительно не ожидаете этого с файлом в современной системе.

Также следует учитывать MemoryFile, который предоставляет файлоподобный объект, поддерживаемый ashmem. Я думаю, что как-то это можно было бы обработать.

person dr_g    schedule 13.02.2019
comment
Наличие буфера имеет смысл, а обрезка должна использовать некоторую память (не тонны памяти, но все же...). Это кажется логичным, но как я могу это сделать? У вас есть рабочее решение для этого, учитывая Uri и/или способ создания InputStream? Ничего не предполагайте относительно данного ввода. То есть это может быть очень высокое качество и/или большая продолжительность, и мы можем обрезать до крошечной длительности или до огромной (посмотрите на параметры реальной функции обрезки)... - person android developer; 13.02.2019
comment
В своем вопросе вы упоминаете 2 разных места: 1. В файле K4LVideoTrimmer и 2. TrimVideoUtils. Вы разместили решение для 1. заменить на Uri. Приведенный выше пример кода должен предоставить вам решение для 2, начиная с Uri и преобразователя содержимого. Но я согласен, что для высокого качества и продолжительности нужно подумать о том, как вы буферизуетесь. MemoryFile может обеспечить гибкого посредника при чтении из InputStream. А в ситуациях с нехваткой памяти вы можете включить настройку системы для очистки файла. Эта задача может быть дополнительно оптимизирована с использованием кольцевых буферов и/или считывания секций одновременно. - person dr_g; 13.02.2019
comment
Библиотека k4l-video-trimmer использует mprparser, который требует использования файла. Второе решение, которое может обрабатывать Uri, упоминается здесь: stackoverflow.com/q/54573454/878126 , но оно не похоже, не поддерживает некоторые видео (в моем случае те, в которых есть аудио/ac3). А пока вы можете проверить мой репозиторий Github, который использует оба способа (второй вариант является резервным вариантом первого): github.com/ AndroidDeveloperLB/VideoTrimmer . Насчет MemoryFile, вы пробовали? Использует ли он много памяти? Пожалуйста, поделитесь кодом для него. Вы можете использовать для этого мой репозиторий. - person android developer; 14.02.2019
comment
@androiddeveloper Что ты в итоге сделал? - person HB.; 03.10.2019
comment
@androiddeveloper Похоже, вы не решили эту проблему, глядя на это -github.com/AndroidDeveloperLB/VideoTrimmer/blob/ - person HB.; 03.10.2019
comment
@ХБ. Я использовал свое решение и надеялся, что проблем не будет. Позже мы все это забросили и не имели к этому никакого отношения. Но если вы знаете, как это сделать, пожалуйста, дайте мне знать. Чем я мог бы вам помочь, так это тем, что обычно вы можете найти путь, и даже с помощью SAF вы можете получить доступ к файлам (если они файлы). Ссылка: stackoverflow.com/a/57424828/878126 - person android developer; 04.10.2019
comment
@androiddeveloper Возможно, я нашел решение, пожалуйста, обратитесь к комментарию к вашему вопросу здесь - github.com /sannys/mp4parser/issues/357 - person HB.; 23.01.2020

Далее показано, как открыть Uri MediaStore с помощью IsoFile из Mp4Parser. Итак, вы можете увидеть, как получить FileChannel из Uri.

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();
        }
    }
}
person PerracoLabs    schedule 03.06.2020