我的AVPlayer的内存在哪里,我该如何取回?

时间:2013-11-18 11:11:48

标签: ios video avplayer didreceivememorywarning

我正与AVPlayer同时播放大量视频。为了减少加载时间,我将相应的视图存储在NSCache

此功能正常,直到达到一定数量的视频,视频才会停止播放,甚至出现。

没有错误,日志或内存警告。特别是,我正在听UIApplicationDidReceiveMemoryWarningNotification清除缓存,但是从未收到过。

如果删除缓存,所有视频都会以较差的性能为代价。

这让我怀疑AVPlayer正在使用来自不同进程的内存(哪一个?)。当记忆达到一定限度时,新玩家就会停止工作。

这是对的吗?

如果是这样,是否有办法在达到此魔法限制时通知采取适当的措施(例如,清除缓存)以确保播放其他媒体?

2 个答案:

答案 0 :(得分:14)

好消息和坏消息 - 好的是你可以解决问题,不好是需要工作而且有点复杂。

根问题

之前没有得到通知的原因是因为iOS没有发现你的应用程序已经超出了内存预算,直到它已经太晚了,然后它会立即杀死它。问题与iOS(和OS X)管理文件系统缓存的方式有关。通常,当文件打开时,当您读取数据时,文件数据会被转移到Unified Buffer Cache中的缓冲区(您可以谷歌获取更多信息的术语) - 从现在开始我将其称为UBC。 / p>

假设你有10个打开的文件,并且你已经读完了每个文件,但还没有关闭文件。好吧,所有这些数据都在UBC中。现在,如果关闭文件,缓冲区将全部释放。从技术上讲,操作系统也可以清除这些缓冲区 - 只有当它意识到内存紧张时,它才会选择先将应用程序吹走(并且可能有正当理由这样做)。因此,假设您的应用程序正在显示视频,并且视频加载的方式是通过文件系统,空闲缓冲区的数量开始下降。在某些时候iOS会注意到这一点,追踪最属于你的应用程序(你的应用程序),并且尽快杀死你的应用程序。

我在一个支持的开源项目{@ 3}}中自己解决了这个问题。用户开始抱怨他们的项目被系统终止,就像你一样,没有任何通知。我徒劳地试图监控UBC(OS X上有API这样做,但不是在iOS上)。最后,我找到了一个使用启发式的解决方案 - 监控所有内存使用情况,包括UBC,并且不超过总可用iOS内存池的50%。

所以(你可能会问) - Apple批准的解决这个问题的方法是什么?嗯,没有。我怎么知道的?因为我在2012年WWDC上与其中一个实验室的核心iOS总监进行了半小时的讨论(在其他人不知道我在谈论什最后,在我解释了上述启发式之后,他直接告诉我,解决方案可能与他能想到的一样好。如果没有API直接监控UBC,您只能估算其使用情况并进行相应调整。

但是你说,我正在使用NSCache - 为什么系统不占用那里的AVPlayer内存?毫无疑问,UBC - 一个AVPlayer实例本身可能只消耗几千K的内存 - 它是视频的开放文件,而不是iOS所占据的。

可能的解决方案

1)如果您可以将视频直接加载到NSData对象中,并将其保留在NSCache中,您很可能完全避免上面提到的UBC问题。 [我对AV系统知之甚少,知道你是否可以这样做。]在这种情况下,系统应该能够在需要时清除内存。

2)继续使用原始代码,但为其添加内存管理。也就是说,当您创建AVPlayer实例时,您需要以字节为单位计算视频大小,并保持所有内存的运行记录。当你接近50%的总设备可用内存时,然后开始清除旧的AVPlayers。

代码

为了完整起见,我提供了以下PhotoScrollerNetwork的相关代码。如果你想要更多的细节,你可以仔细阅读项目 - 但是它非常复杂,所以希望花一些时间(它可以动态地进行JPEG解码以获得大量图像,并在解码过程中将图块写入文件系统)。

// Data Structure
typedef struct {
    size_t freeMemory;
    size_t usedMemory;
    size_t totlMemory;
    size_t resident_size;
    size_t virtual_size;
} freeMemory;

您应用程序的早期版本:

// ubc_threshold_ratio defaults to 0.5f
// Take a big chunk of either free memory or all memory
freeMemory fm       = [self freeMemory:@"Initialize"];
float freeThresh    = (float)fm.freeMemory*ubc_threshold_ratio;
float totalThresh   = (float)fm.totlMemory*ubc_threshold_ratio;
size_t ubc_threshold = lrintf(MAX(freeThresh, totalThresh));
size_t ubc_usage = 0;

// Method on some class to monitor the memory pool
- (freeMemory)freeMemory:(NSString *)msg
{
    // http://stackoverflow.com/questions/5012886
    mach_port_t host_port;
    mach_msg_type_number_t host_size;
    vm_size_t pagesize;
    freeMemory fm = { 0, 0, 0, 0, 0 };

    host_port = mach_host_self();
    host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
    host_page_size(host_port, &pagesize);        

    vm_statistics_data_t vm_stat;

    if (host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size) != KERN_SUCCESS) {
        LOG(@"Failed to fetch vm statistics");
    } else {
        /* Stats in bytes */ 
        natural_t mem_used = (vm_stat.active_count +
                              vm_stat.inactive_count +
                              vm_stat.wire_count) * pagesize;
        natural_t mem_free = vm_stat.free_count * pagesize;
        natural_t mem_total = mem_used + mem_free;

        fm.freeMemory = (size_t)mem_free;
        fm.usedMemory = (size_t)mem_used;
        fm.totlMemory = (size_t)mem_total;

        struct task_basic_info info;
        if(dump_memory_usage(&info)) {
            fm.resident_size = (size_t)info.resident_size;
            fm.virtual_size = (size_t)info.virtual_size;
        }

#if MEMORY_DEBUGGING == 1
        LOG(@"%@:   "
            "total: %u "
            "used: %u "
            "FREE: %u "
            "  [resident=%u virtual=%u]", 
            msg, 
            (unsigned int)mem_total, 
            (unsigned int)mem_used, 
            (unsigned int)mem_free, 
            (unsigned int)fm.resident_size, 
            (unsigned int)fm.virtual_size
        );
#endif
    }
    return fm;
}

当您打开视频时,请将大小添加到ubc_usage,当您关闭时减小一个视频。当您想要打开一个新视频时,请针对ubc_threadhold测试ubc_usage,并且它超过您必须先关闭某些内容的值。

PS:您可以尝试在其他时间调用freeMemory方法,并查看,但在我的情况下,当文件打开时它几乎没有变化 - 系统似乎认为整个UBC是“免费的”,因为它如果需要(我猜),可以清除它。

答案 1 :(得分:2)

如果您将所有这些视频投放到NSCache中,则必须准备好缓存,以便在感觉消耗太多内存时丢弃这些项目。来自NSCache documentation

  

NSCache类包含各种自动删除策略   确保它不会占用太多的系统内存。该   如果需要内存,系统会自动执行这些策略   其他应用。调用时,这些策略会删除一些项目   从缓存中,最小化其内存占用。

检查您是否从缓存中获取nil,如果是,则必须重建对象。

编辑:

值得一提的是objc.io #7建议不要在NSCache中存储大型对象:

  

NSCache的驱逐方法是非确定性的而不是   记录。放入超大型物体不是一个好主意   图片可能会比你自己更快地填满你的缓存。