Базовые видео задачи

В этой главе описываются некоторые общие задачи программирования, используемые при обработке Базового Видео. Примеры в этой главе записаны в Objective C и используют Какао, но Базовое Видео может использоваться в программе Углерода также.

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

Получение кадров Используя ссылку дисплея

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

Для простоты предположите, что все вызовы метода в этом разделе действуют на a MyVideoView объект, разделяющийся на подклассы от NSOpenGLView класс:

  Интерфейс Listing 2-1 The MyVideoView

@interface MyVideoView : NSOpenGLView
{
 
    NSRecursiveLock         *lock;
    QTMovie                 *qtMovie;
    QTTime                  movieDuration;
    QTVisualContextRef      qtVisualContext;
    CVDisplayLinkRef        displayLink;
    CVImageBufferRef        currentFrame;
    CIFilter                *effectFilter;
    CIContext               *ciContext;
 
    NSDictionary            *fontAttributes;
 
    int                     frameCount;
    int                     frameRate;
    CVTimeStamp             frameCountTimeStamp;
    double                  timebaseRatio;
    BOOL                    needsReshape;
    id                      delegate;
}
@end

Для получения дополнительной информации об использовании NSOpenGLView класс, см. проект в качестве примера Какао OpenGL.

Установка ссылки дисплея

Установка ссылки дисплея включает следующие шаги:

  • Создайте поток ссылки дисплея.

  • Свяжите ссылку к определенному дисплею.

  • Зарегистрируйте выходной обратный вызов дисплея.

  • Запуск дисплея соединяет поток.

Метод awakeFromNib в Перечислении 2-2 показывает, как Вы могли бы реализовать ссылку дисплея.

Перечисление 2-2  , Настраивающее ссылку дисплея

- (void)awakeFromNib
{
    CVReturn            error = kCVReturnSuccess;
    CGDirectDisplayID   displayID = CGMainDisplayID();// 1
 
    error = CVDisplayLinkCreateWithCGDisplay(displayID, &displayLink);// 2
    if(error)
    {
        NSLog(@"DisplayLink created with error:%d", error);
        displayLink = NULL;
        return;
    }
    error = CVDisplayLinkSetOutputCallback(displayLink,// 3
                                 MyDisplayLinkCallback, self);
 
}

Вот то, как работает код:

  1. Получает Базовый Графический дисплей ID для дисплея, который Вы хотите связать с этой ссылкой дисплея. Базовая Графическая функция CGMainDisplayID просто возвращает ID основного дисплея пользователя (т.е. тот, содержащий строку меню).

  2. Создает ссылку дисплея для указанного дисплея. При желании можно создать ссылку дисплея, которая может работать с любым из в настоящее время активных дисплеев путем вызова CVDisplayLinkCreateWithActiveCGDisplays вместо этого. Необходимо тогда вызвать CVDisplayLinkSetCurrentCGDisplay определять определенный дисплей для ссылки дисплея.

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

    - (void)windowChangedScreen:(NSNotification*)inNotification
    {
      NSWindow *window = [mainView window];
      CGDirectDisplayID displayID = (CGDirectDisplayID)[[[[window screen]
             deviceDescription] objectForKey:@"NSScreenNumber"] intValue];
      if((displayID != NULL) && (mainViewDisplayID != displayID))
      {
        CVDisplayLinkSetCurrentCGDisplay(displayLink, displayID);
        mainViewDisplayID = displayID;
      }
    }

    В Углероде необходимо вызвать функцию Менеджера окон GetWindowGreatestAreaDevice получить GDevice структура для дисплея окна. Можно тогда сохранить его устройство ID окном и проверкой, чтобы видеть, изменилось ли это каждый раз, когда Ваш kEventWindowBoundsChanged обработчик вызывают.

  3. Устанавливает выходной обратный вызов для ссылки дисплея. Это - функция, которую вызывает ссылка дисплея каждый раз, когда это хочет, чтобы Вы вывели видеокадр. Этот пример передает ссылку на экземпляр с помощью этого метода (т.е. self), как пользовательские данные. Например, если этот метод является частью MyVideoView класс, пользовательские данные являются ссылкой на a MyVideoView экземпляр.

