Mobile-ffmpeg: Android: SAF & FileDescriptor

Created on 2 Feb 2020  路  77Comments  路  Source: tanersener/mobile-ffmpeg

I need to use this library on android using an uri from SAF.
Therefore it would be great if you implemented an api for using a file descriptor.

An example for how to implement this so the uir can be passed to the NDK can be found here:
https://github.com/wseemann/FFmpegMediaMetadataRetriever/blob/b42986fb4044c54131379407598d8ac0ff1a3326/gradle/fmmr-library/core/src/main/java/wseemann/media/FFmpegMediaMetadataRetriever.java#L214

question

Most helpful comment

@pawaom
I've opened an issue on issuetracker

It seems that we can continue using the data column to access the file path. I have tested this and I was able to pass a file to mobile-ffmpeg on Android R. This includes the SD Card.

The only issue I have now is that on Android Q, this doesn't work. I'm waiting for Google to reply. I feel they should give us the option to enable legacy storage programmatically. We will then be able to access files on Android Q as well.

I will let you know when they reply.

@tanersener
I think you can close this issue as there is nothing you have to do on the library side. Thank you for your support and great library.

All 77 comments

Can you please give some more info like what is the issue you're facing right now and why that feature needs to be implemented in the library rather than the application?

I need to parse metadata of a lot of media files from the users phone storage.

On Android 10 you don't have access to the users sd card using the file api:
https://developer.android.com/about/versions/10/privacy/changes#scoped-storage

Instead you need to use the SAF framework:
https://developer.android.com/guide/topics/providers/document-provider

You fire an intent:
https://developer.android.com/reference/android/content/Intent.html#ACTION_OPEN_DOCUMENT

And get an uri in onActivityResult. This uri is no file uri but a content provider uri.
Therefore you can't use it with mobile-ffmpeg at all (unless you make a deep temporary copy of the file, which is no option here as I can't duplicate gigabytes of media.)

You can use the content provider to obtain a file descriptor:
contentResolver.openFileDescriptor(uri, "r"). and work with that.
But this need integration with mobile-ffmpeg.

Thanks for explaining the issue in detail.

MobileFFmpeg for Android is made of two modules; one native module (written in C/C++) and one Java module. There is a JNI layer between these two and Java module sends API requests to native module through this JNI layer.

Argument parsing and opening input/output files are all implemented in native module. Supported protocols are also defined there. As far as I know, SAF is not available in NDK. That means that I can't use SAF inside the native module. So, I don't think that SAF can be integrated with MobileFFmpeg. Do you have any recommendations?

@wseemann

Can you help out here?

@tanersener I'm not that familiar with NDK and I do not want to highjack this issue, I just thought I would give my 2 cents.

As @PaulWoitaschek mentioned, on Android we can not access the file (or its path) directly, unless the file is located in our application's directory. Instead, we have to make use of the ContentResolver to open a FileDescriptor. As shown below:

FileDescriptor fd = getContentResolver().openFileDescriptor(Uri, "r").getFileDescriptor();

In the project that was provided as an example above, the owner of that library checks to see if the Uri that was passed is a content:// Uri. If it was then he uses a FileDescriptor instead of passing the path of the file, please have a look here:

https://github.com/wseemann/FFmpegMediaMetadataRetriever/blob/b42986fb4044c54131379407598d8ac0ff1a3326/gradle/fmmr-library/core/src/main/java/wseemann/media/FFmpegMediaMetadataRetriever.java#L214


So you can create 2 native methods, one for handling file paths(as you have it currently):
https://github.com/tanersener/mobile-ffmpeg/blob/917c22e20947e89d4c1cf4ce4290909360f8973b/android/app/src/main/java/com/arthenica/mobileffmpeg/Config.java#L642

and one for passing the FileDescriptor, something like this:

native static int nativeFFmpegExecute((final String[] arguments, final FileDescriptor fd) {

On the C/C++ side you can then handle this depending on which one of the above was called.
Of course, there will also have to be another method added in FFmpeg.java:

https://github.com/tanersener/mobile-ffmpeg/blob/917c22e20947e89d4c1cf4ce4290909360f8973b/android/app/src/main/java/com/arthenica/mobileffmpeg/FFmpeg.java#L55

Like this:

public static int execute(final String[] arguments, final FileDescriptor fd) {

Developers using your library could then have the option to pass a FileDescriptor, something like this:

FileDescriptor fd = getContentResolver().openFileDescriptor(Uri, "r").getFileDescriptor();
//Note that, instead of passing the path at the end, FileDiscriptor is passed instead.
FFmpeg.execute("some_commands_by_developer"+outputPath, fd);

Edit:

This is how he handles it from C:

https://github.com/wseemann/FFmpegMediaMetadataRetriever/blob/b42986fb4044c54131379407598d8ac0ff1a3326/gradle/fmmr-library/library/src/main/jni/metadata/ffmpeg_mediametadataretriever.c#L304

int set_data_source_fd(State **ps, int fd, int64_t offset, int64_t length) {
    char path[256] = "";

    State *state = *ps;

    ANativeWindow *native_window = NULL;

    if (state && state->native_window) {
        native_window = state->native_window;
    }

    init(&state);

    state->native_window = native_window;

    int myfd = dup(fd);

    char str[20];
    sprintf(str, "pipe:%d", myfd);
    strcat(path, str);

    state->fd = myfd;
    state->offset = offset;

    *ps = state;

    return set_data_source_l(ps, path);
}

There is no need to change the C++ side at all. A neat trick lets you work with a file path instead of the FileDescriptor. It's based on the specs of the Linux /proc/self/fd files.

As service to the community, the integer descriptor may be extracted on the Java side of the library (if it sniffs the content:// prefix), but may be easily done by the app developer who uses the library.

Hi, another reason that must support FileDescriptor is defined here
https://github.com/tanersener/mobile-ffmpeg/issues/185

Above trick, I am testing with the other project with same purpose, but this behaviour can be changed by Google in the future.

I know you expect the library to handle URIs internally but I think using the following method is the most elegant solution for SAF.

File descriptors received from SAF can be used using the pipe protocol. Both input and output files can be defined with pipe protocol. But some file formats require the output to be seekable, so pipe protocol will fail with them. Be careful about them.

String safUriToFFmpegPath(final Uri uri) {
    try {
        ParcelFileDescriptor parcelFileDescriptor = getContentResolver().openFileDescriptor(uri, "r");
        return String.format(Locale.getDefault(), "pipe:%d", parcelFileDescriptor.getFd());
    } catch (FileNotFoundException e) {
        return "";
    }
}

All this is unnecessary, I pondered upon this myself and hated this elegant library for it. I did it using a utility function like:

val filePath = RealPathUtil.getRealPath(
                           context,
                            currentUri
                        )

so, contentUri to filePath is what you need. It is just as fast.

@RowlandOti
The RealPathUtil you are referring to is probably querying the _data column to get the path of the file. I have released a library myself that converts a Uri to a "real" path. The problem is that the _data column can no longer be accessed when you are targeting 29.

You are right, this will only work for API < 29. This was the only caveat. @HBiSoft

@tanersener
I've tested the pipe protocol and it is working.

But some file formats require the output to be seekable

Can you please explain what is meant with the above?

@HBiSoft

Muxers of some file formats like mp4 and mov requires the output to be seekable to write file metadata/header information correctly. Unfortunately pipe protocol does not support seeking. Therefore you may receive muxer does not support non seekable output error if you try to use pipe protocol in an output file.

There are some options that can be used to overcome this limitation, like -movflags frag_keyframe or -movflags empty_moov. But using these options has consequences. For example, you may not have a seekable video file at the end. So, you need to be careful about the options you use. FFmpeg's mov, mp4, ismv guide includes the list of options and explains what they are used for. It may help you to understand the those consequences better.

Update: Unfortunately input files are affected from this limitation too. You can't use mp4 and mov files with pipe protocol as input unless they are created using -movflags +faststart. There is not way to overcome this limitation.

@tanersener
Thank you for your informative reply.
As I understand it, this will only be an issue if I use the pipe protocol with the output and not when I use it with the input? If this is the case, I will then be able to set the output path to my application directory and all will be good.

There is no need to change the C++ side at all. A neat trick lets you work with a file path instead of the FileDescriptor. It's based on the specs of the Linux /proc/self/fd files.

As service to the community, the integer _descriptor_ may be extracted on the Java side of the library (if it sniffs the content:// prefix), but may be easily done by the app developer who uses the library.

About this solution?, why does not any one interest this one?

@HBiSoft I missed some details about input files. Updated my previous post with the following information. It looks like safUriToFFmpegPath can be used as a temporary workaround but a protocol like saf: or fd: has to be implemented.

Unfortunately input files are affected from this limitation too. You can't use mp4 and mov files with pipe protocol as input unless they are created using -movflags +faststart. There is not way to overcome this limitation.

@kingvhit That solution is a trick that works for some devices and doesn't work on some others. I tested it and it didn't work for me. I receive Permission denied errors when I try. You can try and see if it works for you.

@tanersener : Hi, I have tested on Xiaomi Mi 8 Pro (Adroid 9), and Google Pixel XL 3 (android 10) and all of them working well.

I have concern when you told that it's is not really work for all devices :'(. Could you please tell me which device are you testing on before.

Just I want to re-check it on my app.

@tanersener I agree with:

a protocol like saf: or fd: has to be implemented.

I think the best is to use fd: because we can open a FileDescriptor with any(almost) Uri. Then we, using the library, can pass the FileDescriptor instead of the path. This will work on all API's and no "hacks" are involved. I know my first comment means more work, but I believe it is the best/correct way - https://developer.android.com/training/data-storage/shared/media#native

@kingvhit
My old Galaxy A5 (2016) (Android 7.0) receives /proc/self/fd/66: Permission denied.
Also Pixel 2 Simulator with API 24 also receives permission denied error for me.

@tanersener I am afraid this only means that there is no universal way of handling this. I didn't expect Permission denied on Android 7, but anyways you only need this workaround for API 29 and higher. BTW, my Android 7.0 device handles this perfectly. I only bothered to handle input in my simple exercise, but it works even cross-process: see https://github.com/alexcohn/FFmpeg-Android/commit/686c4b50653e39c5acb181b0d09cec03e873af5d

@alexcohn Tried to test your pull request to see whether I'm missing something but I still receive /proc/11193/fd/52: Permission denied on my device. Attached the logs here if you want to take a look.

Which file did you choose? For me, uri content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fqq.mov points to /sdcard/Download/qq.mov (a.k.a. /storage/emulated/0/Download/qq.mov).

I tested with a file on my SD Card, the content uri is content://com.android.externalstorage.documents/document/6330-6333%3ATest.mp4. It is pointing to /storage/6330-6333/Test.mp4 and I also get permissions denied -
/proc/23074/fd/70: Permission denied

I tested the above by calling:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        if (requestCode == SELECT_VIDEO_REQUEST && resultCode == RESULT_OK) {
                ParcelFileDescriptor parcelFileDescriptor = null;
                try {
                    parcelFileDescriptor = getContentResolver().openFileDescriptor(data.getData(), "r");
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
                int fd = parcelFileDescriptor.getFd();
                int pid = Process.myPid();
                String mediaFile = "/proc/" + pid + "/fd/" + fd;

                int rc = FFmpeg.execute("-i "+mediaFile+" -crf 18 -c:v libx264 -c:a copy "+outputPath");
                if (rc == RETURN_CODE_SUCCESS) {
                    Log.i(Config.TAG, "Command execution completed successfully.");
                } else if (rc == RETURN_CODE_CANCEL) {
                    Log.i(Config.TAG, "Command execution cancelled by user.");
                } else {
                    Log.i(Config.TAG, String.format("Command execution failed with rc=%d and the output below.", rc));
                    Config.printLastCommandOutput(Log.INFO);
                }
        }
    }

By default, by experimentation, I noticed that drives mounted to /storage/XXXX-XXX will by default have write permissions denied.

I don't think we should face similar problems with /sdcard/Download/ aka /storage/emulated/0/Download/ as both have read/write access.

@alexcohn

If I select /storage/emulated/0/Download/video1.mp4 file, it gets content://com.android.externalstorage.documents/document/primary%3ADownload%2Fvideo1.mp4 uri. I don't know why it has a different format then yours or from @HBiSoft's file.

````
opened content://com.android.externalstorage.documents/document/primary%3ADownload%2Fvideo1.mp4 as /proc/14517/fd/52
/proc/14517/fd/52: Permission denied

@tanersener
The reason why it is different is - If you select a file from the downloads provider directly, it will return the Uri as content://com.android.providers.downloads.documents/document/3656 or like in @alexcohn's case content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fqq.mov and if you select a file by going to your internal storage then Downloads, it will return content://com.android.externalstorage.documents/document/primary%3ADownload%2FNameOfVideo.mp4.

See the deffirence between com.android.providers.downloads and com.android.externalstorage.documents/document/primary.


@alexcohn
If the Uri returned is a raw file then it works. For example, if I select a file from the downloads provider and I get a Uri like
```text
content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fqq.mov

 then it works, but if I select the same file, by selecting it from Internal Storage/Download and the returned Uri is 
```text
content://com.android.externalstorage.documents/document/primary%3ADownload%2F`Fqq.mov

then I get the permissions denied.

@HBiSoft Thanks for explaining that. Unfortunately, selecting a file from one of the providers doesn't improve the case for me. Selected a file from Videos provider which was actually under the /storage/emulated/0/Pictures/ path and got the url content://com.android.providers.media.documents/document/video%3A727, which still receives permission denied errors.

@tanersener
I see. I also tested with different providers/paths and it was very inconsistent, some worked and some didn't. The same when using the pipe protocol, a lot of videos failed, especially videos that were recorded with an iPhone. What other options do we have? How is it that it works with the FFmpegMediaMetadataRetriever library as mentioned here?

I don't know any other options we can try right now.

I think FFmpegMediaMetadataRetriever is using an offset to skip the data and just reads the metadata/header. But I'm not sure, I think @alexcohn may have a better idea.

Unfortunately, I don't know how this can be overcome. Regarding the FFmpegMediaMetadataRetriever, they use ffmpeg as library, so they are not limited with filenames-based API. Having an open descriptor, you can access the file the way you want (e.g. funopen()).

People think that the problem loading media files (and some other types) from /sdcard is a bug: https://issuetracker.google.com/issues/147619577. More stars there may have a positive effect! I gave mine.

@kingvhit
My old Galaxy A5 (2016) (Android 7.0) receives /proc/self/fd/66: Permission denied.
Also Pixel 2 Simulator with API 24 also receives permission denied error for me.

according to this video , there were recent changes in android , to support android.system.os.lseek so may be /proc/self/fd/66 was not supported earlier

https://www.youtube.com/watch?v=oIn0MZQJpp0&feature=youtu.be&t=961

as per this , it looks like to be the expected behavior

https://developer.android.com/training/data-storage/shared/media#native

so we can use

Uri contentUri = ContentUris.withAppendedId(
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
        cursor.getLong(Integer.parseInt(BaseColumns._ID)));
String fileOpenMode = "r";
ParcelFileDescriptor parcelFd =
        resolver.openFileDescriptor(contentUri, fileOpenMode);
if (parcelFd != null) {
    int fd = parcelFd.detachFd();
    // Pass the integer value "fd" into your native code. Remember to call
    // close(2) on the file descriptor when you're done using it.
}

to manage it in a better way we may check the android version using

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
//  use /proc/self/fd/66
}
else {
// use file path
}

I dont know if this is the perfect solution, but till we find a better sollution we can use this, as I said earlier I am a Novice, to FFmpecg and android can some one help with the way to get '/proc/self/fd/66' I think we can get it from a native code (I dont know NDK) can some body share a .so file to pass it to the FFmpeg code

thanks

You don't need to check SDK_INT. The detached file descriptor will work for earlier Android versions, just as well. The caveat is that on Q, you cannot use /proc/self/fd/66 if it points to a file on sdcard (/sdcard/Downloads are still OK, but, as far as I understand, through a different Provider).

@alexcohn
I agree. It is clear that the only way is to pass an open file descriptor. Is there any reason this will not work?


Edit:
This seems like exactly what we want (I see you commented on it):

https://stackoverflow.com/a/59004193/8199772

I have asked for help here
6963

I dont know if they would help, but if somebody can really clarify on this accessing files in native C/C++ code with Google Scoped Storage API thing, it would be really helpful, otherwise many apps which are in production may stop working

You don't need to check SDK_INT. The detached file descriptor will work for earlier Android versions, just as well. The caveat is that on Q, you cannot use /proc/self/fd/66 if it points to a file on sdcard (/sdcard/Downloads are still OK, but, as far as I understand, through a different Provider).

just a thought, how are you getting the fd int,

I guess, one should use

String fileOpenMode = "r";
ParcelFileDescriptor parcelFd =
        resolver.openFileDescriptor(contentUri, fileOpenMode);

where String fileOpenMode = "r"; is the important part

You don't need to check SDK_INT. The detached file descriptor will work for earlier Android versions, just as well. The caveat is that on Q, you cannot use /proc/self/fd/66 if it points to a file on sdcard (/sdcard/Downloads are still OK, but, as far as I understand, through a different Provider).

just a thought, how are you getting the fd int,

I guess, one should use

String fileOpenMode = "r";
ParcelFileDescriptor parcelFd =
        resolver.openFileDescriptor(contentUri, fileOpenMode);

where String fileOpenMode = "r"; is the important part

you can easily get the fd number by method

String fileOpenMode = "r";
ParcelFileDescriptor parcelFd =
        resolver.openFileDescriptor(contentUri, fileOpenMode);
parcelFd.getFdId();

I don't know why, but could you please write down all your expected result of your issue

@kingvhit
My old Galaxy A5 (2016) (Android 7.0) receives /proc/self/fd/66: Permission denied.
Also Pixel 2 Simulator with API 24 also receives permission denied error for me.

unfortunately my Android Q emulator has crashed, and not starting at all, while I was testing, so it will take time , the problem is not about getting fd but may be the permission required to read the file, which we can access using String fileOpenMode = "r";

@kingvhit
My old Galaxy A5 (2016) (Android 7.0) receives /proc/self/fd/66: Permission denied.
Also Pixel 2 Simulator with API 24 also receives permission denied error for me.

unfortunately my Android Q emulator has crashed, and not starting at all, while I was testing, so it will take time , the problem is not about getting fd but may be the permission required to read the file, which we can access using String fileOpenMode = "r";

Let me know, which is expected result you want to do.

E.g:

  • you want to input a file from A (uri, file) and then process -> export the file results to B destination (uri, file)
  • you just want to open a document from all of Provider (file manager, google driver, Google photo, ... ) and read data of it.

I have to make my app working with all of above case, with some tip trick in this issue. If you understand what is my question, let me more detail of you case.

case 1
I want to input a file from A (uri, file) and then process -> export the file results to B destination (uri, file)

Firstly, as you target SDK to 29, when you pick a file from the Other Apps (Google Driver, File Manager...) you just get a uri with content:// prefix.

My solution is:

First, check a uri is internal storage or Document Provider by other app (Google Driver, One Driver, ...) or is a external storage (sd-card)

NOTE: We only have permission to access internal storage by absolute path, like /storage/emulated/0/DCIM... and we don't have any permission (read/write) to access to the other absolute path of the other Document Provider or private internal storage of other app like /storage/emulated/0/data/data/com.googledrive.com/** except owner of that apps.

Bellow is a tip trick of above issue:

  • When you get a URI -> convert it to proc absolute path -> Then, check this path is readable or not.
    If Yes -> (maybe this path is internal storage) -> normally reading.
    If no -> (maybe this path is in private storage of the other application) -> copy this file into cached dir of your app (use contentResolver) -> normally reading by cached dir by your app, mark this case.

Bellow is a way to copy file from all of URI into cached dir without permission

val contentResolver = getApplication<Application>().contentResolver
val inputStream = contentResolver.openInputStream(uri)
val outputStream = FileOutputStream(getApplication<Application>().cacheDir)

Read file by absolute file path above to continue working aboslute path (input file) on your app.

  • How to write to target uri that the user have to pick by SAF.
    By the SAF, like as above, the user can pick a output folder in Document Provider (Google Driver, One Driver,...), SD-card or internal storage
    > In case this path is in internal storage -> You can easily to write file into it with abosolute path.
    > In case this path is in Document Provider (E.g: /data/data/com.google.driver...) or external storage (SD-card): You need using abosolute path of your app (like cached dir) to export the result file, then copy it into target uri you want.
    This is a method that you can do.
// Folder to extract a file.
val treeDocFile = DocumentFile.fromTreeUri(getApplication(), data)
// Create a output file with no data (just create a file)
val docFile = treeDocFile.createFile("text/csv", fileName)
// Copy data of output file from cached dir into target uri
val contentResolver = getApplication<Application>().contentResolver
// Input file from cached dir
val exportedCsv = File(context.cacheDir.absolutePath,fileName)
val inputStream = FileInputStream(exportedCsv)
// Output file into target uri
val outputStream = contentResolver.openOutputStream(docFile.uri, "w")

// write file into target uri
inputStream.copyTo(out)

@kingvhit
I do not believe this is a feasible solution.

Let's say the file size if 2GB>, it will take very long to copy/write a new file.
By doing it as you say, you will have to write the file to your applications directory to get a file scheme Uri and get the path from that Uri(the user will have to wait for the file to be copied/written), then run the FFmpeg command (the user will have to wait again).

If the file is from (Google Driver, One Drive, ...) then there is no other way then to copy the file to your application's directory and get the path from that. But I do not believe that this should be the option with SD Card.

Also, with what you said:

First, check a uri is internal storage or Document Provider by other app (Google Driver, One Driver, ...) or is a external storage (sd-card)

AFAIK, there is no way to check if a Uri points to a file on the SD Card. Look here


I feel this should be handled by the library and I think we should stop looking at "workarounds/tricks" and focus on doing it right.

As I mentioned in my previous comment, this answer got it working.

In this question (above), he mentioned the following (he is talking about /proc/self/fd/" + fd):

Passing this to native C/C++ code works, but only if the file is in phone main storage. If the user tries to open a file that is on external SD card inserted into the phone slot, it does not work - there is no read permission for the file opened this way. I can only grab the file descriptor int number and use fdopen(fd). But this will require modifying the source code of 3rd party libraries (open source or licensed), and a big headache, whenever the original source of these libraries is updated.

And with the solution he provided, there is no need to change the native library itself.

After wasting another good day of my life on the Android "Scoped Storage" B.S., I found a solution that works, without modifying the source of 3rd party native libraries, provided that one has the source of these libraries and can build them.

@HBiSoft : I agree with you about this is a tip/tricks to handle this issue. May I discuss in wrong topic, because my app does not have any solution to modify native code, but FFmpeg mobile here can do it.

This is a last confirm that I was tested on my app.

If the file is from (Google Driver, One Drive, ...) then there is no other way then to copy the file to your application's directory and get the path from that. But I do not believe that this should be the option with SD Card.

=> We don't need to care about this folder is a member of Document Provider or SD-card, but we can check writable permission with its file. All of them is the same.
If write permission is no, use content resolver to open uri as input stream from this file from them, then write to application dir.

I agree with you, it's not best solution to handle the large file but this case is only appear when user picking file or input file, output file which are not internal storage where our app have full permission to read/write dirrectly (just only need READ/WRITE permission). If the input file, output file still internal storage, we can still using fd path based proc (/proc/self/fd/66).

I tested with a file on my SD Card, the content uri is content://com.android.externalstorage.documents/document/6330-6333%3ATest.mp4. It is pointing to /storage/6330-6333/Test.mp4 and I also get permissions denied -
/proc/23074/fd/70: Permission denied

I tested the above by calling:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        if (requestCode == SELECT_VIDEO_REQUEST && resultCode == RESULT_OK) {
                ParcelFileDescriptor parcelFileDescriptor = null;
                try {
                    parcelFileDescriptor = getContentResolver().openFileDescriptor(data.getData(), "r");
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
                int fd = parcelFileDescriptor.getFd();
                int pid = Process.myPid();
                String mediaFile = "/proc/" + pid + "/fd/" + fd;

                int rc = FFmpeg.execute("-i "+mediaFile+" -crf 18 -c:v libx264 -c:a copy "+outputPath");
                if (rc == RETURN_CODE_SUCCESS) {
                    Log.i(Config.TAG, "Command execution completed successfully.");
                } else if (rc == RETURN_CODE_CANCEL) {
                    Log.i(Config.TAG, "Command execution cancelled by user.");
                } else {
                    Log.i(Config.TAG, String.format("Command execution failed with rc=%d and the output below.", rc));
                    Config.printLastCommandOutput(Log.INFO);
                }
        }
    }

how do you create file in outputPath can you please elaborate

@pawaom
outputPath is my applications directory.

File directoryToStore = getBaseContext().getExternalFilesDir("tempDirectory");
if(directoryToStore != null) {
    if (!directoryToStore.exists()) {
        if (directoryToStore.mkdir()) ; //directory is created;
    }
}
// Output path passed to FFmpeg
String outputPath = directoryToStore+"/testFile.mp4";

The path will be:

/storage/emulated/0/Android/data/com.package.name/files/tempDirectory/testFile.mp4

any update guys , has anyone tested on multiple devices using the Firebase Test lab,

https://www.youtube.com/watch?v=o0aUuKztO4A

I tested with a file on my SD Card, the content uri is content://com.android.externalstorage.documents/document/6330-6333%3ATest.mp4. It is pointing to /storage/6330-6333/Test.mp4 and I also get permissions denied -
/proc/23074/fd/70: Permission denied

have you used takePersistableUriPermission

if (resultCode == RESULT_OK) {
                getContentResolver().takePersistableUriPermission(data.getData(),
                        Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            } 

@pawaom

has anyone tested on multiple devices using the Firebase Test lab

What do you want to test? There is nothing to test at the moment.

have you used takePersistableUriPermission

Yes, I did.


@tanersener
Can you have a look at this(and the answer)? It looks like we can add that to the build and perhaps create a native method, similar to nativeFFmpegExecute and return the path, or just use that file.

@HBiSoft Solution posted in the answer has to be tested. I don't know what will that idiocy_fopen_fd function return for files on the cloud?

so what is this all about

1) file path access to (read) media will be allowed
2) media modification API (To edit delete)

https://youtu.be/UnJ3amzJM94?t=1451

so what are we supposed to do , its really confusing now

@tanersener

I don't know what will that idiocy_fopen_fd function return for files on the cloud?

I assume you mean if a file was selected from Dropbox, Google Drive etc, I will base my answer on that ->
When selecting a file from Dropbox, for example, the file gets downloaded first(by the Dropbox application) before returning the Uri. So, the file is not actually on the cloud.

It will only work if the Uri has a ContentProvider and all applications that share files to other applications (like Dropbox, Google Drive etc) have to have a ContentProvider.

@HBiSoft I looked at idiocy_fopen_fd function again and I noticed that idiocy_fopen_fd function does not return a path. I don't think I can use that.

some explanation about scoped storage
https://proandroiddev.com/working-with-scoped-storage-8a7e7cafea3

it seems that getContentResolver().openFileDescriptor(contentUri, fileOpenMode) is not able to handle files in SDcard I have tested the same code works for files on the internal(phone's) storage but for SDcard there is an issue of permission denied

came across this
https://stackoverflow.com/a/28805474/3126760

but how can we use it with FFMpeg

this shows the changes for Q

https://youtu.be/3EtBw5s9iRY?t=1006

I have not tested it on Android Q, has any one done it, Google has asked native Library developers to Contact for Bugs and reports @tanersener , @HBiSoft have you tried to reach out to them regarding the issues

https://youtu.be/3EtBw5s9iRY?t=1718

I have tested on a Physical device Moto C android 7.0 (Api 24)and an Emulator(Pixel XL Api 29 ) with the same file on SDcard

The Strange thing was the same code worked on Emulator(Pixel XL Api 29 )

but not on Moto C android 7.0 (Api 24)

I guess it has to do with the changes in android Api 29

so my guess is to use cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA) for Less than Api 29
and use "/proc/" + pid + "/fd/" + fd for Api > 29

I changed the code and it worked properly with

if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
String mediaFile = "/proc/" + pid + "/fd/" + fd;
                rc = FFmpeg.execute("-y -i " + mediaFile + " -filter:v scale=1280:720 -c:a copy " + outputPath);
else{
 String Videopath =  null;
                String[] Projection = {
                        MediaStore.Video.Media._ID,
                        MediaStore.Video.Media.DATA};
                String selection = MediaStore.Video.Media._ID + " like ?";
                String[] selectionArgs = new String[]{
                        "" + videoId + ""};
                Cursor cursor = getContentResolver().query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                        Projection, selection, selectionArgs, null);
                if (cursor.moveToFirst()) {
                    do {
                       Videopath  = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
                    } while (cursor.moveToNext());
                }
                if (cursor != null) {
                    cursor.close();
                }
rc = FFmpeg.execute("-y -i " + Videopath  + " -filter:v scale=1280:720 -c:a copy " + outputPath);
}
            }

_Can any one confirm its working from them as well_

@pawaom What do you recommend asking google? I don't think asking anything about a non-documented API (/proc/self/fd/) is a good idea?

@tanersener @pawaom
I think we should put this issue on hold until the next release of Android, where presumably file paths will be returned specifically for situations like this. Even though using file paths are more memory/battery intensive, we (using FFmpeg) only call the file once.

I'm still not sure how this will work, so we will have to wait and see. (I will be asking a question on StackOverflow about this and let you know.)

For now, we can request legacy storage android:requestLegacyExternalStorage="true" till the next release.


I asked a question about this on StackOverflow. It is still too early to know how this will work. Apparently, Google will release their first beta release for Android R next month, so will we have to wait and see.

@pawaom What do you recommend asking google? I don't think asking anything about a non-documented API (/proc/self/fd/) is a good idea?

the have mentioned, we can reach out to them even for specific usecase
https://youtu.be/3EtBw5s9iRY?t=1730

the Things that we need to know What are the best practices to use this Library

1) should we use (/proc/self/fd/) for all cases
2) or should we use it only for Above Api 29
3) you can present your own case of not being able to run on older devices(Android 7 etc)(Why it doesnot work there but on API 29, do we need to make changes to the library)
4) can we use MediaStore.Video.Media.RELATIVE_PATH for Above Api 29 I have tested for that and the execution failed
5) the problems faced for pipe protocol what can be done about that, (any other issues that you might face)

