Search code examples
iosipadavfoundationavassetwriteravassetwriterinput

Writing video to file works correctly for about 5 seconds on iPad 3, then fails


I am creating this app that is a camera. Because it is being created for iOS 9, I have to test it on an old device. In that case an iPad 3. The app works perfectly on a new iPad Pro 9.7 but fails to write video after a while on iPad 3.

What happens is, the app starts writing frames fine but suddenly fails.

I am using this method to store each frame:

- (void)writeToFileFrame:(CIImage *) finalImage
          withSampleTime:(CMSampleTimingInfo)sampleTime
                 andSize:(CGSize)size
{

  if (!_assetWriter) {
    if (![self initializeAssetWriter]) {
      return;
    }
  }

  // convert CIImage to CMSampleBufferRef and save
  CGRect extent = [finalImage extent];
  CGFloat width = CGRectGetWidth(extent);
  CGFloat height  = CGRectGetHeight(extent);

  if ((width == 0) && (height == 0)) return;


  NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                           [NSDictionary dictionary], (id)kCVPixelBufferIOSurfacePropertiesKey,
                           @(YES), kCVPixelBufferCGImageCompatibilityKey,
                           @(YES), kCVPixelBufferCGBitmapContextCompatibilityKey,
                           kCVImageBufferYCbCrMatrix_ITU_R_601_4, kCVImageBufferYCbCrMatrixKey,
                           kCVImageBufferColorPrimaries_ITU_R_709_2, kCVImageBufferColorPrimariesKey, nil];

  // Initialize the video input if this is not done yet
  if (!_readyToWriteVideo) {
    _readyToWriteVideo = [self setupAssetWriterVideoInputWithSize:size];
  }

  CVPixelBufferRef pixelBuffer = NULL;

  CVPixelBufferCreate(kCFAllocatorSystemDefault, width, height, kCVPixelFormatType_32BGRA, (__bridge CFDictionaryRef) options, &pixelBuffer);

  CVPixelBufferLockBaseAddress( pixelBuffer, 0 );
  [_ciContext render:finalImage toCVPixelBuffer:pixelBuffer];
  CVPixelBufferUnlockBaseAddress( pixelBuffer, 0 );

  CMVideoFormatDescriptionRef videoInfo = NULL;
  CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, &videoInfo);

  CMSampleBufferRef oBuf;
  OSStatus status = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, true, NULL, NULL, videoInfo, &sampleTime, &oBuf);
  CVPixelBufferRelease(pixelBuffer);
  CFRelease(videoInfo);

  if (status != noErr) {
    NSLog(@"error creating CMSampleBufferCreateForImageBuffer");
    CFRelease(oBuf);
    return;
  }

  // Write video data to file only when all the inputs are ready
  if ([self inputsReadyToWriteToFile]) {
    if (_assetWriter.error) {
      NSLog(@"%@",[_assetWriter.error localizedDescription]);
      return;
    }
    [self writeSampleBuffer:oBuf ofType:AVMediaTypeVideo];
  }

  CFRelease(oBuf);

}


- (void)writeSampleBuffer:(CMSampleBufferRef)sampleBuffer ofType:(NSString *)mediaType
{  
  if ( _assetWriter.status == AVAssetWriterStatusUnknown ) {
    NSLog(@"unknown state");
    // If the asset writer status is unknown, implies writing hasn't started yet, hence start writing with start time as the buffer's presentation timestamp
    if ([_assetWriter startWriting]) {
      [_assetWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
    } else {
          // error
     }
  }

  if ( _assetWriter.status == AVAssetWriterStatusWriting ) {
    // If the asset writer status is writing, append sample buffer to its corresponding asset writer input
    if (mediaType == AVMediaTypeVideo) {
      if (_assetWriterVideoInput.readyForMoreMediaData) {
        if (![_assetWriterVideoInput appendSampleBuffer:sampleBuffer]) {
          NSLog(@"error: %@", [_assetWriter.error localizedFailureReason]); //Ⓐ
        }
      }
    }
    else if (mediaType == AVMediaTypeAudio) {
      if (_assetWriterAudioInput.readyForMoreMediaData) {
        if (![_assetWriterAudioInput appendSampleBuffer:sampleBuffer]) {
                      // error
        }
      }
    }
  }

  if ( _assetWriter.status == AVAssetWriterStatusFailed ) {
    NSLog(@"error"); 
  }

}

This works fine on iPad Pro 9.7 but on iPad 3 it hits line Ⓐ, failing miserably, after writing frames correctly for about 5 seconds.

Fails with error -536870211, that is obviously an unknown error.

I have checked the apps for leaks and there is none.

Any ideas?

NOTE: I have discovered now that the problems only shows if I am writing frames from the rear camera (HD). If I switch to the front camera VGA (640x480) it works fine. So it has to do with memory allocations but I have checked that with instruments and the app is using about 12 MB of memory and this value is more or less constant. No leaks.


Solution

  • That error is kIOReturnNoMemory

    Memory can't be allocated (0xe00002bd).Value: 0xe00002bd (-536870211)
    

    so it sounds like you're using too much memory. You need to make sure that you're retaining as few sample buffers and their derivatives for as short a period of time as possible.

    The Instruments app memory and GPU tools and Xcode's memory gauges should be helpful too.