Когда Вы будете готовы начать обрабатывать видеокадры, вызвать CVDisplayLinkStart для активации дисплея соединяют поток. Этот поток выполнения, независимые от Вашего порядка подачи заявки. Необходимо остановить поток путем вызова CVDisplayLinkStop когда Ваше приложение выходит или иначе прекращает отображать видео.

Инициализация Вашего источника видеосигнала

Прежде чем можно будет начать обрабатывать, необходимо установить источник видеосигнала для обеспечения кадров. Источник видеосигнала может быть чем-либо, что может предоставить несжатые видеоданные как текстуры OpenGL. Например, этим источником мог быть QuickTime, OpenGL или Ваш собственный генератор видеокадра.

В каждом случае необходимо создать контекст OpenGL для отображения сгенерированного видео. Вы передаете это своему источнику видеосигнала, чтобы указать, что это - то, где Вы хотите, чтобы Ваше видео было выведено на экран.

Перечисление 2-3 показывает метод, устанавливающий фильм в формате QuickTime, чтобы быть Вашим источником видеосигнала.

Перечисление 2-3  , Инициализирующее источник видеосигнала QuickTime

- (id)initWithFilePath:(NSString*)theFilePath // 1
{
    self = [super init];
 
    OSStatus        theError = noErr;
    Boolean         active = TRUE;
    UInt32          trackCount = 0;
    OSType          theTrackType;
    Track           theTrack = NULL;
    Media           theMedia = NULL;
 
    QTNewMoviePropertyElement newMovieProperties[] = // 2
        {
        {kQTPropertyClass_DataLocation,
            kQTDataLocationPropertyID_CFStringNativePath,
            sizeof(theFilePath), &theFilePath, 0},
        {kQTPropertyClass_NewMovieProperty, kQTNewMoviePropertyID_Active,
            sizeof(active), &active, 0},
        {kQTPropertyClass_Context, kQTContextPropertyID_VisualContext,
            sizeof(qtVisualContext), &qtVisualContext, 0},
        };
 
    theError = QTOpenGLTextureContextCreate( NULL, NULL, // 3
        [[NSOpenGLView defaultPixelFormat]
         CGLPixelFormatObj], NULL, &qtVisualContext);
 
    if(qtVisualContext == NULL)
     {
        NSLog(@"QTVisualContext creation failed with error:%d", theError);
        return NULL;
    }
 
    theError = NewMovieFromProperties(
        sizeof(newMovieProperties) / sizeof(newMovieProperties[0]),// 4
        newMovieProperties, 0, NULL, &channelMovie);
 
    if(theError)
    {
        NSLog(@"NewMovieFromProperties failed with %d", theError);
        return NULL;
    }
 
    // setup the movie
    GoToBeginningOfMovie(channelMovie);// 5
    SetMovieRate(channelMovie, 1 << 16);
    SetTimeBaseFlags(GetMovieTimeBase(channelMovie), loopTimeBase);
    trackCount = GetMovieTrackCount(channelMovie);
    while(trackCount > 0)
    {
        theTrack = GetMovieIndTrack(channelMovie, trackCount);
        if(theTrack != NULL)
        {
            theMedia = GetTrackMedia(theTrack);
            if(theMedia != NULL)
            {
                GetMediaHandlerDescription(theMedia, &theTrackType, 0, 0);
                if(theTrackType != VideoMediaType)
                {
                    SetTrackEnabled(theTrack, false);
                }
            }
        }
        trackCount--;
    }
 
    return self;
}

