BOOL WriteFrameToAnnexBStream()

in FBControlCore/Utility/FBVideoStream.m [45:153]


BOOL WriteFrameToAnnexBStream(CMSampleBufferRef sampleBuffer, id<FBDataConsumer> consumer, id<FBControlCoreLogger> logger, NSError **error)
{
  if (!CMSampleBufferDataIsReady(sampleBuffer)) {
    return [[FBControlCoreError
      describeFormat:@"Sample Buffer is not ready"]
      failBool:error];
  }
  NSData *headerData = AnnexBNALUStartCodeData();
  NSMutableData *consumableData = [NSMutableData alloc];
  
  bool isKeyFrame = false;
  CFArrayRef attachments =
      CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true);
  if (CFArrayGetCount(attachments)) {
    CFDictionaryRef attachment = (CFDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
    CFBooleanRef dependsOnOthers = (CFBooleanRef)CFDictionaryGetValue(
        attachment, kCMSampleAttachmentKey_DependsOnOthers);
    isKeyFrame = (dependsOnOthers == kCFBooleanFalse);
  }

  if (isKeyFrame) {
    CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);
    size_t spsSize, spsCount;
    const uint8_t *spsParameterSet;
    OSStatus status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
      format,
      0,
      &spsParameterSet,
      &spsSize,
      &spsCount,
      0
    );
    if (status != noErr) {
      return [[FBControlCoreError
        describeFormat:@"Failed to get SPS Params %d", status]
        failBool:error];
    }
    size_t ppsSize, ppsCount;
    const uint8_t *ppsParameterSet;
    status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
      format,
      1,
      &ppsParameterSet,
      &ppsSize,
      &ppsCount,
      0
    );
    if (status != noErr) {
      return [[FBControlCoreError
        describeFormat:@"Failed to get PPS Params %d", status]
        failBool:error];
    }
    NSData *spsData = [NSData dataWithBytes:spsParameterSet length:spsSize];
    NSData *ppsData = [NSData dataWithBytes:ppsParameterSet length:ppsSize];
    [consumableData appendData:headerData];
    [consumableData appendData:spsData];
    [consumableData appendData:headerData];
    [consumableData appendData:ppsData];
  }

  // Get the underlying data buffer.
  CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
  size_t dataLength;
  char *dataPointer;
  OSStatus status = CMBlockBufferGetDataPointer(
    dataBuffer,
    0,
    NULL,
    &dataLength,
    &dataPointer
  );
  if (status != noErr) {
    return [[FBControlCoreError
      describeFormat:@"Failed to get Data Pointer %d", status]
      failBool:error];
  }

  // Enumerate the data buffer
  size_t dataOffset = 0;
  while (dataOffset < dataLength - AVCCHeaderLength) {
    // Write start code to the elementary stream
    [consumableData appendData:headerData];

    // Get our current position in the buffer
    void *currentDataPointer = dataPointer + dataOffset;

    // Get the length of the NAL Unit, this is contained in the current offset.
    // This will tell us how many bytes to write in the current NAL unit, contained in the buffer.
    uint32_t nalLength = 0;
    memcpy(&nalLength, currentDataPointer, AVCCHeaderLength);
    // Convert the length value from Big-endian to Little-endian.
    nalLength = CFSwapInt32BigToHost(nalLength);

    // Write the NAL unit without the AVCC length header to the elementary stream
    void *nalUnitPointer = currentDataPointer + AVCCHeaderLength;
    if ([consumer conformsToProtocol:@protocol(FBDataConsumerSync)]) {
      NSData *nalUnitData = [NSData dataWithBytesNoCopy:nalUnitPointer length:nalLength freeWhenDone:NO];
      [consumableData appendData:nalUnitData];
    } else {
      NSData *nalUnitData = [NSData dataWithBytes:nalUnitPointer length:nalLength];
      [consumableData appendData:nalUnitData];
    }

    // Increment the offset for the next iteration.
    dataOffset += AVCCHeaderLength + nalLength;
  }
  [consumer consumeData:consumableData];
  return YES;
}