AVAssetWriter — пиксельный буфер для наложенных изображений

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

Итак, я предполагаю, что вы должны полностью составить весь пиксельный буфер для каждого кадра, прежде чем писать кадр. Но как бы вы это сделали?

Вот мой код, который работает для рендеринга одного фонового изображения:

CGSize renderSize = CGSizeMake(320, 568);
    NSUInteger fps = 30;

    self.assetWriter = [[AVAssetWriter alloc] initWithURL:
                                  [NSURL fileURLWithPath:videoOutputPath] fileType:AVFileTypeQuickTimeMovie
                                                              error:&error];
    NSParameterAssert(self.assetWriter);

    NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                   AVVideoCodecH264, AVVideoCodecKey,
                                   [NSNumber numberWithInt:renderSize.width], AVVideoWidthKey,
                                   [NSNumber numberWithInt:renderSize.height], AVVideoHeightKey,
                                   nil];

    AVAssetWriterInput* videoWriterInput = [AVAssetWriterInput
                                            assetWriterInputWithMediaType:AVMediaTypeVideo
                                            outputSettings:videoSettings];


    AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor
                                                     assetWriterInputPixelBufferAdaptorWithAssetWriterInput:videoWriterInput
                                                     sourcePixelBufferAttributes:nil];

    NSParameterAssert(videoWriterInput);
    NSParameterAssert([self.assetWriter canAddInput:videoWriterInput]);
    videoWriterInput.expectsMediaDataInRealTime = YES;
    [self.assetWriter addInput:videoWriterInput];

    //Start a session:
    [self.assetWriter startWriting];
    [self.assetWriter startSessionAtSourceTime:kCMTimeZero];

    CVPixelBufferRef buffer = NULL;

    NSInteger totalFrames = 90; //3 seconds

    //process the bg image
    int frameCount = 0;

    UIImage* resizedImage = [UIImage resizeImage:self.bgImage size:renderSize];
    buffer = [self pixelBufferFromCGImage:[resizedImage CGImage]];

    BOOL append_ok = YES;
    int j = 0;
    while (append_ok && j < totalFrames) {
        if (adaptor.assetWriterInput.readyForMoreMediaData)  {

            CMTime frameTime = CMTimeMake(frameCount,(int32_t) fps);
            append_ok = [adaptor appendPixelBuffer:buffer withPresentationTime:frameTime];
            if(!append_ok){
                NSError *error = self.assetWriter.error;
                if(error!=nil) {
                    NSLog(@"Unresolved error %@,%@.", error, [error userInfo]);
                }
            }
        }
        else {
            printf("adaptor not ready %d, %d\n", frameCount, j);
            [NSThread sleepForTimeInterval:0.1];
        }
        j++;
        frameCount++;
    }
    if (!append_ok) {
        printf("error appending image %d times %d\n, with error.", frameCount, j);
    }


    //Finish the session:
    [videoWriterInput markAsFinished];
    [self.assetWriter finishWritingWithCompletionHandler:^() {
        self.assetWriter = nil;
    }];

- (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image {

    CGSize size = CGSizeMake(320,568);

    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,
                             [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey,
                             nil];
    CVPixelBufferRef pxbuffer = NULL;

    CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault,
                                          size.width,
                                          size.height,
                                          kCVPixelFormatType_32ARGB,
                                          (__bridge CFDictionaryRef) options,
                                          &pxbuffer);
    if (status != kCVReturnSuccess){
        NSLog(@"Failed to create pixel buffer");
    }

    CVPixelBufferLockBaseAddress(pxbuffer, 0);
    void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);

    CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(pxdata, size.width,
                                                 size.height, 8, 4*size.width, rgbColorSpace,
                                                 (CGBitmapInfo)kCGImageAlphaPremultipliedFirst);

    CGContextConcatCTM(context, CGAffineTransformMakeRotation(0));
    CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image),
                                           CGImageGetHeight(image)), image);
    CGColorSpaceRelease(rgbColorSpace);
    CGContextRelease(context);

    CVPixelBufferUnlockBaseAddress(pxbuffer, 0);

    return pxbuffer;
}

Опять же, вопрос заключается в том, как создать пиксельный буфер для фонового изображения и массив из N небольших изображений, которые будут накладываться поверх изображения bg. Следующим шагом после этого будет также наложение небольшого видео.


