Базовые видео задачи
В этой главе описываются некоторые общие задачи программирования, используемые при обработке Базового Видео. Примеры в этой главе записаны в 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); |
} |
Вот то, как работает код:
Получает Базовый Графический дисплей ID для дисплея, который Вы хотите связать с этой ссылкой дисплея. Базовая Графическая функция
CGMainDisplayID
просто возвращает ID основного дисплея пользователя (т.е. тот, содержащий строку меню).Создает ссылку дисплея для указанного дисплея. При желании можно создать ссылку дисплея, которая может работать с любым из в настоящее время активных дисплеев путем вызова
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
обработчик вызывают.Устанавливает выходной обратный вызов для ссылки дисплея. Это - функция, которую вызывает ссылка дисплея каждый раз, когда это хочет, чтобы Вы вывели видеокадр. Этот пример передает ссылку на экземпляр с помощью этого метода (т.е.
self
), как пользовательские данные. Например, если этот метод является частьюMyVideoView
класс, пользовательские данные являются ссылкой на aMyVideoView
экземпляр.
Когда Вы будете готовы начать обрабатывать видеокадры, вызвать 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; |
} |
Вот то, как работает код:
Этот метод берет путь к файлу фильма в формате QuickTime как его входной параметр.
Устанавливает массив свойств фильма. Эти свойства указывают
путь к файлу к фильму
должен ли новый фильм быть активным (да, в этом случае)
визуальный контекст для соединения с этим фильмом.
qtVisualContext
переменная является переменной экземпляра класса этого метода.
Эти свойства передаются позже
NewMovieFromProperties
функция.Создает контекст текстуры OpenGL. Это - абстрактное место назначения, в которое нарисованы текстуры OpenGL. Функция QuickTime
QTOpenGLTextureContextCreate
требует, чтобы Вы передали в CGLContext и объекте CGLPixelFormat. В Какао можно получить их из объектов NSOpenGLContext и NSOpenGLPixelFormat, создаваемых при инициализации OpenGL. В Углероде можно получить базовый контекст и формат пикселя от AGLContext и объектов AGLPixelFormat с помощью функций AGLaglGetCGLContext
иaglGetCGLPixelFormat
.)Этот контекст, сохраненный в переменной экземпляра
qtVisualContext
передаетсяNewMovieFromProperties
быть визуальным контекстом, в который QuickTime составит свои фильмы.Создает фильм. Функция QuickTime
NewMovieFromProperties
, доступный в OS X v10.4 и позже, или QuickTime 7.0 и позже, предпочтительный способ инстанцировать фильмов.При использовании Какао можно вызвать метод QTKit
movieFromFile
вместо этого.Если по некоторым причинам Вы хотите установить или изменить визуальный контекст после создания фильма, можно вызвать функцию QuickTime
SetMovieVisualContext
.Выполняет различные инициализации на фильме. Этот раздел является главным образом шаблонным кодом для запуска фильма вначале на уровне обычного кадра, и в непрерывном цикле. Код также циклы через доступные дорожки и выключает любые невидеотреки.
Реализация выходной функции обратного вызова ссылки дисплея
Когда ссылка дисплея работает, она периодически перезванивает к Вашему приложению каждый раз, когда необходимо подготовить кадр. Ваша функция обратного вызова должна получить кадр из определяемого источника видеосигнала как текстура 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 |
} |
Вот то, как работает код:
Дает время контексту, позволяя ему выполнить любое требуемое обслуживание. Необходимо вызвать эту функцию прежде, чем получить каждый кадр.
Проверки, чтобы видеть, доступен ли новый кадр в течение данного времени. Когда кадр будет выведен на экран, требуемое время, как передано Вашему обратному вызову ссылкой дисплея, представляет не текущее время, но время в будущем.
Если кадр доступен, получите его как текстуру OpenGL. Функция QuickTime
QTVisualContextCopyImageForTime
позволяет Вам получить кадр из QuickTime как любой Базовый буферный тип Видеоизображения.Выпускает текущую текстуру (сохраненный в переменной экземпляра
currentFrame
) и устанавливает недавно полученную текстуру для замены его. Необходимо выпустить текстуры OpenGL, когда Вы посредством использования их для минимизации вероятности заполнения видеопамяти.Получает координаты чистой апертуры для текстуры. В большинстве случаев это границы текстуры, необходимые для рендеринга.
Управление кадрами
После получения кадра от источника видеосигнала Вам решать для решения, что сделать с ним. Кадр дан Вам как текстура 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 |
} |
Вот то, как работает код:
Блокирует текущий поток. OpenGL не ориентирован на многопотоковое исполнение, таким образом, необходимо удостовериться, что только один поток может выполнить вызовы OpenGL в любой момент времени.
Наборы OpenGL для рендеринга в контексте этого объекта.
Если прямоугольник получения был изменен, то предпринимает шаги для обновления размера контекста OpenGL.
Если представление видимо, отображает контекст OpenGL на новые границы представления. В противном случае тогда отобразите контекст, чтобы быть эффективно невидимыми.
Получает текущий кадр снова, если он уже не существует. Эта ситуация может произойти если
drawRect
метод вызывается в ответ на представление, изменяют размеры.Вовлекает текущий кадр в контекст OpenGL.
renderCurrentFrame
метод, содержащий Ваш пользовательский код кадра.Сбрасывает получение к средству рендеринга OpenGL. Кадр тогда автоматически выведен на экран на экране в подходящее время.
Разблокировал поток после завершения всех вызовов 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 |
} |
Вот то, как работает код:
Создает Базовое изображение Изображения из текущего кадра. Базовый метод Изображения
ImageWithCVImageBuffer
создает изображение из любого буферного типа изображения (т.е. пиксельный буфер, буфер OpenGL или текстура OpenGL).Получает ограничительный прямоугольник для изображения.
Вовлекает изображение в Базовый контекст Изображения. Базовый метод Изображения
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);
См. Базовое Руководство по программированию Изображения для получения дополнительной информации о создании Базовых контекстов Изображения.
Дает время визуальному контексту для выполнения любого требуемого обслуживания. Необходимо вызвать
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); |
} |
Вот то, как работает код:
Устанавливает фильтр CIImage для применения к кадру.
Рисует изображение с указанным фильтром.
Следует иметь в виду, что Базовое изображение Изображения является неизменным; каждый раз, когда Вы получаете кадр, необходимо создать новое изображение.
Для получения дополнительной информации о создании и использовании Базовых фильтров Изображения, см. Базовое Руководство по программированию Изображения.