-
-
Notifications
You must be signed in to change notification settings - Fork 796
Android: SAF & FileDescriptor #334
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: Instead you need to use the SAF framework: You fire an intent: And get an uri in onActivityResult. This uri is no file uri but a content provider uri. You can use the content provider to obtain a file descriptor: |
Thanks for explaining the issue in detail.
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 |
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 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 So you can create 2 native methods, one for handling file paths(as you have it currently):
and one for passing the 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.
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 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: 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 As service to the community, the integer descriptor may be extracted on the Java side of the library (if it sniffs the |
Hi, another reason that must support FileDescriptor is defined here 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.
|
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 |
You are right, this will only work for API < 29. This was the only caveat. @HBiSoft |
@tanersener
Can you please explain what is meant with the above? |
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 There are some options that can be used to overcome this limitation, like 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 |
@tanersener |
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
@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 |
@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:
I think the best is to use |
@kingvhit |
@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 alexcohn/FFmpeg-Android@686c4b5 |
Which file did you choose? For me, uri |
I tested with a file on my SD Card, the content uri is 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 I don't think we should face similar problems with /sdcard/Download/ aka /storage/emulated/0/Download/ as both have read/write access. |
If I select
|
@alexcohn
The permissions to access it also looks ugly Also from the docs:
But this doesn't help because how are we supposed to retrieve the path from |
Android has put up some additional documentation regarding Storage access in android 11 here are the links 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 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 |
@HBiSoft , according to this https://medium.com/androiddevelopers/modern-user-storage-on-android-e9469e8624f9 they have written
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 |
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
I am adding this comment just for others who might refer to it in the future |
this can be useful, @alexcohn , @HBiSoft @tanersener and |
@pawaom We've had access to files/paths from the 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 |
they have mentioned
|
@alexcohn |
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? |
any progress in support on Android 11? |
After a couple of days of struggle with this one, here's a workaround that supports Android 11 and will probably be good enough until this one gets released:
Tested on Samsung s8+ (Android 10 rooted) and Pixel 4a (Android 11). Drawbacks of this approach might include memory issues if the file itself is larger. |
Is this available in release 4.4? First, I create a new MediaStore entry with
|
Also just to chip into those who will make the mistake of trying to copy the file first - it is going to very slow for some devices and users on the other end may notice lagging especially if the file is large. If reading the file is a repetitive and on-demand thing, then that approach is highly inefficient. On the other hand, reading is as a Uri resource directly is pretty smooth. I build an app around other android users consuming media content hosted on android media hubs and the experience was very smooth. |
I'm facing the same error using release 4.4. Has anybody a solution?
|
@aGreatDay, @blue-peacock you can not simply use a
|
@alexcohn thank you, but when I pass the Uri
The
|
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
The text was updated successfully, but these errors were encountered: