关于 Android 8.0 的录像 quota exceeded 异常

xiaoxiao2021-02-28  64

这是个之前从没碰到过的问题, 记录一下这无语的跟踪过程 ... 两周前的某一天, 忽然一封邮件转过来, 测试那边描述手机录像在内置卡录满状态下会出问题, 录出的文件播不了;  按说, 磁盘录满根本不是个大事, Camera APK 会统计内置卡或者外置卡的剩余可用空间, 减掉个保留值设下来, 然后 MPEG4Writer 还会按这个值配个 95% 的折扣 mMaxFileSizeLimitBytes, 录像过程中会时刻比对录像的临时文件 .mp4.tmp 和这个 mMaxFileSizeLimitBytes, 随时终止录像并上报 MAX_FILESIZE_REACHED 的通知,  所以只要录像时不会有其他进程后台写文件的状况, 是肯定不会出现录像文件写失败的;  这套处理逻辑早已成熟, 直可追溯到上上届常委时期;  而且现在 google 还为了磁盘录满, 增加了 splitting 的处理, 无懈可击 ... // MAGIC1. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/ 说了那么多背景,那测试反映的到底是什么情况? 

自己试了下, 将内置卡塞到接近塞满,  此时开始录像, 录出的文件确实播不了, 体现在这 mp4 文件的 moov header 在偏移量第 0x1000 后,  全部是零数据,  仿佛写不进去:

看到 0x1000 这么整整齐齐, 很可能是文件系统的锅啊, 于是在 MPEG4Writer 代码里调用 write 的地方加了 log :// MAGIC2. DO NOT TOUCH.  BY 冗戈微言 http://blog.csdn.net/leonxu_sjtu/

size_t MPEG4Writer::write(        const void *ptr, size_t size, size_t nmemb) {...    } else {        ALOGW("mWriteMoovBoxToMemory false - write size=%lld, nmemb=%lld", (long long)size,(long long)nmemb);        ssize_t rt;        rt = ::write(mFd, ptr, size * nmemb);        ALOGW("mWriteMoovBoxToMemory write rt = %lld, errno = %d-%s", (long long)rt, errno, strerror(errno));        mOffset += bytes;    }    return bytes;} 对应的 log : 01-11 06:18:22.362  1709  2285 I MPEG4Writer: limits: 200621952/0 bytes/us, bit rate: 14096000 bps and the estimated moov size 405123 bytes01-11 06:20:11.390  1709  2285 I MPEG4Writer: mWriteMoovBoxToMemory!!!  mMoovBoxBufferOffset = 31557, mFreeBoxOffset = 2401-11 06:20:11.390  1709  2285 W MPEG4Writer: mWriteMoovBoxToMemory false - write size=1, nmemb=3155701-11 06:20:11.391  1709  2285 W MPEG4Writer: mWriteMoovBoxToMemory write rt = 4072, errno = 25-Not a typewriter01-11 06:20:11.391  1709  2285 W MPEG4Writer: mWriteMoovBoxToMemory false - write size=1, nmemb=401-11 06:20:11.391  1709  2285 W MPEG4Writer: mWriteMoovBoxToMemory write rt = -1, errno = 122-Quota exceeded01-11 06:20:11.391  1709  2285 W MPEG4Writer: mWriteMoovBoxToMemory false - write size=1, nmemb=401-11 06:20:11.391  1709  2285 W MPEG4Writer: mWriteMoovBoxToMemory write rt = -1, errno = 122-Quota exceeded01-11 06:20:11.391  1709  2285 D MPEG4Writer: Video track stopping. Stop source01-11 06:20:11.391  1709  2285 D MPEG4Writer: Audio track stopping. Stop source ------ 这里表示, MPEG4Writer 想在文件偏移量 24的地方,调用 ::write  写入 31557 字节数据,  但是返回值显示值只写入了  4072字节,  也就是写到偏移量  24+4072 = 0x1000 处就写不下去了;

但此时用 df 命令查看,  /sdcard/ 下只到 97%, 还有 180M 的空间 !// MAGIC3. DO NOT TOUCH.  BY 冗戈微言 http://blog.csdn.net/leonxu_sjtu/

而且我在录像中一直在  ls –l /sdcard/DCIM/Camera   观测 .mp4.tmp  临时文件的大小, 它确实增长到 80M  就不再增长了,  即使此时界面上仍然在持续录像  (这是因为 MPEG4Writer  只会在录像数据增长到  Camera  APK  按剩余空间设定的  limit 值的 95% 之后才会主动停止录像 ) ;  对录像文件来说,  系统剩余的可用空间只有 80M ,  不是 Camera APK 设下来的上限 200M  ( df 命令体现的 80+180=260M 是 free 部分,  减掉 reserved 部分后就是 available 部分 -- 200M ),   写数据只要超过 80M 录像文件就坏了; // MAGIC4. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/

注意, 此时 errno 显示为 Quota exceeded …    之前不了解这个 quota, 出这个异常后搜了搜, 越搜越觉得是个大坑!   然后查看手机上的 /vendor/etc/fstab.xxx 参数,  果然 /data/ 分区带了个 quota 选项,  这个选项在 Android 7.0 可没有! /dev/block/bootdevice/by-name/userdata       /data        ext4    nosuid,noatime,nodev,barrier=1,noauto_da_alloc,discard      wait,check,encryptable=footer,quota 删掉这个选项重启, 录像的剩余空间问题立刻恢复正常了!   根在这里没错了~ // MAGIC5. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/ 好了,我们知道了 quota 被使能所以磁盘空间会有使用额度,  那具体这个额度是哪里配的? 搜代码, 在 android 工程下搜, 终于看到了 installd 的 prepare_app_quota 这里配置的 90% 额度: +/**+ * Ensure that we have a hard-limit quota to protect against abusive apps;+ * they should never use more than 90% of blocks or 50% of inodes.+ */+static int prepare_app_quota(const std::unique_ptr<std::string>& uuid, const std::string& device,+        uid_t uid) {+    if (device.empty()) return 0;++    struct dqblk dq;+    if (quotactl(QCMD(Q_GETQUOTA, USRQUOTA), device.c_str(), uid,+            reinterpret_cast<char*>(&dq)) != 0) {+        PLOG(WARNING) << "Failed to find quota for " << uid;+        return -1;+    }++    if ((dq.dqb_bhardlimit == 0) || (dq.dqb_ihardlimit == 0)) {+        auto path = create_data_path(uuid ? uuid->c_str() : nullptr);+        struct statvfs stat;+        if (statvfs(path.c_str(), &stat) != 0) {+            PLOG(WARNING) << "Failed to statvfs " << path;+            return -1;+        }++        dq.dqb_valid = QIF_LIMITS;+        dq.dqb_bhardlimit = (((stat.f_blocks * stat.f_frsize) / 10) * 9) / QIF_DQBLKSIZE;+        dq.dqb_ihardlimit = (stat.f_files / 2);+        if (quotactl(QCMD(Q_SETQUOTA, USRQUOTA), device.c_str(), uid,+                reinterpret_cast<char*>(&dq)) != 0) { 以及对 /data/media 强行设置了 AID_MEDIA_RW 的 quota :  // 这里有点坑爹, 因为我开始一直觉得录像文件或者录像临时文件的 fd 是被 mediaserver 进程持有, 所以我应该去查 mediaserver 进程的 UID -  AID_MEDIA 的 quota 配额, 结果 QCMD(Q_GETQUOTA, USRQUOTA) 一查什么限制都没有,  直到后来加了 log 才知道对应的 UID 其实是 AID_MEDIA_RW ...  // MAGIC6. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/ +    // Data under /data/media doesn't have an app, but we still want+    // to limit it to prevent abuse.+    if (prepare_app_quota(uuid, findQuotaDeviceForUuid(uuid),+            multiuser_get_uid(userId, AID_MEDIA_RW))) {+        return error("Failed to set hard quota for media_rw");+    }+ 以上的提交都是 2017年 Android 8.0 的新货: commit e59c85cc0e78bfcc8fec6acc8e37e6a472ffc07f Author: Jeff Sharkey <jsharkey@android.com> Date:   Sun Apr 2 21:53:14 2017 -0600     Define upper-bound disk quotas for all apps.     Abusive or broken apps can go crazy and try allocating all of the     disk space on the device.  To mitigate the impact on system health,     set hard limits to block any given app from using more than 90% of     disk blocks, or 50% of disk inodes.     Also define the hard limit for AID_MEDIA_RW to avoid filling up the     device via the SD card. 好了, 基本理清 ...    // MAGIC7. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/ 所以什么时候录像会出 quota exceeded 的异常?    ---- 当 /data/ 分区空间还没到用满时,但 AID_MEDIA_RW 的使用额度已达到总尺寸的 90% ; 所以什么时候不会出现 quota exceeded 的异常?    ---- 当 /data/ 分区空间接近用满时,  AID_MEDIA_RW 的使用额度仍然没有达到总尺寸的 90% ;  具体来说, 就是 /data/ 分区上,  除了挂载 /sdcard/ 分区的 /data/media  之外,  其他 UID 的容量占用已满 10% , 比如我事先拷贝一个 300M 的文件到 /data/ 目录下;  如果其他 UID 的占用没有满 10%, 那 quota exceeded 就会对你的录像文件招手了; // MAGIC8. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/

这应该也是 google 使能 quota 选项的目的所在, 就是为了限制 /sdcard/ 分区的使用, 以免 /data/ 分区下 APP 的部分受到影响; 只是这种使用的限制, android framework 层面看起来并没有准备好嘛, 否则应该在 storage/statFs 这边加上支持, 不然按现有的 available space 的查询接口, 避免不了 quota exceeded 的报错, 而且这一错就厉害了, 当前可能录了很久的录像文件或者声音文件就报废了, 而且用户会一直不理解为什么明明文件管理器看到还有很多内置空间, 但就是录音录像拍照等等什么都用不了; // MAGIC9. DO NOT TOUCH.  BY 冗戈微言 http://blog.csdn.net/leonxu_sjtu/

拭目以待吧, 说不定 Android P 出来就修复了呢?   

转载请注明原文地址: https://www.6miu.com/read-2972627.html

最新回复(0)