问题背景
android10默认执行文件沙箱机制,native层代码失去了通过文件路径访问公共媒体文件的权限。当时可通过android:requestLegacyExternalStorage="true" 来兼容,设置这个标志后依然可以通过路径访问。
估计是谷歌考虑到不太合理,android11改回来了,android11的真机上native层恢复可以通过路径访问公共媒体文件,不需要设置android:requestLegacyExternalStorage="true"。
但是蛋疼的是,发的目标android11的包还是有可能被android10真机下载,所以android:requestLegacyExternalStorage="true"这个还是得设置以免android10的用户出错。
另外,发包带上android:requestLegacyExternalStorage="true"这个标志还会收到谷歌的警告。
基于以上麻烦,决定试试使用文件描述符来代替路径。在java层通过MediaStore的权限打开媒体文件,取得文件描述符,然后把文件描述符传到native层,native层使用文件描述符来读写文件。这样就可以把android:requestLegacyExternalStorage="true"甩掉。
解决思路
ffmpeg只支持路径参数的api,我们需要想办法让他支持文件描述符(FD)。
一种方法是重写新的支持FD的api,这样修改太大,最后放弃这种做法。
看了一下ffmpeg内部,关于文件操作的部分,在AVformat模块里面。刚好使用的文件api不是fopen这套,是系统io这套(open,read),也就是说文件描述符只要传进去就能直接用。
想了一番后,决定不需要大改,把FD有效传进去就行了。而FD是个int值,可以转成字符串送进去,在里面再恢复int,这样利用现有的路径就能带进去。
为了区别真正的路径,我们选个假路径作为沟通的“密码”就行了。
"fileDescriptorkey/" + FD
例如在外面avformat_open_input传 “fileDescriptorkey/123” 进去,里面检测到fileDescriptorkey这串“密码”,就知道这不是普通路径,是带货来的,后面跟的123就是FD值。
接下来就是修改ffmpeg里面的代码,识别到这个key就另外处理就行了。
ffmpeg修改
跟踪一下代码,确认里面关于路径逻辑修改后,不影响整个解码流程走向,确实可行。
而最后定位到只要修改avformat模块下的file.c文件就可以打到我要的效果。这个文件刚好负责全部的普通文件处理,没有什么耦合性,容易修改。
改两个函数,添加一个函数,就行,如下
static int getfdfs(const char *str,char *ptr)
{
while (*str != '.' && *str != 0) {
*ptr = *str;
ptr++;
str++;
}
*ptr = 0;
return 0;
}
static int file_check(URLContext *h, int mask)
{
int ret = 0;
const char *filename = h->filename;
av_strstart(filename, "file:", &filename);
char *fds;
char fdss[64] = { 0 };
if (av_strstart(filename, "fileDescriptorkey/", &fds)) {
int fd;
getfdfs(fds, fdss);
fd = atoi(fdss);
if (fd != -1) {
#if HAVE_FCNTL
int flag = 0;
flag = fcntl(fd, F_GETFL);
if (flag != -1) {
int accmode;
accmode = flag & O_ACCMODE;
if (accmode == O_RDONLY)
ret |= mask&AVIO_FLAG_READ;
else if (accmode == O_WRONLY)
ret |= mask&AVIO_FLAG_WRITE;
else if (accmode == O_RDWR){
ret |= mask&AVIO_FLAG_READ;
ret |= mask&AVIO_FLAG_WRITE;
}
}
else {
av_log(h, AV_LOG_WARNING, "ttflog file_check cannot fcntl\n");
return AVERROR(errno);
}
#else
ret |= mask&AVIO_FLAG_READ;
#endif
}
else {
av_log(h, AV_LOG_WARNING, "ttflog file_check fd<=0 %d\n",fd);
}
}
else
{
#if HAVE_ACCESS && defined(R_OK)
if (access(filename, F_OK) < 0)
return AVERROR(errno);
if (mask&AVIO_FLAG_READ)
if (access(filename, R_OK) >= 0)
ret |= AVIO_FLAG_READ;
if (mask&AVIO_FLAG_WRITE)
if (access(filename, W_OK) >= 0)
ret |= AVIO_FLAG_WRITE;
#else
struct stat st;
# ifndef _WIN32
ret = stat(filename, &st);
# else
ret = win32_stat(filename, &st);
# endif
if (ret < 0)
return AVERROR(errno);
ret |= st.st_mode&S_IRUSR ? mask&AVIO_FLAG_READ : 0;
ret |= st.st_mode&S_IWUSR ? mask&AVIO_FLAG_WRITE : 0;
#endif
}
return ret;
}
static int file_open(URLContext *h, const char *filename, int flags)
{
FileContext *c = h->priv_data;
int access;
int fd;
struct stat st;
char *fds;
av_strstart(filename, "file:", &filename);
if (av_strstart(filename, "fileDescriptorkey/", &fds)) {
char fdss[64] = { 0 };
getfdfs(fds, fdss);
fd = atoi(fdss);
av_log(h, AV_LOG_WARNING, "ttflog fileDescriptor-key %d\n",fd);
//if (fd <= 0) {
// return -1000;
//}
//if (fcntl(fd, F_GETFD, 0)) {
//}
if (fstat(fd, &st) != 0) {
av_log(h, AV_LOG_WARNING, "ttflog cannot fstat\n");
return AVERROR(errno);
}
else {
h->is_streamed = S_ISFIFO(st.st_mode);
av_log(h, AV_LOG_WARNING, "ttflog is_streamed %d\n", h->is_streamed);
}
}
else {
if (flags & AVIO_FLAG_WRITE && flags & AVIO_FLAG_READ) {
access = O_CREAT | O_RDWR;
if (c->trunc)
access |= O_TRUNC;
}
else if (flags & AVIO_FLAG_WRITE) {
access = O_CREAT | O_WRONLY;
if (c->trunc)
access |= O_TRUNC;
}
else {
access = O_RDONLY;
}
#ifdef O_BINARY
access |= O_BINARY;
#endif
fd = avpriv_open(filename, access, 0666);
if (fd == -1)
return AVERROR(errno);
h->is_streamed = !fstat(fd, &st) && S_ISFIFO(st.st_mode);
}
c->fd = fd;
/* Buffer writes more than the default 32k to improve throughput especially
* with networked file systems */
if (!h->is_streamed && flags & AVIO_FLAG_WRITE)
h->min_packet_size = h->max_packet_size = 262144;
return 0;
}
java层示例
获取FD组成串
pfd = this.getContentResolver().openFileDescriptor(uri, "r");
if (pfd != null) {
//Bitmap bitmap = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
int fd = pfd.detachFd();
path = "fileDescriptorkey/" + fd;
把这个path放avformat_open_input就可以了。
补充
可以加个“.mp4”之类指定一下文件类型,记忆中ffmpeg里面好像有根据文件后缀的逻辑判断。没仔细看。
我目前只验证了读权限没问题,写没用到
如果确认不会用到seek操作,使用pipe也是可以的,这样不用改ffmpeg

针对Android 10的文件沙箱机制,原有的ffmpeg通过路径访问公共媒体文件的方式失效。文章介绍了如何通过在java层获取文件描述符并传递到ffmpeg native层,实现ffmpeg支持文件描述符读取,以此规避沙箱限制。通过修改ffmpeg源码,将文件描述符伪装成路径参数传递,确保在Android 10设备上的正常运行。同时,文章提供了java层的示例代码,并提及了可能的扩展应用。
7232

被折叠的 条评论
为什么被折叠?