hope you reach out to them and clear a few doubts we all developers have

@tanersener @pawaom
I think we should put this issue on hold until the next release of Android, where presumably file paths will be returned specifically for situations like this. Even though using file paths are more memory/battery intensive, we (using FFmpeg) only call the file once.

I'm still not sure how this will work, so we will have to wait and see. (I will be asking a question on StackOverflow about this and let you know.)

For now, we can request legacy storage android:requestLegacyExternalStorage="true" till the next release.

I asked a question about this on StackOverflow. It is still too early to know how this will work. Apparently, Google will release their first beta release for Android R next month, so will we have to wait and see.

I have tested for MediaStore.Video.Media.RELATIVE_PATH for Above Api 29 and the execution failed
I dont know if they are going to provide file paths

this issue needs to be resolved soon

@pawaom
As I said in my previous comment, we will have to wait for Android R. We are not the only developers that have this issue (Have a look here).

I have tested for MediaStore.Video.Media.RELATIVE_PATH for Above Api 29 and the execution failed

I do not think RELATIVE_PATH was intended to retrieve the file path. Instead, it is used to save media to a specific location in the MediaStore, for example Movies/MyFolder. If you query a file from MediaStore that has the RELATIVE_PATH column, it will return the MediaStore directory, for example DCIM/SomeFolder and not the path as you would expect it.

I dont know if they are going to provide file paths

Currently, it is unclear and there is no point in speculating. Google knows about this issue and they will provide us with an alternative.

@tanersener @pawaom

Apparently, Google will release their first beta release for Android R next month, so will we have to wait and see.

Finally android 11 is released
https://techcrunch.com/2020/02/19/google-launches-the-first-developer-preview-of-android-11/

https://android-developers.googleblog.com/2020/02/Android-11-developer-preview.html

https://www.androidpolice.com/2020/02/19/scoped-storage-in-android-11-will-have-exemptions-for-older-apis-and-core-apps-like-file-managers/

some thing called opt-in raw file path access for media is allowed

https://developer.android.com/preview/privacy/storage#media-files-raw-paths

This probably involves the new MANAGE_EXTERNAL_STORAGE permission.

@alexcohn
I'm not sure, I think MANAGE_EXTERNAL_STORAGE is intented for app that requires broad access like file managing applications.

Some apps have a core use case that requires broad file access, such as file management or backup & restore operations.

The permissions to access it also looks ugly Allow access to manage all files where I only want the user to select one file.

Also from the docs:

Starting in Android 11, apps that have the READ_EXTERNAL_STORAGE permission can read a device's media files using direct file paths and native libraries. This new capability allows your app to work more smoothly with third-party media libraries.

