IOSurfaces - артефакты в видео и неспособность захватить видео поверхности
Это вопрос из двух частей. У меня работает следующий код, который захватывает текущую поверхность дисплея и создает видео из поверхностей (все происходит в фоновом режиме).
for(int i=0;i<100;i++){
IOMobileFramebufferConnection connect;
kern_return_t result;
IOSurfaceRef screenSurface = NULL;
io_service_t framebufferService = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleH1CLCD"));
if(!framebufferService)
framebufferService = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleM2CLCD"));
if(!framebufferService)
framebufferService = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleCLCD"));
result = IOMobileFramebufferOpen(framebufferService, mach_task_self(), 0, &connect);
result = IOMobileFramebufferGetLayerDefaultSurface(connect, 0, &screenSurface);
uint32_t aseed;
IOSurfaceLock(screenSurface, kIOSurfaceLockReadOnly, &aseed);
uint32_t width = IOSurfaceGetWidth(screenSurface);
uint32_t height = IOSurfaceGetHeight(screenSurface);
m_width = width;
m_height = height;
CFMutableDictionaryRef dict;
int pitch = width*4, size = width*height*4;
int bPE=4;
char pixelFormat[4] = {'A','R','G','B'};
dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(dict, kIOSurfaceIsGlobal, kCFBooleanTrue);
CFDictionarySetValue(dict, kIOSurfaceBytesPerRow, CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pitch));
CFDictionarySetValue(dict, kIOSurfaceBytesPerElement, CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bPE));
CFDictionarySetValue(dict, kIOSurfaceWidth, CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &width));
CFDictionarySetValue(dict, kIOSurfaceHeight, CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &height));
CFDictionarySetValue(dict, kIOSurfacePixelFormat, CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, pixelFormat));
CFDictionarySetValue(dict, kIOSurfaceAllocSize, CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &size));
IOSurfaceRef destSurf = IOSurfaceCreate(dict);
IOSurfaceAcceleratorRef outAcc;
IOSurfaceAcceleratorCreate(NULL, 0, &outAcc);
IOSurfaceAcceleratorTransferSurface(outAcc, screenSurface, destSurf, dict, NULL);
IOSurfaceUnlock(screenSurface, kIOSurfaceLockReadOnly, &aseed);
CFRelease(outAcc);
// MOST RELEVANT PART OF CODE
CVPixelBufferCreateWithBytes(NULL, width, height, kCVPixelFormatType_32BGRA, IOSurfaceGetBaseAddress(destSurf), IOSurfaceGetBytesPerRow(destSurf), NULL, NULL, NULL, &sampleBuffer);
CMTime frameTime = CMTimeMake(frameCount, (int32_t)5);
[adaptor appendPixelBuffer:sampleBuffer withPresentationTime:frameTime];
CFRelease(sampleBuffer);
CFRelease(destSurf);
frameCount++;
}
PS: последние 4-5 строк кода являются наиболее актуальными (если вам нужно отфильтровать).
1) Созданное видео имеет артефакты. Я работал над видео ранее и раньше сталкивался с такой проблемой. Я полагаю, что для этого может быть две причины:
я. PixelBuffer, который передается адаптеру, модифицируется или освобождается до завершения обработки (кодирование + запись). Это может быть связано с асинхронными вызовами. Но я не уверен, что это сама проблема и как ее решить.
II. Переданные временные метки являются неточными (например, 2 кадра имеют одинаковую временную метку или кадр имеют меньшую временную метку, чем предыдущий кадр). Я записал значения меток времени, и это не проблема.
2) Приведенный выше код не может захватывать поверхности, когда воспроизводится видео или когда мы играем в игры. Все, что я получаю, это пустой экран в выводе. Это может быть связано с аппаратным ускорением декодирования, которое происходит в таких случаях.
Любые вклады по любой из 2 частей вопросов будут действительно полезны. Кроме того, если у вас есть хорошие ссылки для чтения на IOSurfaces в целом, пожалуйста, разместите их здесь.
1 ответ
Я немного поэкспериментировал и пришел к выводу, что поверхность экрана, с которой копируется содержимое, меняется еще до завершения передачи содержимого (вызов IOSurfaceAcceleratorTransferSurface()). Я использую блокировку (пробовал как асинхронную, так и только для чтения), но она переопределяется iOS. Я изменил код между частью блокировки / разблокировки до следующего минимального:
IOSurfaceLock(screenSurface, kIOSurfaceLockReadOnly, &aseed);
aseed1 = IOSurfaceGetSeed(screenSurface);
IOSurfaceAcceleratorTransferSurface(outAcc, screenSurface, destSurf, dict, NULL);
aseed2 = IOSurfaceGetSeed(screenSurface);
IOSurfaceUnlock(screenSurface, kIOSurfaceLockReadOnly, &aseed);
Функция GetSeed сообщает, изменилось ли содержимое поверхности. И я записал счетчик, указывающий количество кадров, для которых изменяется начальное число. Количество было ненулевым. Итак, следующий код решил проблему:
if(aseed1 != aseed2){
//Release the created surface
continue; //Do not use this surface/frame since it has artefacts
}
Это, однако, влияет на производительность, так как многие кадры / поверхности отклоняются из-за артефактов. Любые дополнения / исправления к этому будут полезны.