- copyPixelBufferForItemTime:itemTimeForDisplay: нулевое значение

Проблема, с которой я сталкиваюсь, заключается в том, что когда я компилирую свое приложение с iOS9 sdk, когда мое приложение пытается получить CVPixelBufferRef из AVPlayerItemVideoOutput с помощью функции - copyPixelBufferForItemTime:itemTimeForDisplay:, я время от времени получаю нулевое значение, когда видео загружается и все экземпляры создаются.

с iOS 8 мое приложение работало нормально, но с iOS9 у меня возникла проблема, даже версия моего приложения, доступная для загрузки в магазине приложений, которая была скомпилирована с iOS 8 SDK, дает мне ту же проблему при установке в iOS9.

Когда возникает проблема, и я получаю нулевое значение getCVPixelBufferRef, если я нажимаю кнопку «Домой», и приложение переходит в фоновый режим, когда я снова открываю приложение и становится активным, экземпляр AVPlayerItemVideoOutput, который давал мне нулевое значение CVPixelBufferRef, начинает работать нормально, и проблема в том, решено.

Вот видео на YouTube, где я повторяю проблему:

https://www.youtube.com/watch?v=997zG08_DMM&feature=youtu.be

Вот пример кода для создания экземпляров всех элементов:

NSURL *url ;
url = [[NSURL alloc] initFileURLWithPath:[_mainVideo objectForKey:@"file"]];

NSDictionary *pixBuffAttributes = @{(id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)};
_videoOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:pixBuffAttributes];
_myVideoOutputQueue = dispatch_queue_create("myVideoOutputQueue", DISPATCH_QUEUE_SERIAL);
[_videoOutput setDelegate:self queue:_myVideoOutputQueue];

_player = [[AVPlayer alloc] init];


// Do not take mute button into account
NSError *error = nil;
BOOL success = [[AVAudioSession sharedInstance]
                setCategory:AVAudioSessionCategoryPlayback
                error:&error];
if (!success) {
   // NSLog(@"Could not use AVAudioSessionCategoryPlayback", nil);
}

asset = [AVURLAsset URLAssetWithURL:url options:nil];


if(![[NSFileManager defaultManager] fileExistsAtPath:[[asset URL] path]]) {
   // NSLog(@"file does not exist");
}

NSArray *requestedKeys = [NSArray arrayWithObjects:kTracksKey, kPlayableKey, nil];

[asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:^{

    dispatch_async( dispatch_get_main_queue(),
                   ^{
                       /* Make sure that the value of each key has loaded successfully. */
                       for (NSString *thisKey in requestedKeys)
                       {
                           NSError *error = nil;
                           AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
                           if (keyStatus == AVKeyValueStatusFailed)
                           {
                               [self assetFailedToPrepareForPlayback:error];
                               return;
                           }
                       }

                       NSError* error = nil;
                       AVKeyValueStatus status = [asset statusOfValueForKey:kTracksKey error:&error];
                       if (status == AVKeyValueStatusLoaded)
                       {
                           //_playerItem = [AVPlayerItem playerItemWithAsset:asset];


                           [_playerItem addOutput:_videoOutput];
                           [_player replaceCurrentItemWithPlayerItem:_playerItem];
                           [_videoOutput requestNotificationOfMediaDataChangeWithAdvanceInterval:ONE_FRAME_DURATION];

                           /* When the player item has played to its end time we'll toggle
                            the movie controller Pause button to be the Play button */
                           [[NSNotificationCenter defaultCenter] addObserver:self
                                                                    selector:@selector(playerItemDidReachEnd:)
                                                                        name:AVPlayerItemDidPlayToEndTimeNotification
                                                                      object:_playerItem];

                           seekToZeroBeforePlay = NO;

                           [_playerItem addObserver:self
                                         forKeyPath:kStatusKey
                                            options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
                                            context:AVPlayerDemoPlaybackViewControllerStatusObservationContext];

                           [_player addObserver:self
                                     forKeyPath:kCurrentItemKey
                                        options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
                                        context:AVPlayerDemoPlaybackViewControllerCurrentItemObservationContext];

                           [_player addObserver:self
                                     forKeyPath:kRateKey
                                        options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
                                        context:AVPlayerDemoPlaybackViewControllerRateObservationContext];


                           [self initScrubberTimer];

                           [self syncScrubber];


                       }
                       else
                       {
                         //  NSLog(@"%@ Failed to load the tracks.", self);
                       }
                   });
}];

Вот пример кода, дающий мне нулевой буфер пикселей

CVPixelBufferRef pixelBuffer =
[_videoOutput
 copyPixelBufferForItemTime:[_playerItem currentTime]
itemTimeForDisplay:nil];

NSLog(@"the pixel buffer is %@", pixelBuffer);
NSLog (@"the _videoOutput is %@", _videoOutput.description);
CMTime dataTime = [_playerItem currentTime];
//NSLog(@"the current time is %f", dataTime);
return pixelBuffer;

person user1241006    schedule 09.10.2015    source источник
comment
Вы уже нашли решение для этого? Я создал плагин Unity для OSX/iOS, используя аналогичный код, и у меня возникла та же проблема. У меня есть хак, который работает для меня, но я бы не назвал его решением, поэтому я не назвал его ответом, и я все еще ищу более элегантный подход. *** Я обнаружил, что распределение alloc AVPlayerItemVideoOutput зависит от параметра формата, который вы ему передаете, но время, затрачиваемое на это, не является абсолютным. Принудительное время ожидания в одну секунду между распределением и загрузкой/воспроизведением исправило это для меня. Также я создаю только 1 AVPlayerItemVideoOutput и повторно использую его, поэтому мне нужна только 1 задержка.   -  person Brian Hodge    schedule 12.11.2015
comment
Я также хотел отметить, что мне трудно поверить в ответ TimoRozendal, потому что в моем случае это не проблема, специфичная для iOS, а скорее проблема AVFoundation, как это также происходит в OSX.   -  person Brian Hodge    schedule 12.11.2015
comment
привет @BrianHodge большое спасибо за взлом, это действительно сработало, так как два месяца назад я заполнил отчет об ошибке с Apple, но они не дают мне никаких отзывов, также я запросил билет в службу поддержки с моей учетной записью разработчика, и парень из службы поддержки Apple сказал мне, что они все еще пытаются выяснить, что происходит, поэтому он не сможет дать мне решение или, по крайней мере, обходной путь.   -  person user1241006    schedule 13.11.2015
comment
Для меня это всегда терпит неудачу с видео quicktime и отлично работает с mp4. Это может быть связано с моими настройками videoOutput   -  person Guig    schedule 30.09.2016


Ответы (4)


У меня была такая же проблема, и я нашел ответ в этой теме: https://forums.developer.apple.com/thread/27589#128476

Вы должны дождаться, пока видео будет готово к воспроизведению, прежде чем добавлять вывод, иначе оно завершится ошибкой и вернет nil. Мой быстрый код выглядит так:

func retrievePixelBufferToDraw() -> CVPixelBuffer? {
  guard let videoItem = player.currentItem else { return nil }
  if videoOutput == nil || self.videoItem !== videoItem {
    videoItem.outputs.flatMap({ return $0 as? AVPlayerItemVideoOutput }).forEach {
      videoItem.remove($0)
    }
    if videoItem.status != AVPlayerItemStatus.readyToPlay {
      // see https://forums.developer.apple.com/thread/27589#128476
      return nil
    }

    let pixelBuffAttributes = [
      kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
      ] as [String: Any]

    let videoOutput = AVPlayerItemVideoOutput.init(pixelBufferAttributes: pixelBuffAttributes)
    videoItem.add(videoOutput)
    self.videoOutput = videoOutput
    self.videoItem = videoItem
  }
  guard let videoOutput = videoOutput else { return nil }

  let time = videoItem.currentTime()
  if !videoOutput.hasNewPixelBuffer(forItemTime: time) { return nil }
  return videoOutput.copyPixelBuffer(forItemTime: time, itemTimeForDisplay: nil)
}
person Guig    schedule 17.02.2017
comment
Абсолютно правильный ответ! - person Volodymyr Bereziuk; 29.11.2020

Хотя это не является реальным решением проблемы, которая, согласно вашему комментарию, кажется ошибкой в ​​​​AVFoundation, я нашел лучший обходной путь, чем ждать 1 секунду, как предложил Брайан Ходж, — это воссоздать AVPlayer, если он не работает. пиксельные буферы. В зависимости от того, что делает ваша реальная процедура перезапуска, это может оказаться значительно быстрее и менее раздражающим для пользователя. Кроме того, он вводит (небольшую) задержку только в том случае, если у AVPlayer действительно возникла проблема, а не каждый раз, когда вы его запускаете.

Однако AVPlayerItemVideoOutput больше не будет доставлять пиксельные буферы после того, как проигрыватель завершит воспроизведение. Поэтому вам, вероятно, следует защищаться от этого случая, помня, получали ли вы уже какие-либо пиксельные буферы. В противном случае ваш проигрыватель будет выполнять непроизвольное циклическое воспроизведение.

В интерфейсе класса:

@property (nonatomic) BOOL videoOutputHadPixelBuffer;

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

if (![self.videoOutput hasNewPixelBufferForItemTime:self.player.currentTime] && !self.videoOutputHadPixelBuffer)
{
    [self restartPlayer]; // call your custom restart routine where you create a new AVPlayer object
}

self.videoOutputHadPixelBuffer = YES; // guard against missing pixel buffers after playback finished
person bfx    schedule 27.11.2015

У меня была аналогичная проблема сегодня, и я обнаружил, что это происходит только с: 64-битными устройствами под управлением ios 9.0 или выше, когда проект не создан для архитектуры arm64.

Изменение настроек сборки для сборки для архитектуры arm64 решило эту проблему для меня.

person TimoRozendal    schedule 27.10.2015
comment
привет, здесь вы можете найти настройки сборки, которые у меня есть в моем приложении, и arm64 включен с самого начала. [ссылка] (dropbox.com/s/cnmaqv0i9uycbqo/) - person user1241006; 28.10.2015

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

Ответ: я все еще ищу более элегантный подход. Я обнаружил, что распределение alloc AVPlayerItemVideoOutput зависит от параметра формата, который вы ему передаете, но время, которое требуется, не является абсолютным. Принудительное время ожидания в одну секунду между распределением и загрузкой/воспроизведением исправило это для меня. Также я создаю только 1 AVPlayerItemVideoOutput и повторно использую его, поэтому мне нужна только 1 задержка.

Также:

Используйте hasNewPixelBufferForItemTime ниже, это небольшой образец из созданного мной плагина Unity, который просто загружает содержимое пиксельного буфера в текстуру.

//////////////////////
 if (g_TexturePointer)
{
    if([plug.playerOutput hasNewPixelBufferForItemTime:[plug.player currentTime]])
    {
        pbuffer = [plug.playerOutput copyPixelBufferForItemTime:plug.player.currentItem.currentTime itemTimeForDisplay:nil];
    } ... .. . (No need to show the rest.)

Удачного кодирования!

person Brian Hodge    schedule 13.11.2015