【问题标题】:Play Motion JPG stream in iOS out of memory在 iOS 中播放 Motion JPG 流内存不足
【发布时间】:2014-03-28 15:42:57
【问题描述】:

我正在尝试在 iOS 中播放来自 IP 摄像头的视频,但目前我尝试了 2 种方法,它们似乎都非常快地填满了我的 iOS 设备的内存。我在这个项目中使用 ARC。

我的 IP 摄像机使用 Videostream.cgi (Foscam),这是一种众所周知的 IP 摄像机通过浏览器流式传输“视频”的方式。

所以,我尝试了 3 种方法,最终都导致我的 iOS 应用程序崩溃,并出现内存不足异常。

1.UIWebView 放在我的UIViewController 上,然后使用NSURLRequest 直接调用CGI。

NSString* url = [NSString stringWithFormat:@"http://%@:%@/videostream.cgi?user=%@&pwd=%@&rate=0&resolution=%ld", camera.ip, camera.port, camera.username, camera.password, (long)_resolution];
NSURLRequest* request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
webView = [[UIWebView alloc] init];
[webView loadRequest:request];

2. 在我的UIViewController 上放置一个UIWebView 并创建一段HTML(在代码中),其中包含一个<img> 标记,该标记具有前面提到的CGI 的来源。 (见:IP camera stream with UIWebview works on IOS 5 but not on IOS 6

NSString* imgHtml = [NSString stringWithFormat:@"<img src='%@'>", url];
webView = [[UIWebView alloc] init];
[webView loadHTMLString:imgHtml];

3. 使用基于UIImageView 的自定义控件,该控件连续获取数据。 https://github.com/mateagar/Motion-JPEG-Image-View-for-iOS

所有这些东西都会烧毁内存,即使我尝试删除它们并在一段时间后重新添加它们,但这似乎并不能解决问题。内存不会被释放,iPad会崩溃。

更新:

我目前正在修改我尝试过的解决方案的选项 3。它基于NSURLConnection 及其检索的数据。

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {

    if (!_receivedData) {
        _receivedData = [NSMutableData new];
    }

    [_receivedData appendData:data];

    NSRange endRange = [_receivedData rangeOfData:_endMarkerData 
                                          options:0 
                                            range:NSMakeRange(0, _receivedData.length)];

    NSUInteger endLocation = endRange.location + endRange.length;
    if (_receivedData.length >= endLocation) {

        NSData *imageData = [_receivedData subdataWithRange:NSMakeRange(0, endLocation)];
        UIImage *receivedImage = [UIImage imageWithData:imageData];
        if (receivedImage) {
            NSLog(@"_receivedData length: %d", [_receivedData length]);
            self.image = receivedImage;
            _receivedData = nil;
            _receivedData = [NSMutableData new];
        }
    }

    if (_shouldStop) {
        [connection cancel];
    }
}

_receivedData 是一个 NSMutableData 对象。一旦从流中检索到图像,我就会尝试“清空”它。 if (receivedImage) 中的部分在应该被调用时被调用。 _receivedData 对象的长度也没有增加,它保持在相同的大小(~ 14k)左右,所以这似乎有效。

但不知何故,每didReceiveData 一次,我的应用程序使用的内存就会增加,即使我禁用self.image = receivedImage 行也是如此。

更新 正如 iosengineer 所建议的,我一直在使用自动释放池,但这并不能解决问题。

使用 Instruments 我发现大部分分配都是由CFNetwork 完成的,方法是HTTPBodyData::appendBytes(unsigned char const*, long)。 (这一次分配 64KB 并让它们保持活动状态)。

【问题讨论】:

    标签: ios iphone objective-c video-streaming


    【解决方案1】:

    下一步我将使用 Charlie 分析请求/响应模式,使用 Xcode 逐步检查源代码,并可能使用 NSURLSession 和 NSURLRequest 编写我自己的解决方案。

    流不只是创建自己 - 某些东西正在从响应中提取数据并且没有足够快地摆脱它。

    这是我对可能发生的事情的猜测:

    当您使用 NSURLRequest 下载内容时,您会创建一个 NSMutableData 实例来以块的形式收集响应,直到您准备好将其保存到磁盘。在这种情况下,流永远不会结束,因此商店会变得庞大,然后保释。

    对此的自定义解决方案必须知道何时可以安全地根据帧的结尾放弃存储(例如)。祝你好运! Instruments 是你的朋友。

    附:小心自动释放的内存 - 明智地使用自动释放池


    在您修改后的问题中,代码示例显示了一些使用自动释放内存创建的对象。适当使用自动释放池应该可以解决这个问题。查看哪个对象导致的问题最多,以及您的问题是否已通过使用 Instruments(分配工具)分析应用程序来解决,应该相当简单。

    特别有趣的是,UIImage imageWithData: 调用绝对应该被包装,因为它每次都会创建一个新的图像对象。

    另外subdataWithRange 创建一个新对象,该对象仅在池被刷新后才被释放。

    我从不使用“新”语法进行创建,所以我不记得它是如何工作的。我总是使用 alloc init。

    用这个包裹整个例程的大部分内容:

    @autoreleasepool
    {
      ROUTINE
    }
    

    这样一来,在接收到的每一块数据时,池都会被耗尽,并且您将清除所有自动释放的内存对象。

    【讨论】:

      【解决方案2】:

      我重写了 MotionJpegImageView 的东西,这导致了我所有的问题:

      - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
          if (!_receivedData) {
              _receivedData = [NSMutableData new];
          }
      
          [_receivedData appendData:data];
      
          NSRange endRange = [_receivedData rangeOfData:_endMarkerData
                                                options:0
                                                  range:NSMakeRange(0, _receivedData.length)];
      
          if (endRange.location == NSNotFound) {
              return;
          }
      
          @autoreleasepool {
              UIImage *receivedImage = [UIImage imageWithData:_receivedData];
              if (receivedImage) {
                  self.image = receivedImage;
              }
              else {
                  DDLogVerbose(@"Invalid image data");
              }
          }
      
          [_receivedData setLength:0];
      
          if (_shouldStop) {
              [connection cancel];
              DDLogVerbose(@"Should stop connection");
          }
      }
      

      此外,由于未正确取消旧连接,我的连接最终被打开了多次。非常愚蠢的错误,但对于想知道它是如何工作的人来说。上面提到了代码。

      【讨论】:

        猜你喜欢
        • 2015-07-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-09-06
        • 2015-12-02
        • 2014-04-09
        • 2013-06-13
        相关资源
        最近更新 更多