Вот то, как работает код:

  1. Этот метод берет путь к файлу фильма в формате QuickTime как его входной параметр.

  2. Устанавливает массив свойств фильма. Эти свойства указывают

    • путь к файлу к фильму

    • должен ли новый фильм быть активным (да, в этом случае)

    • визуальный контекст для соединения с этим фильмом. qtVisualContext переменная является переменной экземпляра класса этого метода.

    Эти свойства передаются позже NewMovieFromProperties функция.

  3. Создает контекст текстуры OpenGL. Это - абстрактное место назначения, в которое нарисованы текстуры OpenGL. Функция QuickTime QTOpenGLTextureContextCreate требует, чтобы Вы передали в CGLContext и объекте CGLPixelFormat. В Какао можно получить их из объектов NSOpenGLContext и NSOpenGLPixelFormat, создаваемых при инициализации OpenGL. В Углероде можно получить базовый контекст и формат пикселя от AGLContext и объектов AGLPixelFormat с помощью функций AGL aglGetCGLContext и aglGetCGLPixelFormat.)

    Этот контекст, сохраненный в переменной экземпляра qtVisualContext передается NewMovieFromProperties быть визуальным контекстом, в который QuickTime составит свои фильмы.

  4. Создает фильм. Функция QuickTime NewMovieFromProperties, доступный в OS X v10.4 и позже, или QuickTime 7.0 и позже, предпочтительный способ инстанцировать фильмов.

    При использовании Какао можно вызвать метод QTKit movieFromFile вместо этого.

    Если по некоторым причинам Вы хотите установить или изменить визуальный контекст после создания фильма, можно вызвать функцию QuickTime SetMovieVisualContext.

  5. Выполняет различные инициализации на фильме. Этот раздел является главным образом шаблонным кодом для запуска фильма вначале на уровне обычного кадра, и в непрерывном цикле. Код также циклы через доступные дорожки и выключает любые невидеотреки.

Реализация выходной функции обратного вызова ссылки дисплея

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

При использовании объектно-ориентированного программирования Вы, вероятно, захотите, чтобы Ваш обратный вызов вызвал метод, как показано в Перечислении 2-4 и Перечислении 2-5.

Перечисление 2-4  , Вызывающее метод от Вашего обратного вызова

CVReturn MyDisplayLinkCallback (
    CVDisplayLinkRef displayLink,
    const CVTimeStamp *inNow,
    const CVTimeStamp *inOutputTime,
    CVOptionFlags flagsIn,
    CVOptionFlags *flagsOut,
    void *displayLinkContext)
{
 CVReturn error =
        [(MyVideoView*) displayLinkContext displayFrame:inOutputTime];
 return error;
}

Функция обратного вызова просто вызывает displayFrame метод, реализованный в MyVideoView класс. Экземпляр этого класса передается Вашему обратному вызову в displayLinkContext параметр. ( MyVideoView класс, выводящий на экран Ваши кадры, должен быть подклассом NSOpenGLView, как показано в Перечислении 2-1.)

Перечисление 2-5  Реализовывая displayFrame метод

- (CVReturn)displayFrame:(const CVTimeStamp *)timeStamp
{
    CVReturn rv = kCVReturnError;
    NSAutoreleasePool *pool;
 
    pool = [[NSAutoreleasePool alloc] init];
    if([self getFrameForTime:timeStamp])
    {
        [self drawRect:NSZeroRect];
        rv = kCVReturnSuccess;
    }
    else
    {
       rv = kCVReturnError;
    }
    [pool release];
    return rv;
}
 

Вы получаете кадр в течение требуемого времени как текстура OpenGL. Перечисление 2-6 показывает, как Вы могли бы реализовать getFrameForTime метод, если Вы получали свои видеокадры из QuickTime. Этот пример предполагает, что метод является частью пользовательского MyVideoView класс.

Перечисление 2-6  Получая кадры из QuickTime

- (BOOL)getFrameForTime:(const CVTimeStamp*)syncTimeStamp
{
    CVOpenGLTextureRef      newTextureRef = NULL;
 
    QTVisualContextTask(qtVisualContext);// 1
    if(QTVisualContextIsNewImageAvailable(qtVisualContext, syncTimeStamp))// 2
    {
        QTVisualContextCopyImageForTime(qtVisualContext, NULL, syncTimeStamp,// 3
                 &newTextureRef);
 
        CVOpenGLTextureRelease(currentFrame);// 4
        currentFrame = newTextureRef;
 
        CVOpenGLTextureGetCleanTexCoords (// 5
                    currentFrame, lowerLeft, lowerRight, upperRight, upperLeft);
        return YES; // we got a frame from QT
    }
    else
    {
        //NSLog(@"No Frame ready");
    }
    return NO;  // no frame available
}