But this doesn't help because how are we supposed to retrieve the path from MediaStore.. We can use the file path but be can't retrieve it (unless there is a way I don't know of, without using the _data column).

Android has put up some additional documentation regarding Storage access in android 11

here are the links
https://medium.com/androiddevelopers/modern-user-storage-on-android-e9469e8624f9

https://developer.android.com/preview/privacy/storage

and This is from commonsware

https://commonsware.com/blog/2020/03/22/r-raw-paths-all-files-access.html

@tanersener , @HBiSoft we can even file feedback and get specific help for use cases impacted by this feature

here

https://google.qualtrics.com/jfe/form/SV_9HOzzyeCIEw0ij3?Source=scoped-storage

@pawaom
I've opened an issue on issuetracker

It seems that we can continue using the data column to access the file path. I have tested this and I was able to pass a file to mobile-ffmpeg on Android R. This includes the SD Card.

The only issue I have now is that on Android Q, this doesn't work. I'm waiting for Google to reply. I feel they should give us the option to enable legacy storage programmatically. We will then be able to access files on Android Q as well.

I will let you know when they reply.

@tanersener
I think you can close this issue as there is nothing you have to do on the library side. Thank you for your support and great library.

@HBiSoft , according to this

https://medium.com/androiddevelopers/modern-user-storage-on-android-e9469e8624f9

they have written

However, we understand that many apps depend heavily on APIs that use file paths, including third-party libraries, that cannot easily switch to using file descriptors. So in Android 11, APIs and libraries that use file paths will be enabled again. Your app can use the requestLegacyExternalStorage manifest attribute to ensure compatibility for users running Android 10

also if you read commonware's blog, it seems we need to use old way for upto android p , then for android Q requestLegacyExternalStorage and again for android R use the new access Raw file path thing, all this is really confusing

I have also filed an issue with them from the link they provided

https://google.qualtrics.com/jfe/form/SV_9HOzzyeCIEw0ij3?Source=scoped-storage

If few others do the same they might consider it to be urgent

@pawaom

Your app can use the requestLegacyExternalStorage manifest attribute to ensure compatibility for users running Android 10

If that's the case, then I'm all sorted. I've tested and everything seems to be working fine.

Thanks everyone who contributed to sort this out 馃憤

finally they have understood it

https://issuetracker.google.com/issues/151407044#comment10

Do keep requestLegacyExternalStorage in the manifest if you need legacy storage on Q devices. It will be ignored on R devices once you target R. We are improving the documentation on this.

Re DATA column: will publish more guidance on this soon

I am adding this comment just for others who might refer to it in the future

this can be useful, @alexcohn , @HBiSoft @tanersener
https://youtu.be/RjyYCUW-9tY?t=300

and

https://github.com/android/uamp/issues/325#issuecomment-627368296

@pawaom
I'm not sure why you are sharing this?

We've had access to files/paths from the MediaStore since API 1, the only issue (why this question was started in the first place) was that it was going to be removed(as it is in Android 10 without requesting legacy storage), but Google decided to not remove access in Android R (Instead they introduced FUSE, which is basically the same as it always was).

I clarified everything in my answer here as well - https://stackoverflow.com/a/60917774/5550161

https://developer.android.com/preview/privacy/storage#media-direct-file-native

@HBiSoft , according to the new explanation , this is just an updated info for any one who might check it in the future, I shared tagging you and a few others, because the changes in Android 10 had caused confusion, I was unaware of your answer in Stackoverflow, sorry for the trouble

There are two little problems with the fopen() approach. For one, it does not work on Android 10 and there doesn't seem to be an intention to fix that for the ~20% of devices currently active. Second, this does not work smoothly for Google Drive and other document providers. I have addressed both issues in https://github.com/tanersener/mobile-ffmpeg/pull/440.

There are two little problems with the fopen() approach. For one, it does not work on Android 10 and there doesn't seem to be an intention to fix that for the ~20% of devices currently active. Second, this does not work smoothly for Google Drive and other document providers. I have addressed both issues in #440.

they have mentioned

If you access media files using the File API or native libraries, it's recommended that you opt out of scoped storage by setting requestLegacyExternalStorage to true in your app's manifest file. That way, your app behaves as expected on devices that run Android 10.

@alexcohn
I had a quick look at your pull request, it looks very promising. Could you perhaps push it to a different branch so we can test it?

Which different branch could it be? Feel free to test directly from https://github.com/alexcohn/mobile-ffmpeg/tree/experiment/scoped-storage

@alexcohn @tanersener any plan to give updated Gradle version for these changes?

But soon Android 11 will come, and we cannot opt out of scoped storage, how to solve this issue?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

JayParikh20 picture JayParikh20  路  4Comments

krislyy picture krislyy  路  5Comments

rabelhmd picture rabelhmd  路  7Comments

vlazdra picture vlazdra  路  6Comments

andyrenkehe picture andyrenkehe  路  8Comments