Скриншоты iOS / Техника записи приложений - PullRequest
3 голосов
/ 22 марта 2012

У меня есть довольно визуально сложное приложение, в котором есть базовый UIViewController и несколько UIViews (подклассифицированных и расширенных мной).Периодически я добавляю UIAlertViews и UIPopOverControllers.

Я работаю над решением для видеозаписи, чтобы, когда пользователи работают с приложением, оно записывало то, что происходит для последующего опроса.

У меня есть частично работающее решение, но оно очень медленное (на самом деле оно не может занимать более 1 кадра в секунду), имеет некоторые изгибы (изображения в настоящее время вращаются и перекошены, но я думаю, что я могу это исправить) и немоя идея идеального решения.

Я перешел на эту линию мышления и пошел к реализации решения, которое использует UIGetImageFromCurrentImageContext (), но которое продолжает давать мне nil изображений, даже когда вызывается из drawRect:.

Мне, однако, приходит в голову, что я не хочу непрерывно звонить drawRect: просто чтобы сделать снимок экрана!Я не хочу на самом деле инициировать какие-либо дополнительные рисунки, просто запишите то, что на экране.

Я рад опубликовать код, который я использую, но он пока не работает.Кто-нибудь знает хорошее решение сделать то, что я ищу?

Единственное решение, которое я нашел, не работает для меня полностью, поскольку кажется, что оно никогда не захватывает UIAlertViews и другие наложенные виды.

Любая помощь?

Спасибо!

удар

Ответы [ 4 ]

2 голосов
/ 03 мая 2012

Мне не удалось выполнить полноразмерное кодирование видео в реальном времени.Однако, в качестве альтернативы, рассмотрите это.

Вместо записи кадров запишите действия (с их временными метками) по мере их возникновения.Затем, когда вы хотите воспроизвести, просто повторите действия.У вас уже есть код, потому что вы выполняете его в «реальной жизни».

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

EDIT

Если вы хотите попробовать запись, вот что я сделал (заметьте, я отказался от нее ... Это был эксперимент, так что просто возьмите его в качестве примера того, как я подошел к нему ...)ничто не готово к производству).Я мог записывать живое аудио / видео с разрешением 640x360, но это разрешение было слишком низким для меня.На iPad все выглядело отлично, но ужасно, когда я переместил видео на свой Mac и посмотрел его там.

У меня были проблемы с более высоким разрешением.Я адаптировал основную часть этого кода из примера проекта RosyWriter.Вот основные процедуры для настройки средства записи активов, начала записи и добавления UIImage в видеопоток.

Удачи.

CGSize const VIDEO_SIZE = { 640, 360 };

- (void) startRecording
{
    dispatch_async(movieWritingQueue, ^{
        NSLog(@"startRecording called in state 0x%04x", state);
        if (state != STATE_IDLE) return;
        state = STATE_STARTING_RECORDING;
        NSLog(@"startRecording changed state to 0x%04x", state);

        NSError *error;
        //assetWriter = [[AVAssetWriter alloc] initWithURL:movieURL fileType:AVFileTypeQuickTimeMovie error:&error];
        assetWriter = [[AVAssetWriter alloc] initWithURL:movieURL fileType:AVFileTypeMPEG4 error:&error];
        if (error) {
            [self showError:error];
        }
        [self removeFile:movieURL];
        [self resumeCaptureSession];
        [self.delegate recordingWillStart];
    }); 
}


// TODO: this is where we write an image into the movie stream...
- (void) writeImage:(UIImage*)inImage
{
    static CFTimeInterval const minInterval = 1.0 / 10.0;

    static CFAbsoluteTime lastFrameWrittenWallClockTime;
    CFAbsoluteTime thisFrameWallClockTime = CFAbsoluteTimeGetCurrent();
    CFTimeInterval timeBetweenFrames = thisFrameWallClockTime - lastFrameWrittenWallClockTime;
    if (timeBetweenFrames < minInterval) return;

    // Not really accurate, but we just want to limit the rate we try to write frames...
    lastFrameWrittenWallClockTime = thisFrameWallClockTime;

    dispatch_async(movieWritingQueue, ^{
        if ( !assetWriter ) return;

        if ((state & STATE_STARTING_RECORDING) && !(state & STATE_MASK_VIDEO_READY)) {
            if ([self setupAssetWriterImageInput:inImage]) {
                [self videoIsReady];
            }
        }
        if (state != STATE_RECORDING) return;
        if (assetWriter.status != AVAssetWriterStatusWriting) return;

        CGImageRef cgImage = CGImageCreateCopy([inImage CGImage]);
        if (assetWriterVideoIn.readyForMoreMediaData) {
            CVPixelBufferRef pixelBuffer = NULL;

            // Resize the original image...
            if (!CGSizeEqualToSize(inImage.size, VIDEO_SIZE)) {
                // Build a context that's the same dimensions as the new size
                CGRect newRect = CGRectIntegral(CGRectMake(0, 0, VIDEO_SIZE.width, VIDEO_SIZE.height));
                CGContextRef bitmap = CGBitmapContextCreate(NULL,
                                                            newRect.size.width,
                                                            newRect.size.height,
                                                            CGImageGetBitsPerComponent(cgImage),
                                                            0,
                                                            CGImageGetColorSpace(cgImage),
                                                            CGImageGetBitmapInfo(cgImage));

                // Rotate and/or flip the image if required by its orientation
                //CGContextConcatCTM(bitmap, transform);

                // Set the quality level to use when rescaling
                CGContextSetInterpolationQuality(bitmap, kCGInterpolationHigh);

                // Draw into the context; this scales the image
                //CGContextDrawImage(bitmap, transpose ? transposedRect : newRect, imageRef);
                CGContextDrawImage(bitmap, newRect, cgImage);

                // Get the resized image from the context and a UIImage
                CGImageRef newImageRef = CGBitmapContextCreateImage(bitmap);
                CGContextRelease(bitmap);
                CGImageRelease(cgImage);
                cgImage = newImageRef;
            }

            CFDataRef image = CGDataProviderCopyData(CGImageGetDataProvider(cgImage));

            int status = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, self.assetWriterPixelBufferAdaptor.pixelBufferPool, &pixelBuffer);
            if(status != 0){
                //could not get a buffer from the pool
                NSLog(@"Error creating pixel buffer:  status=%d", status);
            }
            // set image data into pixel buffer
            CVPixelBufferLockBaseAddress( pixelBuffer, 0 );
            uint8_t* destPixels = CVPixelBufferGetBaseAddress(pixelBuffer);

            // Danger, Will Robinson!!!!!  USE_BLOCK_IN_FRAME warning...
            CFDataGetBytes(image, CFRangeMake(0, CFDataGetLength(image)), destPixels);

            if(status == 0){
                //CFAbsoluteTime thisFrameWallClockTime = CFAbsoluteTimeGetCurrent();
                CFTimeInterval elapsedTime = thisFrameWallClockTime - firstFrameWallClockTime;
                CMTime presentationTime = CMTimeAdd(firstBufferTimeStamp, CMTimeMake(elapsedTime * TIME_SCALE, TIME_SCALE));
                BOOL success = [self.assetWriterPixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:presentationTime];


                if (!success)
                    NSLog(@"Warning:  Unable to write buffer to video");
            }

            //clean up
            CVPixelBufferUnlockBaseAddress( pixelBuffer, 0 );
            CVPixelBufferRelease( pixelBuffer );
            CFRelease(image);
            CGImageRelease(cgImage);
        } else {
            NSLog(@"Not ready for video data");
        }
    });
}