person csoul    schedule 30.01.2014    source источник


Ответы (1)


Вы можете добавить информацию о пикселях из списка изображений поверх буфера пикселей. В этом примере кода показано, как добавить данные BGRA поверх пиксельного буфера ARGB.

// Try to create a pixel buffer with the image mat
uint8_t* videobuffer = m_imageBGRA.data;


// From image buffer (BGRA) to pixel buffer
CVPixelBufferRef pixelBuffer = NULL;
CVReturn status = CVPixelBufferCreate (NULL, m_width, m_height, kCVPixelFormatType_32ARGB, NULL, &pixelBuffer);
if ((pixelBuffer == NULL) || (status != kCVReturnSuccess))
{
    NSLog(@"Error CVPixelBufferPoolCreatePixelBuffer[pixelBuffer=%@][status=%d]", pixelBuffer, status);
    return;
}
else
{
    uint8_t *videobuffertmp = videobuffer;
    CVPixelBufferLockBaseAddress(pixelBuffer, 0);
    GLubyte *pixelBufferData = (GLubyte *)CVPixelBufferGetBaseAddress(pixelBuffer);

    // Add data for all the pixels in the image
    for( int row=0 ; row<m_width ; ++row )
    {
        for( int col=0 ; col<m_height ; ++col )
        {
            memcpy(&pixelBufferData[0], &videobuffertmp[3], sizeof(uint8_t));       // alpha
            memcpy(&pixelBufferData[1], &videobuffertmp[2], sizeof(uint8_t));       // red
            memcpy(&pixelBufferData[2], &videobuffertmp[1], sizeof(uint8_t));       // green
            memcpy(&pixelBufferData[3], &videobuffertmp[0], sizeof(uint8_t));       // blue
            // Move the buffer pointer to the next pixel
            pixelBufferData += 4*sizeof(uint8_t);
            videobuffertmp  += 4*sizeof(uint8_t);
        }
    }


    CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
}

Итак, в этом примере данные изображения (видеобуфер) добавляются в буфер пикселей. Обычно данные пикселя хранятся в одной строке, поэтому для каждого пикселя у нас есть 4 байта (в данном случае представленные как «uint8_t»): первый для синего, затем зеленый, следующий красный и последний для альфа-значения (помните исходное изображение в формате BGRA). Пиксельный буфер работает таким же образом, поэтому данные хранятся в одной строке (в данном случае ARGB, как определено параметром kCVPixelFormatType_32ARGB). Этот фрагмент кода переупорядочивает данные пикселей в соответствии с конфигурацией пиксельного буфера:

memcpy(&pixelBufferData[0], &videobuffertmp[3], sizeof(uint8_t));       // alpha
memcpy(&pixelBufferData[1], &videobuffertmp[2], sizeof(uint8_t));       // red
memcpy(&pixelBufferData[2], &videobuffertmp[1], sizeof(uint8_t));       // green
memcpy(&pixelBufferData[3], &videobuffertmp[0], sizeof(uint8_t));       // blue

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

// Move the buffer pointer to the next pixel
pixelBufferData += 4*sizeof(uint8_t);
videobuffertmp  += 4*sizeof(uint8_t);

Перемещение указателей на 4 байта вперед.

Если ваши изображения меньше, вы можете добавить их в меньшую область или определить «если», используя альфа-значение в качестве целевых данных. Например:

// Add data for all the pixels in the image
for( int row=0 ; row<m_width ; ++row )
{
    for( int col=0 ; col<m_height ; ++col )
    {
        if( videobuffertmp[3] > 10 ) // check alpha channel
        {
            memcpy(&pixelBufferData[0], &videobuffertmp[3], sizeof(uint8_t));       // alpha
            memcpy(&pixelBufferData[1], &videobuffertmp[2], sizeof(uint8_t));       // red
            memcpy(&pixelBufferData[2], &videobuffertmp[1], sizeof(uint8_t));       // green
            memcpy(&pixelBufferData[3], &videobuffertmp[0], sizeof(uint8_t));       // blue
        }
        // Move the buffer pointer to the next pixel
        pixelBufferData += 4*sizeof(uint8_t);
        videobuffertmp  += 4*sizeof(uint8_t);
    }
}
person goe    schedule 06.05.2014