Вот то, как работает код:

  1. Дает время контексту, позволяя ему выполнить любое требуемое обслуживание. Необходимо вызвать эту функцию прежде, чем получить каждый кадр.

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

  3. Если кадр доступен, получите его как текстуру OpenGL. Функция QuickTime QTVisualContextCopyImageForTime позволяет Вам получить кадр из QuickTime как любой Базовый буферный тип Видеоизображения.

  4. Выпускает текущую текстуру (сохраненный в переменной экземпляра currentFrame) и устанавливает недавно полученную текстуру для замены его. Необходимо выпустить текстуры OpenGL, когда Вы посредством использования их для минимизации вероятности заполнения видеопамяти.

  5. Получает координаты чистой апертуры для текстуры. В большинстве случаев это границы текстуры, необходимые для рендеринга.

Управление кадрами

После получения кадра от источника видеосигнала Вам решать для решения, что сделать с ним. Кадр дан Вам как текстура OpenGL, таким образом, можно управлять им с любыми вызовами OpenGL. Перечисление 2-7 показывает, как Вы могли установить OpenGL для вовлечения границ представления путем переопределения стандартного NSView drawRect метод.

Перечисление 2-7  , Выводящее на экран OpenGL в прямоугольнике

- (void)drawRect:(NSRect)theRect
{
    [lock lock];    // 1
    NSRect      frame = [self frame];
    NSRect      bounds = [self bounds];
 
    [[self openGLContext] makeCurrentContext];// 2
    if(needsReshape)// 3
    {
        GLfloat     minX, minY, maxX, maxY;
 
        minX = NSMinX(bounds);
        minY = NSMinY(bounds);
        maxX = NSMaxX(bounds);
        maxY = NSMaxY(bounds);
 
        [self update];
 
        if(NSIsEmptyRect([self visibleRect])) // 4
        {
            glViewport(0, 0, 1, 1);
        } else {
            glViewport(0, 0,  frame.size.width ,frame.size.height);
        }
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(minX, maxX, minY, maxY, -1.0, 1.0);
 
        needsReshape = NO;
    }
 
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
 
    if(!currentFrame)// 5
        [self updateCurrentFrame];
    [self renderCurrentFrame];      // 6
    glFlush();// 7
    [lock unlock];// 8
}

Вот то, как работает код:

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

  2. Наборы OpenGL для рендеринга в контексте этого объекта.

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

  4. Если представление видимо, отображает контекст OpenGL на новые границы представления. В противном случае тогда отобразите контекст, чтобы быть эффективно невидимыми.

  5. Получает текущий кадр снова, если он уже не существует. Эта ситуация может произойти если drawRect метод вызывается в ответ на представление, изменяют размеры.

  6. Вовлекает текущий кадр в контекст OpenGL. renderCurrentFrame метод, содержащий Ваш пользовательский код кадра.

  7. Сбрасывает получение к средству рендеринга OpenGL. Кадр тогда автоматически выведен на экран на экране в подходящее время.

  8. Разблокировал поток после завершения всех вызовов OpenGL.

renderCurrentFrame метод содержит пользовательскую обработку Вашего приложения, хочет примениться к кадру. Перечисление 2-8 показывает простой пример того, как можно реализовать этот метод. Этот пример использует Базовое Изображение для вовлечения кадра в контекст OpenGL.

Перечисление 2-8  , Получающее кадр

- (void)renderCurrentFrame
{
    NSRect      frame = [self frame];
 
    if(currentFrame)
    {
        CGRect      imageRect;
        CIImage     *inputImage;
 
        inputImage = [CIImage imageWithCVImageBuffer:currentFrame];// 1
 
        imageRect = [inputImage extent];// 2
        [ciContext drawImage:inputImage // 3
                atPoint:CGPointMake(
                (int)((frame.size.width - imageRect.size.width) * 0.5),
                (int)((frame.size.height - imageRect.size.height) * 0.5))
                fromRect:imageRect];
 
    }
    QTVisualContextTask(qtVisualContext);// 4
}

