修改ffmpeg支持文件描述符,以适配android10沙箱机制

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

问题背景

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值