-(BOOL) setupAssetWriterImageInput:(UIImage*)image
{
    NSDictionary* videoCompressionProps = [NSDictionary dictionaryWithObjectsAndKeys:
                                           [NSNumber numberWithDouble:1024.0*1024.0], AVVideoAverageBitRateKey,
                                           nil ];

    NSDictionary* videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                   AVVideoCodecH264, AVVideoCodecKey,
                                   //[NSNumber numberWithInt:image.size.width], AVVideoWidthKey,
                                   //[NSNumber numberWithInt:image.size.height], AVVideoHeightKey,
                                   [NSNumber numberWithInt:VIDEO_SIZE.width], AVVideoWidthKey,
                                   [NSNumber numberWithInt:VIDEO_SIZE.height], AVVideoHeightKey,

                                   videoCompressionProps, AVVideoCompressionPropertiesKey,
                                   nil];
    NSLog(@"videoSettings: %@", videoSettings);

    assetWriterVideoIn = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
    NSParameterAssert(assetWriterVideoIn);
    assetWriterVideoIn.expectsMediaDataInRealTime = YES;
    NSDictionary* bufferAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                      [NSNumber numberWithInt:kCVPixelFormatType_32ARGB], kCVPixelBufferPixelFormatTypeKey, nil];

    self.assetWriterPixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:assetWriterVideoIn sourcePixelBufferAttributes:bufferAttributes];

    //add input
    if ([assetWriter canAddInput:assetWriterVideoIn]) {
        [assetWriter addInput:assetWriterVideoIn];
    }
    else {
        NSLog(@"Couldn't add asset writer video input.");
        return NO;
    }

    return YES;
}
1 голос
/ 07 мая 2012

Если вы нацелены на iPhone 4S или iPad 2 (или новее), то зеркалирование AirPlay может сделать то, что вам нужно.Существует приложение Mac под названием Reflection , которое будет получать контент AirPlay и даже может напрямую записывать .mov.Это может быть нецелесообразно для поддержки с разнообразным набором пользователей или мест, но это было полезно для меня.

1 голос
/ 06 мая 2012

Я думаю, вам следует сделать серьезный шаг назад и подумать, каков конечный эффект, которого вы пытаетесь достичь.Я не думаю, что продолжение этого пути приведет вас к очень большому успеху.Если вашей конечной целью является просто аналитическая информация о действиях, предпринимаемых вашими пользователями, в вашем распоряжении довольно много аналитических инструментов со вкусом iOS.Вы должны собирать информацию, которая более полезна, чем просто скриншоты.К сожалению, так как ваш вопрос очень сфокусирован на обнаруженных вами проблемах с частотой кадров, я не могу определить вашу конечную цель.

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

1 голос
/ 01 мая 2012

Может быть, вы можете добиться лучших результатов, используя UIGetScreenImage в сочетании с AVAssetWriter, например, для сохранения файла mp2 h264.

Добавьте AVAssetWriterInput к вашему автору активов и позвоните:

- (BOOL)appendSampleBuffer:(CMSampleBufferRef)sampleBuffer

через регулярные промежутки времени (возможно, вы можете запланировать таймер в своем основном потоке) и создать буфер сэмплов из собранного UIImage, вызвав UIGetScreenImage().

AVAssetWriterInputPixelBufferAdaptor также может быть полезен с этим методом:

- (BOOL)appendPixelBuffer:(CVPixelBufferRef)pixelBuffer withPresentationTime:(CMTime)presentationTime

Вы можете посмотреть на этот вопрос , чтобы преобразовать CGImageRef в CVPixelBufferRef.

Обратите внимание на UIGetScreenImage, это частный API, но он разрешен Apple: https://devforums.apple.com/message/149553#149553

...