Вот то, как работает код:

  1. Создает Базовое изображение Изображения из текущего кадра. Базовый метод Изображения ImageWithCVImageBuffer создает изображение из любого буферного типа изображения (т.е. пиксельный буфер, буфер OpenGL или текстура OpenGL).

  2. Получает ограничительный прямоугольник для изображения.

  3. Вовлекает изображение в Базовый контекст Изображения. Базовый метод Изображения drawImage:atPoint:fromRect рисует кадр в указанном визуальном контексте в указанном расположении.

    Перед получением Вы, должно быть, создали Базовый контекст Изображения, ссылающийся на то же пространство получения как контекст OpenGL. Выполнение так позволяет Вам вовлекать пространство с помощью Базового Изображения APIs и затем отображать его с помощью OpenGL. Например, Вы могли добавить следующий код к Перечислению 2-3 после создания Вашего контекста OpenGL:

    /* Create CGColorSpaceRef */
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
     
    /* Create CIContext */
    ciContext = [[CIContext contextWithCGLContext:
                    (CGLContextObj)[[self openGLContext] CGLContextObj]
                    pixelFormat:(CGLPixelFormatObj)
                    [[self pixelFormat] CGLPixelFormatObj]
                    options:[NSDictionary dictionaryWithObjectsAndKeys:
                    (id)colorSpace,kCIContextOutputColorSpace,
                    (id)colorSpace,kCIContextWorkingColorSpace,nil]] retain];
    CGColorSpaceRelease(colorSpace);

    См. Базовое Руководство по программированию Изображения для получения дополнительной информации о создании Базовых контекстов Изображения.

  4. Дает время визуальному контексту для выполнения любого требуемого обслуживания. Необходимо вызвать QTVisualContextTask один раз каждый раз через Ваш метод рисования.

Используя базовую фильтрацию изображения с базовым видео

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

Можно загрузить Базовый фильтр Изображения с помощью Базового Изображения метод CIFIlter filterWithName:

effectFilter = [[CIFilter filterWithName:@"CILineScreen"] retain];
[effectFilter setDefaults];

Этот пример загружает стандартный Базовый фильтр линейного растра Изображения, но необходимо использовать то, что является подходящим для приложения.

После загрузки фильтра Вы обрабатываете свое изображение с ним в Вашем методе рисования. Перечисление 2-9 показывает, как Вы могли применить Базовый фильтр Изображения. Это перечисление идентично Перечислению 2-8 за исключением того, что это фильтрует входное изображение прежде, чем вовлечь его в Базовый контекст Изображения.

Перечисление 2-9  , Применяющее Базовое Изображение, фильтрует к изображению

- (void)renderCurrentFrameWithFilter
{
    NSRect      frame = [self frame];
 
    if(currentFrame)
    {
        CGRect      imageRect;
        CIImage     *inputImage, *outputImage;
 
        inputImage = [CIImage imageWithCVImageBuffer:currentFrame];
 
        imageRect = [inputImage extent];
        [effectFilter setValue:inputImage forKey:@"inputImage"];// 1
        [[[NSGraphicsContext currentContext] CIContext]// 2
            drawImage:[effectFilter valueForKey:@"outputImage"]
            atPoint:CGPointMake((int)
                ((frame.size.width - imageRect.size.width) * 0.5),
                (int)((frame.size.height - imageRect.size.height) * 0.5))
            fromRect:imageRect];
 
    }
    QTVisualContextTask(qtVisualContext);
}

Вот то, как работает код:

  1. Устанавливает фильтр CIImage для применения к кадру.

  2. Рисует изображение с указанным фильтром.

Следует иметь в виду, что Базовое изображение Изображения является неизменным; каждый раз, когда Вы получаете кадр, необходимо создать новое изображение.

Для получения дополнительной информации о создании и использовании Базовых фильтров Изображения, см. Базовое Руководство по программированию Изображения.