Core Video 프로그래밍 가이드
OSXDEV
목차 |
[편집] Introduction
[편집] 코어 비디오란 무엇인가?
코어 비디오는 Mac OS X의 디지털 비디오를 위한 새로운 파이프라인 모델이다. 프로세스를 각각의 단계로 나눔으로서 개발자가 데이터 형의 변환( QuickTime, OpenGL등)이나 디스플레이 동기화에 대한 걱정 없이 프레임에 접근하고 수정할 수 있도록 단순화 했다.
코어 비디오는 코어 이미지와 코어 오디오 기술과 호환성을 가진다.
코어 비디오는 다음 경우 사용가능하다
- Mac OS X 10.4이후
- Mac OS X 10.3에 QuickTime 7.0 이상이 인스톨 되어있는 경우
최적의 결과를 위해, 하드웨어 가속을 지원하는( Quartz Extreme) 컴퓨터에서만 코어 비디오 기능을 사용하길 권한다.
[편집] 이 문서를 읽어야 할 사람은?
이 문서의 독자는 비디오 이미지를 수정하는 데 있어 보다 높은 차원의 컨트롤을 원하는 코코아나 카본 개발자이다. 개발자는 디지털 비디오와 OpenGL 그리고 멀티쓰레드 프로그래밍에 익숙해야 한다.
코어 비디오는 개별 비디오 프레임을 다루고자 할때에만 필요하다. 예를 들어, 다음과 같은 타입의 비디오 처리는 코어 비디오를 필요로 한다:
- 색보정 혹은 코어 이미지 필터가 제공하는 다른 필터 적용
- 비디오 이미지의 물리적인(워핑이나 표면에 매핑하기와 같은) 변형
- OpenGL 장면에 비디오를 더하기
- 프레임에 타임코드 보이기와 같은 추가적인 정보를 더하기.
- 다중 비디오 스트림을 합성하기
이 정도 수준의 복잡성이 없다면(예를 들어, 어플리케이션에서 비디오를 단지 디스플레이 하고 싶다), 비디오의 디스플레이에는 보다 단순화한 무비 플레이어인 HIMovieView(카본)나 QTKit(코코아)를 이용하라. 쿼츠 컴포저를 이용해서 비디오에 효과를 줄 수도 있다.
[편집] 이 문서의 구성
이 문서는 다음의 챕터로 구성되어 있다.
- "Core Video Concepts"는 코어 비디오 파이프 라인 모델을 설명하고 코어 비디오 API를 사용하는 데 필수적인 핵심 개념을 설명한다.
- "Core Video Tasks"는 개별 비디오 프레임을 가져오고 수정하기 위해 코어 비디오를 사용하는 법을 보여준다.
[편집] 참조
애플은 코어 비디오 프로그래밍 가이드를 보완하기 위해 ADC 레퍼런스 라이브러리중 다음과 같은 추가 리소스를 제공한다 :
- 코어 비디오 레퍼런스는 코어 비디오 API에 대한 자세한 설명을 제공한다
- Mac OS X의 OpenGL 프로그래밍 가이드는 GL 텍스쳐와 CGL 그래픽스 컨텍스트에 대한 정보를 제공한다.
- 코코아 OpenGL은 코코아에서 사용가능한 OpenGL클래스(NSOpenGLView와 같은)에 대한 정보를 가지고 있다.
- 코어 이미지 프로그래밍 가이드는 비디오 프레임에 적용할 수 있는 코어 이미지 필터를 만드는 법을 보여준다.
부가적으로, OpenGL의 웹사이트 ( http://www.opengl.org )는 OpenGL API에 대한 정보의 주요 소스이다.
[편집] Core Video Concepts
코어 비디오는 Mac OS X의 디지털 비디오를 위한 새로운 파이프라인 모델이다.이는 비디오 처리를 단순화 하기 위해 두가지 주요 기능을 제공한다:
- 비 압축 비디오 프레임(QuickTime등에서 나온)과 OpenGL간의 전환을 쉽게 하는 표준 버퍼링 모델.
- 디스플레이 동기화 솔루션
이 장에서는 이 기능들 뒤에 숨은 개념을 소개하려 한다.
[편집] 코어 비디오 파이프 라인
코어 비디오는 읽어온 무비 데이터로부터 스크린에 디스플레이 되는 실제 비디오 프레임까지의 비디오 처리를 분리된 몇 단계의 파이프라인으로 가정한다. 이 파이프 라인이 커스텀 프로세싱을 추가하는 것을 쉽게 한다.
무비의 프레임 데이타는 (QuickTime같은)비디오 소스로부터 오며, 이것은 비쥬얼 컨텍스트에 할당된다. 비쥬얼 컨텍스트는 단순히 비디오를 렌더하여 그려질 목적지를 지정한다. 예를 들어, 이 컨텍스트는 코어 그래픽스 컨텍스트나 OpenGL 컨텍스트 일 수 있다. 대부분의 경우, 비쥬얼 컨텍스트는 윈도우안의 뷰와 연관있으나, 오프스크린 컨텍스트 역시 가능하다.
드로잉 컨텍스트를 지정하고 나면, 그 프레임을 원하는 대로 수정할 수 있다. 예를 들어, 코어 이미지 필터를 이용하거나 OpenGL의 워핑 효과를 지정하여 프레이을 처리할 수 있다. 그런 후, 그 프레임을 OpenGL에게 떠넘겨, 렌더링 명령을 수행하고 완성된 이미지를 디스플레이로 보낸다.
코어 비디오 파이프 라인내에서, 개발자들에게 가장 중요한 면은 디스플레이 동기화를 관장하는 디스플레이 링크와 다양한 버퍼 타입들 간에 프레임을 옮길 때 메모리 관리를 간단히 하는 커먼 버퍼링 모델이다. 대부분의 어플리케이션은 디스플레이 링크만을 사용하기 위해서 비디오를 재처리한다. 당신은 비디오 프레임을 만들 때(혹은 압축할 때)에나 코어 비디오 버퍼의 사용을 걱정하면 될 것이다.
[편집] 디스플레이 링크
비디오와 디스플레이의 리플레쉬율을 동기화 하기 위해서, 코어 비디오는 디스플레이 링크라고 하는 특별한 타이머를 제공한다. 디스플레이 링크는 높은 우선순위를 가진 독립적인 쓰레드처럼 동작하므로, 당신이 만드는 어플리케이션 프로세스의 상호작용에 영향을 받지 않는다.
과거에는 , 비디오 프레임을 디스플레이의 리플레쉬율과 맞추는 것이 종종 문제가 되었다.특히 오디오가 있는 경우에는 더 했다. 그냥 단순히 프레임을 언제 내 보낼지를 (타이머를 이용하거나 해서)막연히 예상 할 수 밖에 없었으며, 사용자 상호작용, CPU 부하, 윈도우 합성등으로 인해 발생할 수 있는 대기시간은 계산에 넣을 수 없었다. 코어 비디오 디스플레이 링크는 언제 프레임이 아웃풋 되어야 하는 지를 디스플레이 타입과 대기사간에 따라 지능적으로 예상할 수 있다.
그림 1-2는 디스플레이 링크가 비디오 프레임을 처리할 때 당신의 어플리케이션과 어떻게 상호작용하는지를 보여준다.

그림 1-2 디스플레이 링크를 이용해 비디오 프레임을 처리하기
- 디스플레이 링크가 당신의 콜백을 주기적으로 호출하여 프레임을 요청한다
- 당신의 콜백은 요청하는 시각에 해당하는 프레임을 얻어내야 한다. 이 프레임을 OpenGL 텍스쳐로 얻어낸다.(이 예제는 당신의 프레임이 QuickTime으로부터 온다고 가정하지만, 프레임 버퍼를 제공하는 어떤 비디오 소스라도 사용할 수 있다.)
- 수정을 위해 이 텍스쳐에 어떤 OpenGL호출이든 사용할 수 있다.
만약 어떤 이유에서 예상보다 처리시간이 길어진다면 ( 그래서 디스플레이 링크의 예상이 지난다면), 비디오 그래픽 카드는 프레임을 드랍 시키거나 타이밍 에러를 보정한다.
[편집] 버퍼 관리
만약 당신의 어플리케이션이 디스플레이를 위한 프레임을 생성해 내거나 읽어들이는 raw 비디오를 압축한다면 그 처리를 하는 동안 이미지를 저장해 둘 필요가 있다. 코어 비디오는 이 처리를 간단하게 하기 위해 다양한 버퍼 타입을 제공한다.
이전에는 OpenGL을 이용해 퀵타임 프레임을 처리하고자 한다면 많은 어려움이 있었다. 다양한 버퍼 타입들을 변환하고 내부 메모리를 관리하는 것은 부담스러운 일이었다. 이제, 코어 비디오를 이용하면, 버퍼들은 Core Foundation 스타일의 오브젝트가 되어, 생성하고 없애기 쉽고 하나의 버퍼에서 다른 버퍼로 변환이 용이하게 된다.
코어 비디오는 CVBuffer타입의 추상적인 버퍼를 정의한다. 모든 다른 버퍼 타입들은 CVBuffer타입으로부터 얻어진다(또한 그 유형의 버퍼가 된다). CVBuffer는 비디오, 오디오 혹은 다른 어떤 타입의 테이터를 가지고 있을 수 있다. 모든 코어 비디오 버퍼에서 CVBuffer API를 사용할 수 있다.
- 이미지 버퍼는 비디오 이미지(혹은 프레임)를 저장하기 위해 특별히 사용되는 추상적인 버퍼이다. 픽셀 버퍼와 OpenGL버퍼는 이미지 버퍼로부터 나온다.
- 픽셀버퍼는 메인 메모리에 이미지를 저장한다.
- 코어 비디오 OpenGL 버퍼는 표준 OpenGL버퍼에 대한 래퍼이며, 비디오(그래픽 카드) 메모리에 있는 이미지를 저장한다.
- 코어 비디오 OpenGL 텍스쳐는 표준 OpenGL 텍스쳐의 래퍼이며, 그래픽 카드 메모리에 저장되어 있는 수정할 수 없는 이미지이다. 텍스쳐는 픽셀 버퍼 혹은 OpenGL버퍼로부터 가져오며 실제 프레임 데이터를 포함한다. 텍스쳐를 디스플레이 하기 위해서는 (사각형이나 구와 같은) 프리미티브 위에 얹혀져야 한다.
버퍼를 사용할 때는, 버퍼 풀 안에 넣어서 관리하는 것이 유리하다. 버퍼 풀은 몇개의 버퍼를 얼로케이트 해 놓고 필요에 따라 재사용한다. 이 장점은 시스템이 메모리를 얼록/디얼록하는 데 추가적인 시간을 할애할 필요가 없다는 것이다; 버퍼를 릴리즈하면, 그것은 풀로 되돌아 간다. 메인 메모리에 픽셀 버퍼 풀을 만들고 비디오 메모리에 OpenGL 버퍼를 만든다.
버퍼풀을 회사에서 사용하기 위해 구매한 차량으로 생각할 수 있다. 직원은 손쉽게 차를 이용하고, 업무가 끝나면 원래 자리에 돌려 놓는다. 이렇게 하면 매번 차를 구매하고 되파는 수고를 덜 수 있다. 자원을 최대한 활용하기 위해, 차량의 대수는 수요에 맞데 조정되어야 할 것이다.
비슷한 방식으로, 텍스쳐 캐시를 이용하여 OpenGL텍스쳐들을 얼로케이트 하여 재사용 될 수 있는 몇개의 텍스쳐들을 가지고 있을 수 있다.
그림 1-3은 퀵타임 무비를 처리할 때 내부적으로 발생하는 프레임 처리를, 몇개의 버퍼들과 버퍼풀을 이용하며 비디오 데이터를 저장 압축된 파일로부터 스크린에 나타나는 실제 픽셀 이미지로가는 진행 단계를 보여준다
프레임 처리의 단계는 다음과 같다:
- 퀵타임이 각각의 프레임이 될 비디오 데이터 스트림을 공급한다.
- 프레임은 특정 코덱을 이용해서 압축해제된다. 픽셀 버퍼 풀은 개별 프레임을 렌더하는 데 필요한 키 프레임, B 프레임등을 보유하기 위해 사용된다.
- 개별 프레임들은 비디오 메모리에 OpenGL 텍스쳐로 저장된다. 프레임에 추가적인 이미지 처리(디-인터레이싱 등의)는 여기에서 이루어지고 그 결과는 OpenGL버퍼에 저장된다.
- 코어 비디오로부터 프레임을 요청하면 (디스플레이 링크 콜백의 응답에 의해), OpenGL버퍼의 내용은 OpenGL 텍스쳐로 변화되어 전달된다.
[편집] 프레임이란 무엇인가?
비디오 프레임은 주로 그 프레임을 디스플레이하는 시스템이 유용하게 사용하는 데 관련된 정보를 가지고 있다. 코어 비디오에서, 이 정보는 비디오 프레임에 관련된 첨부이다. 첨부는 Core Foundation 오브젝트로서 다음과 같은 일반적인 비디오 속성을 나타내는 다양한 데이터를 가진다:
- Clean aperture와 preferred clean aperture. 비디오 처리(필터링과 같은)는 종종 프레임의 외곽에 인공적인 픽셀을 만들어 낸다. 이런 인공픽셀을 보이지 않게 하려고, 대부분의 비디오 이미지들은 실제 디스플레이 되는 것보다 많은 정보를 가지고 있다가 모서리 부분을 잘라내 버린다. preferred clean aperture 는 비디오가 압축될때 권장하는 cropping이다. clean aperture는 디스플레이 될때 실제 cropping된 것이다.
- 컬러 스페이스. 컬러 스페이스는 RGB나 YCbCr처럼, 이미지를 나타낼 때 사용한 모델이다. 대부분의 모델이 공간상의 점에 매핑될 수 있는 몇가지 변수를 가지고 있기 때문에 "컬러 스페이스"라고 부른다. 예를 들어, RGB 컬러 스페이스는 red, green, blue의 세가지 매개변수를 가지고 있으며, 삼차원적인 공간안에서 세가지 변수의 가능한 모든 조합이 대칭된다.
- 정사각형 픽셀에 대한 비례. 컴퓨터에서의 디지털 비디오는 전형적으로 정사각형 픽셀을 사용한다. 그러나, TV는 직사각형 픽셀을 사용하므로 방송용 비디오를 만든다면 여기서 어긋나는 부분을 보상해 줘야 한다.
- 감마 레벨. 감마는 우리의 눈이 예상하는 바대로 하드웨어의 디스플레이 출력을 맞추기 위해 사용되는 "조작 값"이다. 예를 들어, 디스플레이의 컬러 강도에 따는 전압 비율은 일정하지가 않다; "푸른색" 신호의 전압을 두배로 한다고 해서 "두배로 푸른" 이미지가 만들어지는 것은 아니다. 감마는 입력값 대 출력값이 최대한 맞도록 하는 지수 곡선이다.
- 타임 스탬프. 보통 시, 분, 초와 fraction으로 표현된다. 타임스탬프는 특정 프레임이 무비에서 언제 나타나는 지를 나타낸다. fraction 부분의 크기는 무비가 사용하는 타임 베이스에 의해 결정된다. 타임스탬프는 특정 무비 프레임에 독립적으로 접근할 수 있도록 하며, 다중의 비디오와 오디오 트랙을 동기화 하는 것을 쉽게 한다.
첨부의 값은 키-값 쌍으로 지정한다. Core Video Reference에 있는 미리 정의된 키를 사용하거나 커스텀 프레임 정보가 있다면 자신만의 키를 정의할 수도 있다. 첨부가 전달될 것이라는 것을 미리 지정하면, 다음번 버퍼, 예를 들어 픽셀 버퍼로부터 OpenGL 텍스처를 만들때, 그 첨부를 쉽게 전송 할 수 있다.
[편집] Core Video Tasks
이 챕터에서는 코어 비디오를 처리할때 사용하는 몇가지 일반적인 프로그래밍 과제를 설명한다. 예제들은 Objective-C와 코코아를 이용해서 자성되어있으나, 코어 비디오는 카본 프로그램에서도 사용할 수 있다.
대부분의 경우, 개별 프레임에 접근하기 위해 디스플레이 링크를 이용하려 할 것이다. 당신의 프로그램이 실제 비디오 프레임을 만들어 내는 데 관여한다면 (예를 들어, 비디오 압축기나 애니메이션 이미지를 만든다면), 당신의 프레임 데이타를 보관하기 위해 코어 비디오 버퍼를 사용하는 것을 고려해 볼 것이다.
[편집] 디스플레이 링크를 이용해서 프레임을 가져오기
가장 일반적인 코어 비디오 과제는 무압축 비디오 프레임을 얻어내기 위해 디스플레이 링크를 사용하는 것이다. 그러면 당신의 어플리케이션은 그 프레임들을 디스플레이로 보내기 전에 마음껏 수정할 수 있다.
간단히 설명하기 위해, 이 섹션의 모든 메소드 호출은 NSOpenGLView 클래스의 서브클래스인 MyVideoView 오브젝트에서 일어난다고 가정하자.
코드 2-1 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
중요 : OpenGL은 쓰레드에 안전하지 않다. 어플리케이션에서 OpenGL 호출을 할 때는 예제에서 처럼 NSRecursiveLock 오브젝트를 인스턴스화하고 lock 메소드를 발생시켜서 쓰레드를 잠궈야 한다.
NSOpenGLView 클래스 사용에 대한 보다 자세한 정보는 CocoaOpenGL 예제 프로젝트를 참고하라.
디스플레이 링크 설정하기
디스플레이 링크를 설정하는 것은 다음의 단계들과 관계가 있다
- 디스플레이 링크 쓰레드 만들기
- 링크를 특정 디스플레이에 연결하기
- 디스플레이 출력 콜백을 등록하기
- 디스플레이 링크 쓰레드를 시작하기
Listing 2-2의 awakeFromNib은 디스플레이 링크를 구현하는 법을 보여준다.
Listing 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를 통해 사용자의 주 디스플레이(메뉴바가 있는 것)를 간단히 리턴받을 수 있다.
- 지정된 디스플레이를 위한 디스플레이 링크를 만든다. 필요하다면 CVDisplayLinkCreateWithActiveCGDisplays 함수를 호출해서 현재 활성화 상태인 모든 디스플레이와 함께 할 수 있는 디스플레이 링크를 만들 수도 있다. 이 후 반드시 CVDisplayLinkSetCurretnCGDisplay를 호출해 디스플레이 링크를 위한 특정 디스플레이를 지정해야 한다.
만약 사용자가 비디오를 포함하는 윈도우를 다른 모니터로 옮겼다면, 디스플레이 링크를 적당히 업데이트 해줘야 한다. 코코아에서는 다음과 같이 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 클래스에 속해 있다면, 사용자 데이타는 MyVideoView 인스턴스를 참조한다.
비디오 프레임 처리가 준비되면, CVDisplayLinkStart를 호출하여 디스플레이 링크 쓰레드를 활성화 한다. 이 쓰레드는 어플리케이션 쓰레드와는 독립적으로 운영된다. 어플리케이션이 종료될 때나 다른 이유로 비디오 디스플레이를 중단하려 할때는 CVDisplayLinkStop을 호출하여 쓰레드를 정지시킬 수 있다.
비디오 소스를 초기화 하기
처리를 시작하기 전에, 프레임을 공급하기 위해 비디오 소스를 먼저 설정해야 한다. 비디오 소스는 OpenGL 텍스쳐로 비압축 비디오 데이터를 공급할 수 있는 것이면 어떤 것이든 상관없다. 예를들어, 퀵타임, OpenGL 혹은 당신만의 독점적인 비디오 프레임 생성기여도 된다.
이 경우, 생성된 비디오를 디스플레이 하기 위해서는 OpenGL 컨텍스트를 만들어야 한다. 당신의 비디오 소스에게 이곳에 비디오가 디스플레이 되길 원한다고 알려줘야 한다.
Listing 2-3은 퀵타임 무비를 당신의 비디오 소스로 설정하는 메소드를 보여준다
Listing 2-3 퀵타임 비디오 소스 초기화 하기
- (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;
}
코드의 동작은 다음과 같다:
- 이 메소드는 퀵타임 무비의 파일 경로를 그 입력 매개변수로 받는다.
- 무비의 속성 배열을 설정한다. 속성들은
- 무비에 대한 파일 경로
- 새로운 무비가 활성화 될지 말지 ( 이경우는 활성화)
- 이 무비에 연결될 비주얼 컨텍스트. qtVisualContext 변수는 이 메소드의 클래스의 인스턴스 변수이다.
속성들은 나중에 NewMovieFromProperties함수로 넘겨진다
- OpenGL 텍스쳐 컨텍스트를 만든다. 이것은 OpenGL 텍스쳐가 그려질 추상적인 목적지이다. 퀵타임 함수 QTOpenGLTextureContextCreate는 CGLContext와 CGLPixelFormat 오브젝트를 넘기도록 요청한다. 코코아에서, OpenGL을 초기화 할때 만들어지는 NSOpenGLContext와 NSOpenGLPixelFormat오브젝트 로부터 얻을 수 있다. 카본에서는, AGL함수인 aglGetCGLContext와 aglGetCGLPixelFormat을 이용해 아랫단의 컨텍스트와 픽셀 포맷을 AGLContext와 AGLPixelFormat오브젝트로부터 얻을 수 있다.
인스턴스 변수 qtVisualContext에 저장된 이 컨텍스트는, NewMovieFromProperties로 넘겨져 퀵타임 무비가 그려질 비주얼 컨텍스트가 된다.
- 무비를 만든다. Mac OS X v10.4이상과 퀵타임 7.0 이상에서 사용가능한 퀵타임 함수 NewMovieFromProperties는 무비를 인스턴스화 하는데 추천하는 방법이다.
코코아를 사용한다면, QTKit 메소드인 movieFromFile을 대신 사용해도 된다.
어떤 이유에서 무비를 만든 후 비주얼 컨텍스트를 설정하거나 변경해야 한다면, 퀵타임 함수인 SetMovieVisualContext를 호출하면 된다.
- 무비에 다양한 초기화를 수행한다. 이 섹션은 무비를 시작부분부터, 보통의 프레임 율과 계속된 루프 조건으로 재생시키기 위해 가장 많이 반복사용하는 코드일 것이다. 코드는 사용가능한 트랙을 반복하며 비디오가 아닌 트랙은 꺼버린다.
디스플레이 링크 아웃풋 콜백 함수 구현
디스플레이 링크가 동작중일때, 프레임을 준비해야 할 때가 되면 주기적으로 어플리케이션으로 콜백을 보낸다. 당신의 콜백 함수는 OpenGL 텍스쳐와 같은 지정된 비디오 소스로부터 프레임을 얻어와야 하며 그것을 스크린에 출력한다.
오브젝트 지향의 프로그래밍을 한다면, Listing 2-4와 Listing 2-5와 같은 메소드를 당신의 콜백이 호출하도록 할 것이다.
Listing 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;
}
콜백 함수는 MyVideoView 클래스안에 구현된 displayFrame 메소드를 발생시킬 뿐이다. 이 클래스의 인스턴스는 displayLinkContext 매개변수를 통해 콜백으로 넘겨진다.( 프레임을 디스플레이하는 MyVideoView 클래스는 Listing 2-1 처럼 NSOpenGLView의 서브클래스여야 한다.)
Listing 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 텍스쳐로 얻을 수 있다. Listing 2-6은 getFrameForTime 메소드를 구현하여 퀵타임으로부터 비디오 프레임을 얻어오는 경우를 설명하고 있다. 이 예제는 메소드가 MyVideoView 클래스의 일부로 가정한다.
Listing 2-6 퀵타임으로부터 프레임 얻어오기
- (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 텍스쳐로 얻어온다. 퀵타임 함수 QTVisualContextCopyIamgeTime은 퀵타임으로부터 프레임을 어떤 코어 비디오 버퍼 타입으로든지 가져오게 한다.
- 현재의 텍스쳐(currentFrame 인스턴스 변수에 저장되어 있는)를 릴리즈하고 새로 가져온 텍스쳐가 그것을 대체하게 한다.사용이 끝났으면 OpenGL 텍스쳐를 릴리즈해서 비디오 메모리의 사용량을 최소화해야 한다.
- 텍스쳐의 clean aperture 좌표계를 얻는다. 대붑분의 경우, 렌더링 할 텍스쳐의 바운드이다.
[편집] 프레임 수정하기
비디오 소스로부터 프레임을 받은 후에는, 무엇을 할지는 당신에게 달렸다. 프레임은 OpenGL 텍스쳐로 주어졌으므로, 어떤 OpenGL 콜을 사용해서든 그것을 수정할 수 있다. Listing 2-7은 표준 NSView의 drawRect 메소드를 오버라이딩해서 뷰 바운드안에 그려넣도록 OpenGL을 설정하는 법을 보여준다.
Listing 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 컨텍스트의 사이즈를 업데이트 한다.
- 뷰가 visible하다면 OpenGL 컨텍스트를 부의 새로운 바운드로 매핑한다. 아니라면, 컨텍스트를 효과적으로 보이지 않게 매핑한다.
- 기존의 프레임이 아니라면 프레임을 다시 가져온다. 뷰의 크기가 바뀌어 drawRect이 호출되는 경우 이런 상황이 발생할 수 있다.
- 현재 프레임을 OpenGL컨텍스트에 그린다. renderCurrentFrame은 당신의 커스텀 프레임 코드를 지니고 있는 메소드이다.
- OpenGL 렌더러로 그린 그림을 플러시 한다. 프레임은 적당한 시각에 화면에 디스플레이 된다.
- 모든 OpenGL 호출이 완료되면 쓰레드를 푼다.
renderCurrentFrame메소드는 당신의 어플리케이션이 프레임에 적용하려하는 커스텀 처리를 가지고 있다. Listing 2-8은 이 메소드를 구현하는 간단한 예를 보여준다. 이 예는 코어 이미지를 이용하여 OpenGL 컨텍스트에 프레임을 그리는 것이다.
Listing 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컨텍스트와 동일한 드로잉 공간을 참조하는 코어 이미지 컨텍스트를 만들어야 한다. 그래야만 코어 이미지 API를 이용해 그 공간에 그러넣을 수 있고 OpenGL을 이용해 디스플레이 할 수 있다. 예를 들어, OpenGL컨텍스트를 만든 후에는 Listing 2-3에 다음과 같은 코드를 더해야 한다:
/* 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를 드로잉 메소드가 끝날때 마다 한번씩 호출해야 한다.
끄
[편집] 코어 이미지 필터를 코어 비디오와 함께 사용하기
비디오에 필터 효과를 적용하길 원한다면, 이미지 프로세싱을 직접 구현하려 하는 것보다는 간편하게 코어 이미지 필터를 적용하는 것이 쉽다. 이를 위해서는, 프레임을 코어 이미지 이미지로 받아와야 한다.
filterWithName:이라는 코어 이미지 CIFilter 메소드를 이용해 코어 이미지 필터를 로드할 수 있다.
effectFilter = [[CIFilter filterWithName:@"CILineScreen"] retain]; [effectFilter setDefaults];
이 예제는 표준 코어 이미지 라인 스크린 필터를 로드하는 것이지만, 각자 자신의 어플리케이션에 적당한 것을 사용하도록 한다.
필터를 로드한 후, 드로잉 메소드 내에서 필터를 이용해 이미지를 처리한다. Listing 2-9는 코어 이미지 필터를 적용하는 법을 보여준다. 이 예제는 코어 이미지 컨텍스트에 그려지기 전에 입력 이미지를 필터링 한다는 것만 빼면 Listing 2-8과 동일하다.
Listing 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필터를 프레임에 적용하기 위해 설정한다.
- 지정된 필터와 함께 이미지를 그린다
코어 이미지 이미지는 수정이 불가하다는 것을 염두에 둬야 한다: 프레임을 가져올 때마다, 새로운 이미지를 만들어야 한다.
코어 이미지 필터에 대한 보다 자세한 내용은, 코어 이미지 프로그래밍 가이드를 보라.
| 번역자 | 사용자:LingoStar |
| 원본문서링크 | http://developer.apple.com/documentation/GraphicsImaging/Conceptual/CoreVideo/CVProg_Intro/chapter_1_section_1.html (Last Updated - 2007-04-03) |






