Почему AVFoundation не примет мои планарные пиксельные буферы на устройстве iOS?

Я изо всех сил пытался выяснить, в чем проблема с моим кодом. Я создаю планар CVPixelBufferRef написать в AVAssetWriter, Этот пиксельный буфер создается вручную через какой-то другой процесс (то есть я не получаю эти образцы с камеры или что-то в этом роде). В симуляторе iOS нет проблем с добавлением образцов и созданием корректного выходного фильма.

Но на устройстве оно сразу дает сбой в первом примере и предоставляет меньше, чем бесполезную информацию об ошибках:

AVAssetWriterError: Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSUnderlyingError=0x12fd2c670 {Error Domain=NSOSStatusErrorDomain Code=-12780 "(null)"}, NSLocalizedFailureReason=An unknown error occurred (-12780), NSLocalizedDescription=The operation could not be completed}

Я очень плохо знаком с пиксельными форматами, и я не удивлюсь, если я каким-то образом создаю недопустимые пиксельные буферы, но тот факт, что он прекрасно работает в Simulator (то есть в OS X), оставляет меня в замешательстве.

Вот мой код:

const int pixelBufferWidth = img->get_width();
const int pixelBufferHeight = img->get_height();

size_t planeWidths[3];
size_t planeHeights[3];
size_t planeBytesPerRow[3];
void* planeBaseAddresses[3];

for (int c=0;c<3;c++) {
  int stride;
  const uint8_t* p = de265_get_image_plane(img, c, &stride);

  int width = de265_get_image_width(img,c);
  int height = de265_get_image_height(img, c);

  planeWidths[c] = width;
  planeHeights[c] = height;
  planeBytesPerRow[c] = stride;

  planeBaseAddresses[c] = const_cast<uint8_t*>(p);
}

void* descriptor = calloc(1, sizeof(CVPlanarPixelBufferInfo_YCbCrPlanar));
CVPixelBufferRef pixelBufferRef;
CVReturn result = CVPixelBufferCreateWithPlanarBytes(NULL,
                                                     pixelBufferWidth,
                                                     pixelBufferHeight,
                                                     kCVPixelFormatType_420YpCbCr8Planar,
                                                     NULL,
                                                     0,
                                                     3,
                                                     planeBaseAddresses,
                                                     planeWidths,
                                                     planeHeights,
                                                     planeBytesPerRow,
                                                     &pixelBufferReleaseCallback,
                                                     NULL,
                                                     NULL,
                                                     &pixelBufferRef);


CMFormatDescriptionRef formatDescription = NULL;
CMVideoFormatDescriptionCreateForImageBuffer(NULL, pixelBufferRef, &formatDescription);

if (assetWriter == nil) {
  // ... create output file path in Caches directory

  assetWriter = [AVAssetWriter assetWriterWithURL:fileOutputURL fileType:AVFileTypeMPEG4 error:nil];

  NSDictionary *videoSettings = @{AVVideoCodecKey : AVVideoCodecH264,
                                  AVVideoWidthKey : @(pixelBufferWidth),
                                  AVVideoHeightKey : @(pixelBufferHeight),
                                  AVVideoCompressionPropertiesKey : @{AVVideoMaxKeyFrameIntervalKey : @1}};

  assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings sourceFormatHint:formatDescription];
  [assetWriter addInput:assetWriterInput];

  NSDictionary *pixelBufferAttributes = @{(id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8Planar),
                                          (id)kCVPixelBufferWidthKey : @(pixelBufferWidth),
                                          (id)kCVPixelBufferHeightKey : @(pixelBufferHeight)};

  pixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:assetWriterInput sourcePixelBufferAttributes:pixelBufferAttributes];

  [assetWriter startWriting];
  [assetWriter startSessionAtSourceTime:kCMTimeZero];
}

samplePresentationTime = CMTimeMake(frameIndex++, framesPerSecond);

BOOL success = [pixelBufferAdaptor appendPixelBuffer:pixelBufferRef withPresentationTime:samplePresentationTime];

success всегда NO и ошибка автора ресурса - это то, что я вставил выше.

Я также попытался создать буферы сэмплов вручную вместо использования AVAssetWriterInputPixelBufferAdaptor просто чтобы устранить это как возможную проблему, но результаты те же.

Опять же, это работает на симуляторе, поэтому я знаю, что мои пиксельные буферы содержат правильные данные.

Кроме того, я проверил, что я могу написать в место назначения файла. Я попытался создать фиктивный файл в этом месте, и это удалось.

Я хотел бы избежать преобразования моего буфера в RGB, так как мне не нужно было бы. У меня есть буферы Y'CbCr для начала, и я хочу просто закодировать их в видео H.264, которое поддерживает Y'CbCr.

Источник, который создает эти буферы, утверждает следующее:

The image is currently always 3-channel YCbCr, with 4:2:0 chroma.

Я подтвердил, что он всегда входит в свою логику петли, которая имеет дело с 8-битными каналами YUV.

Что я делаю неправильно?

1 ответ

Итак, я не могу подтвердить это официально, но похоже, что AVAssetWriter не любит 3-плоскостные форматы пикселей (то есть kCVPixelFormatType_420YpCbCr8Planar) на iOS. На OS X, кажется, работает практически со всем. Когда я конвертировал свои 3-плоскостные буферы в двухплоскостной формат пиксельного буфера, это работало на iOS. Это неудивительно, так как камера изначально снимает в kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange формат пикселей, поэтому AV Foundation, вероятно, также будет работать с этим форматом.

Тем не менее, было бы неплохо, если бы мне не пришлось делать этот явный шаг преобразования самостоятельно, хотя vImageConvert_PlanarToChunky8 помогает чередовать плоскости Cb и Cr в одну плоскость.

Другие вопросы по тегам