AVPlayer неправильно воспроизводит результат композиции видео

Мне нужна простая вещь: проигрывать видео, поворачивая и применяя CIFilter в теме.

Сначала я создаю элемент игрока:

AVPlayerItem *item = [AVPlayerItem playerItemWithURL:videoURL];

// DEBUG LOGGING
AVAssetTrack *track = [[item.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
NSLog(@"Natural size is: %@", NSStringFromCGSize(track.naturalSize));
NSLog(@"Preffered track transform is: %@", NSStringFromCGAffineTransform(track.preferredTransform));
NSLog(@"Preffered asset transform is: %@", NSStringFromCGAffineTransform(item.asset.preferredTransform));

Тогда мне нужно применить видео композицию. Первоначально я думал создать AVVideoComposition с 2 инструкциями - одна будет AVVideoCompositionLayerInstruction для вращения, а другой будет CIFilter приложение. Тем не менее, я получил исключение, говорящее "Ожидается, что композиция видео будет содержать только AVCoreImageFilterVideoCompositionInstruction", что означает, что Apple не позволяет объединить эти две инструкции. В результате я объединил оба под фильтрацией, вот код:

AVAsset *asset = playerItem.asset;
CGAffineTransform rotation = [self transformForItem:playerItem];

AVVideoComposition *composition = [AVVideoComposition videoCompositionWithAsset:asset applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest * _Nonnull request) {
    // Step 1: get the input frame image (screenshot 1)
    CIImage *sourceImage = request.sourceImage;

    // Step 2: rotate the frame
    CIFilter *transformFilter = [CIFilter filterWithName:@"CIAffineTransform"];
    [transformFilter setValue:sourceImage forKey: kCIInputImageKey];
    [transformFilter setValue: [NSValue valueWithCGAffineTransform: rotation] forKey: kCIInputTransformKey];
    sourceImage = transformFilter.outputImage;
    CGRect extent = sourceImage.extent;
    CGAffineTransform translation = CGAffineTransformMakeTranslation(-extent.origin.x, -extent.origin.y);
    [transformFilter setValue:sourceImage forKey: kCIInputImageKey];
    [transformFilter setValue: [NSValue valueWithCGAffineTransform: translation] forKey: kCIInputTransformKey];
    sourceImage = transformFilter.outputImage;

    // Step 3: apply the custom filter chosen by the user
    extent = sourceImage.extent;
    sourceImage = [sourceImage imageByClampingToExtent];
    [filter setValue:sourceImage forKey:kCIInputImageKey];
    sourceImage = filter.outputImage;
    sourceImage = [sourceImage imageByCroppingToRect:extent];

    // Step 4: finish processing the frame (screenshot 2)
    [request finishWithImage:sourceImage context:nil];
}];

playerItem.videoComposition = composition;

Снимки экрана, которые я сделал во время отладки, показывают, что изображение успешно повернуто и фильтр применен (в этом примере это был фильтр идентификации, который не изменяет изображение). Вот скриншот 1 и скриншот 2, которые были сделаны в точках, отмеченных в комментариях выше:

Как видите, вращение прошло успешно, степень результирующего кадра также была правильной.

Проблема начинается, когда я пытаюсь воспроизвести это видео в плеере. Вот что я получаю:

Похоже, что все кадры масштабируются и сдвигаются вниз. Зеленая область - это информация о пустом фрейме, когда я ограничиваю размер до бесконечного размера фрейма, вместо зеленого отображаются пиксели границы. У меня есть ощущение, что игрок по-прежнему берет некоторую информацию о старом размере перед поворотом из AVPlayerItem Вот почему в первом фрагменте кода выше я регистрировал размеры и преобразования, есть журналы:

Natural size is: {1920, 1080}
Preffered track transform is: [0, 1, -1, 0, 1080, 0]
Preffered asset transform is: [1, 0, 0, 1, 0, 0]

Плеер настроен так:

layer.videoGravity = AVLayerVideoGravityResizeAspectFill;
layer.needsDisplayOnBoundsChange = YES;

ПОЖАЛУЙСТА, ОБРАТИТЕ ВНИМАНИЕ: самое важное: это происходит только с видео, которые были записаны самим приложением с помощью камеры в альбомной ориентации iPhone[6s] и ранее сохранены в памяти устройства. Видеоролики, которые приложение записывает в портретном режиме, вполне хороши (кстати, портретные видео имеют точно такой же размер и преобразуют журнал, как пейзажные видео! Странно... может быть, iphone помещает информацию о повороте в видео и исправляет ее). Таким образом, масштабирование и смещение видео выглядят как комбинация "аспектного заполнения" и информации о старом разрешении перед поворотом. Кстати, портретные видеокадры показаны частично из-за масштабирования, чтобы заполнить область плеера, которая имеет другое соотношение сторон, но это ожидаемое поведение.

Дайте мне знать ваши мысли по этому поводу, и, если вы знаете лучший способ, как выполнить то, что мне нужно, было бы здорово узнать.

1 ответ

Решение

ОБНОВЛЕНИЕ: существует более простой способ "изменить" AVPlayerItem размеры видео во время воспроизведения - установите renderSize свойство видео композиции (может быть сделано с помощью AVMutableVideoComposition учебный класс).

МОЙ СТАРЫЙ ОТВЕТ НИЖЕ:


После долгих отладок я понял проблему и нашел решение. Мое первоначальное предположение, что AVPlayer все еще считает, что видео оригинального размера было правильным. На изображении ниже объясняется, что происходило:

Что касается решения, я не смог найти способ изменить размер видео внутри AVAsset или же AVPlayerItem, Так что я просто манипулировал видео, чтобы соответствовать размеру и масштабу, что AVPlayer ожидал, а потом при игре в плеере с правильным соотношением сторон и флажком масштабировать и заполнить область игрока - все выглядит хорошо. Вот графическое объяснение:

И здесь идет дополнительный код, который должен быть вставлен в applyingCIFiltersWithHandler блок, упомянутый в вопросе:

... after Step 3 in the question codes above

// make the frame the same aspect ratio as the original input frame
// by adding empty spaces at the top and the bottom of the extent rectangle
CGFloat newHeight = originalExtent.size.height * originalExtent.size.height / extent.size.height;
CGFloat inset = (extent.size.height - newHeight) / 2;
extent = CGRectInset(extent, 0, inset);
sourceImage = [sourceImage imageByCroppingToRect:extent];

// scale down to the original frame size
CGFloat scale = originalExtent.size.height / newHeight;
CGAffineTransform scaleTransform = CGAffineTransformMakeScale(scale, scale);
[transformFilter setValue:sourceImage forKey: kCIInputImageKey];
[transformFilter setValue: [NSValue valueWithCGAffineTransform: scaleTransform] forKey: kCIInputTransformKey];
sourceImage = transformFilter.outputImage;

// translate the frame to make it's origin start at (0, 0)
CGAffineTransform translation = CGAffineTransformMakeTranslation(0, -inset * scale);
[transformFilter setValue:sourceImage forKey: kCIInputImageKey];
[transformFilter setValue: [NSValue valueWithCGAffineTransform: translation] forKey: kCIInputTransformKey];
sourceImage = transformFilter.outputImage;
Другие вопросы по тегам