Creating a QTKit Stop or Still Motion Application
OSXDEV
QTKit Capture Programming Guide로 이동
이 가이드의 이전 챕터들에 있는 예제들을 따라 당신의 QTKit 캡처 플러이어 어플리케이션을 만들고 오디오와 DV 카메라 지원까지 기능을 확장해 왔으므로, 다른 코딩 과제를 수행할 준비가 되었다. 여기서의 목표는, 다시 한번 QTKit 캡처 API에 대한 당신의 지식을 확장시키는 것이다.
API가 정확한 프레임의, 리얼타임 모션 캡처를 지원하기 때문에, 방대한 범위의 사용과 응용성을 알게 되었을 것이다. 그 중 하나는 스틸 또는 스톱 모션 애니메이션이다. 이것은 1959년 월트 디즈니의 클래식 필름인 '노아의 방주'에서 처음 선보인 유명한 애니메이션 기법이다. 기본적으로, 이것은 무언가 동작중인 것 처럼 보이는 정지된 오브젝트를 만들고 각각의 프레임들을 모아서 영화로 만드는 것이다. 이것은 오브젝트를 애니메이팅하고 스크린 위에서 시각적인 생명을 넣어주는 방법이다. 스톱 혹은 스틸 모션 애니메이터들은 월트 디즈니 이후 셀수 없이 많은 영화, TV광고 그리고 TV쇼에 이 기법- 정지 오브젝트를 정상 속도로 재생했을 때 움직이는 것 처럼 보이는-을 도입했다.
이 챕터에서 아웃라인된 단계들을 따라가면, 라이브로 공급되는 비디오를 캡처하고, 엄청난 정확성을 가지고 한번에 한 프레임을 잡아내고, 그 프레임들의 아웃풋을 큅타임 무비로 레코드하는 간단한 스틸 모션 어플리케이션을 만들 수 있을 것이다. 이것을 이전 챕터에서 해왔던 것처럼 Xcode 3와 InterfaceBuilder 3에서 100줄 이하의 Objective-C 코드로 만들어 낼 수 있다.
스틸 모션 캡처 어플리케이션을 만드는 데 있어서, 다음 3가지 QTKit 클래스들을 이용해 작업하게 될 것이다.
- QTCaptureSession. 미디어 스트림을 캡처하는 주 인터페이스
- QTCaptureDecompressedVideoOutput. 캡처되는 비디오로부터 비압축 프레임을 처리하는 데 사용할 수 있는 QTCaptureSession 오브젝트의 아웃풋 목적지. 이 클래스에서 제공하는 메소드들을 사용하여, 고품질의 비디오 처리에 적합한 비압축 비디오 프레임을 만들 수 있다.
- QTCaptureDeviceInput. 카메라나 마이크와 같이 미디어 디바이스에 대한 입력 소스.
[편집] Set Up Your Project
가이드에서 이전 챕터의 코드 예제를 건너뛰고 이 챕터로 왔다면, "First Step"으로 시작하는 섹션으로 되돌아 가는 게 좋을 것이다. 그 섹션들은 Xcode와 인터페이스 빌더를 이용하는 법에 대한 배경을 제공한다.
이전 챕터에서 설명한 대로, Mac OS X v10.5와 다음 아이템들이 시스템에 설치되어 있어야 한다.
- Xcode 3와 인터페이스 빌더 3. 프로젝트를 빌드하기 위해 Xcode2.2를 이용할 수도 있지만, 최대한의 프로그래밍 기능을 이용하려면, Xcode 3이 필요할 것이다.
한, 인터페이스 빌더 3은 당신의 어플리케이션의 사용자 인터페이스를 만드는 데 새로운 패러다임을 제공할 것이다. 팔레트는 더 이상 제공되지 않고; 훨씬 더 쉽고 효율적으로 사용자 인터페이스의 구성요소들을 연결할 수 있도록 디자인된 컨트롤들의 라이브러리를 사용하게 될 것이다. 결국, 당신은 풍부하고 해로운 컨트롤들의 라이브러리를 이용해서 보다 빠르게 어플리케이션을 만들 수 있을 것이다.
- QuickTime Kit 프레임워크. Mac OS X v10.5의 System/Library/Frameworks 디렉토리에 QTKit.framework라는 이름으로 있다.
- iSignt 카메라가 컴퓨터에 연결되어 있어야 한다.
| 중요: 여기와 다음 챕터에서 논의하는 내용에 대한 샘플코드는 Sample Code: QuickTime Cocoa에서 StillMotion라는 이름으로 다운로드 받을 수 있다. |
| 노트: StillMotion 샘플코드는, 이 챕터에서 설명된 바와 같이, QTMediaTypeMuxed 타입인 DV 카메라는 지원하지 않고, QTMeidaTypeVideo만 지원할 것이다. 이 프로그래밍 가이드의 다음 챕터인 "“Adding Audio Input and DV Camera Support,”에서 DV카메라를 이용하고 QTMediaTypeMuxed 타입을 사용하는 법을 설명할 것이다. |
[편집] Prototype the Still Motion Capture Application
이전의 "캡처 플레이어 프로토타입"에서 한 것처럼, QTKit 스틸 모션 어플리케이션의 러프 스케치를 만드는 것으로 시작한다. 어플리케이션에 어떤 디자인 요소들을 넣을 것인지를 고민해 보자. 인터페이스 빌더로 넘어가 프로토 타입을 거기에서 만드는 것보다는, 러프 스케치 안에 그 요소들을 시각화 해야 할 것이다. 그림 4-1 처럼.
그림 4-1 QTKit 스틸 모션 캡처 어플리케이션의 프로토타입 스케치
이 디자인 프로토 타입을 이용해, 세가지 간단한 오브젝트 - 캡처 뷰, 퀵타임 무비 뷰 그리고 프레임을 더하기 위한 하나의 버튼-으로 출발할 수 있다. 그것들은 당신의 어플리케이션의 빌딩 블럭이 될 것이다. 차후에 보다 복잡한 디자인을 추가할 수 있다. 스케치가 끝났으면, 그것들을 인터페이스 빌더에서 어떻게 연결시킬지와 Xcode 프로젝트에서 각각의 동작을 위해 필요한 코드를 고민하기 시작해야 한다.
[편집] Create the Project Using Xcode 3
프로젝트를 만들기 위해, 다음 단계를 따른다
1. Xcode 3(그림 4-2)를 런치하고 File > New Project를 선택한다.
그림 4-2 Xcode 3 아이콘
2. 새로운 프로젝트 윈도우가 나타나면, Cocoa Document-based 어플리케이션을 선택한다.
3. 프로젝트의 이름을 StillMotion으로 주고 Xcode 어플리케이션이 프로젝트 폴더를 만들기를 원하는 위치를 찾는다. 이제 그림 4-3과 같이 Xcode 프로젝트 윈도우가 나타난다.
그림 4-3 StillMotion Xcdoe 프로젝트 윈도우
4. 다음으로, StillMotion 프로젝트에 QuickTime Kit 프레임워크를 더한다. 프레임워크는 System/Library/Frameworks 디렉토리에 있다. 반드시 해야하는 단계이지만, 잊기 쉬운 단계이기도 하다. QuickTime 프레임워크가 아니라 QuickTime Kit 프레임워크를 추가해야 한다는 것을 명심하라. Project > Add to Project를 선택한다.
5. QTKit.framework을 선택하고, Add To Target 윈도우가 나타나면 Add 버튼을 눌러 프로젝트에 더한다.
6. Quartz Core 프레임워크도 프로젝트에 더한다. 이것은 System/Library/Frameworks 디렉토리에 있다. Project > Add to Project를 선택한다. QuartzCore.framework을 선택하고, Add To Target 윈도우가 나타나면 Add를 클릭한다.
| 중요:
여기까지가 프로젝트의 첫번째 시퀀스를 완성하는 것이다. 다음 시퀀스에서, 인터페이스 빌더에서 작업하기 전에 Xcode로 넘어가서 액션과 아웃렛을 정의할 것이다. 이것은 인터페이스 빌더 3과 그 이전 버전에서 어플리케이션을 만드는 패러다임의 변화와 관련이 있다. QTKit 스틸 모션 어플리케이션을 이미 러프하게 나마 명확히 정의된 데이터 모델과 함께 프로토 타입했기 때문에, 어떤 액션과 아웃렛이 구현되어야 하는지를 결정할 수 있다. 이 경우, NSView의 서브클래스인 QTCaptureView 오브젝트와 캡처한 프레임을 디스플레이 하는 QTMovieView 오브젝트를 가지고 있으며 캡처한 미디어 컨텐트를 레코드하고 퀵타임 무비 출력에 하나의 프레임을 더하는 버튼이 하나 있다. |
[편집] QTKit 헤더를 임포트 하고 Implementation File을 셋업하기
1. Xcode 프로젝트에서 MyDocument.h 선언 파일을 더블클릭해서 연다. 파일에서, #import <Cocoa/Cocoa.h> 문구를 지우고 #imoprt <QTKit/QTKit.h>로 대체한다.
2. 프로젝트에서 MyDocument.m 구현 파일을 더블클릭해서 연다. 다음 라인의 코드를 제외한 내용들을 모두 지운다.
#import "MyDocument.h"
@implementation MyDocument
- (NSString *)windowNibName
{
return @"MyDocument";
}
@end
[편집] 액션과 아웃렛을 결정하기
1. 이제 아웃렛과 액션을 더한다. MyDocument.h 파일에서,다음과 같이 mCaptureView와 mMovieView 인스턴스 변수를 더한다.
IBOutlet QTCaptureView *mCaptureView; IBOutlet QTMovieView *mMovieView;
2. 다음 액션 메소드도 더한다.
- (IBAction)addFrame:(id)sender;
3. MyDocument.m 파일을 열고 동일한 액션 메소드를 더한 후, 차후 이 액션의 구현을 더하기 위해 중괄호를 더한다
- (IBAction)addFrame:(id)sender
{
}
이것으로 프로젝트의 두번재 단계를 마쳤다. 이제 기어를 변경해서 인터페이스 빌더 3에서 프로젝트의 사용자 인터페이스를 만들자.
[편집] Create the User Interface Using Interface Builder 3
[편집] Prepare to Capture Single Frame Video
[편집] Complete the Project Nib File in Xcode
프로젝트 nib파일을 완성하기 위해서, 캡처 세션을 가리키는 인스턴스 변수와 장치 입력과 압축해제된 비디오 출력 오브젝트를 정의할 필요가 있다.
1. Xcode 프로젝트의 인터페이스 정의에 인스턴스 변수를 더해야 한다. MyDocument.h 선언 파일에 다음 코드를 더한다:
@interface MyDocument : NSDocument
{
QTMovie *mMovie
QTCaptureSession *mCaptureSession;
QTCaptureDeviceInput *mCaptureDeviceInput;
QTCaptureDecompressedVideoOutput *mCaptureDecompressedVideoOutput;
}
mCaptureSession 인스턴스 변수가 QTCaptureSession 오브젝트를 가리키는 것 처럼, mMovie 인스턴스 변수는 QTMovie 오브젝트를 가리킨다. 유사하게, *mCaptureDeviceInput 인스턴스 변수는 QTCaptureDeviceInput 오브젝트르를 가리키고, 그 다음 줄의 mCaptureDecompressedVideoOutput 인스턴스 변수는 QTCaptureDecompressedVideoOutput 오브젝트를 가리킨다.
2. 이 파일에서 선언해야 할 인스턴스 변수가 하나 더 있다: mCurrentImageBuffer이다. 이 인스턴스 변수는 가장 최근에 잡아낸 이미지를 CVIamgeBufferRef에 저장한다. 마지막 선언문 뒤에 다음 코드를 더한다:
CVImageBufferRef mCurrentImageBuffer;
3. 이제 MyDocument.h 파일에 더해야 할 코드를 모두 마쳤다. 이제 MyDocument.m을 열어서 다음에 나오는 코드 블럭을 더할 준비를 하자. 코드에는 이해를 돕기 위한 코멘트가 붙어있다.
코드를 만들어 나가는 단계를 따라 밟아 가는 데 있어서, 특별히 정해진 규칙이 있는 것은 아니다. 그것들을 당신의 프로젝트에서 구현하고 싶은 특정한 과제로 생각하라.
a. initToWriteableData:메소드를 이용해 메모리에 있는 mutable 데이터에 기록하는 빈 무비를 만든다. b. 잡아내길 원하는 raw 프레임을 출력하는 캡처 세션을 설정한다. c. 비디오 장치를 찾고 해당 장치에 대한 장치 입력을 캡처 세션에 더한다. d. 잡아낸 raw프레임을 리턴하는 decompressed 비디오 출력을 세션에 더하고 세션으로부터의 비디오를 도큐멘트 윈도우에서 프리뷰 한다. e. MyRecorder 샘플코드에서 사용한 적 있는 startRunning 메소드를 이용해 세션을 시작한다. f. QTCaptureDecompressedVideoOutpur 오브젝트가 프레임을 수신할 때마다 델리게이트 메소드를 호출한다. g. 최신 프레임을 저장한다. 델리게이트 메소드가 메인 쓰레드에서 호출되는 것이 아니기 때문에 이 작업은 @synchronized 블럭 안에서 한다. h. 가장 최근의 프레임을 가져온다. 가장 최근의 프레임을 설정하는 델리게이트 메소드가 메인 쓰레드에서 호출되는 것이 아니기 때문에 이 작업은 @synchronized 블럭 안에서 한다. i. NSImage를 만들고 무비에 더한다.
4. 위의 단계들을 따라가면서, 이 코드 블럭을 MyDocument.m파일에 더한다.
- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
NSError *error = nil;
[super windowControllerDidLoadNib:aController];
[[aController window] setDelegate:self];
if (!mMovie) {
// 메모리에 있는 mutable 데이터에 기록하는 빈 무비를 만든다.
mMovie = [[QTMovie alloc] initToWritableData:[NSMutableData data] error:&error];
if (!mMovie) {
[[NSAlert alertWithError:error] runModal];
return;
}
}
[mMovieView setMovie:mMovie];
if (!mCaptureSession) {
//raw 프레임을 출력하는 캡처 세션을 설정한다.
BOOL success;
mCaptureSession = [[QTCaptureSession alloc] init];
// 비디오 장치를 찾는다
QTCaptureDevice *device = [QTCaptureDevice defaultInputDeviceWithMediaType:QTMediaTypeVideo];
success = [device open:&error];
if (!success) {
[[NSAlert alertWithError:error] runModal];
return;
}
//해당 장치의 장치 입력을 캡처 세션에 더한다
mCaptureDeviceInput = [[QTCaptureDeviceInput alloc] initWithDevice:device];
success = [mCaptureSession addInput:mCaptureDeviceInput error:&error];
if (!success) {
[[NSAlert alertWithError:error] runModal];
return;
}
//잡아낸 raw프레임을 리턴하는 decompressed 비디오 출력을 세션에 더한다
mCaptureDecompressedVideoOutput = [[QTCaptureDecompressedVideoOutput alloc] init];
[mCaptureDecompressedVideoOutput setDelegate:self];
success = [mCaptureSession addOutput:mCaptureDecompressedVideoOutput error:&error];
if (!success) {
[[NSAlert alertWithError:error] runModal];
return;
}
// 세션으로부터의 비디오를 도큐멘트 윈도우에서 프리뷰 한다.
[mCaptureView setCaptureSession:mCaptureSession];
// 세션을 시작한다.
[mCaptureSession startRunning];
}
}
5. 윈도우 닫기 notifications에 따라 장치 입력을 제어하고 캡처 세션을 종료하기 위해 다음 을 더한다.
- (void)windowWillClose:(NSNotification *)notification
{
[mCaptureSession stopRunning];
QTCaptureDevice *device = [mCaptureDeviceInput device];
if ([device isOpen])
[device close];
}
6. 캡처 오브젝트의 메모리 deallocation을 위해 다음 코드 블럭을 넣는다.
- (void)dealloc
{
[mMovie release];
[mCaptureSession release];
[mCaptureDeviceInput release];
[mCaptureDecompressedVideoOutput release];
[super dealloc];
}
7.레코드한 미디어의 출력 목적지르르 지정하기 위해 다음 코드를 더한다. 여기에서는 수정가능한 퀵타임 무비이다.
- (BOOL)readFromURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError
{
QTMovie *newMovie = [[QTMovie alloc] initWithURL:absoluteURL error:outError];
if (newMovie) {
[newMovie setAttribute:[NSNumber numberWithBool:YES] forKey:QTMovieEditableAttribute];
[mMovie release];
mMovie = newMovie;
}
return (newMovie != nil);
}
- (BOOL)writeToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError
{
return [mMovie writeToFile:[absoluteURL path] withAttributes:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:QTMovieFlatten] error:outError];
}
- (BOOL)writeToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError
{
return [mMovie writeToFile:[absoluteURL path] withAttributes:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:QTMovieFlatten] error:outError];
}
8.QTCaptureDecompressedVideoOutput 오브젝트가 프레임을 수신할 때마다 델리케이트 메소드를 호출할 수 있도록 다음 코드를 더한다:
- (void)captureOutput:(QTCaptureOutput *)captureOutput didOutputVideoFrame:(CVImageBufferRef)videoFrame withSampleBuffer:(QTSampleBuffer *)sampleBuffer fromConnection:(QTCaptureConnection *)connection
{
// 가장 최근의 프레임을 저장한다
// 이 델리게이트 메소드가 메인 쓰레드에서 호출되는 것이 아니기 때문에 이 작업은 @synchronized 블럭 안에서 한다.
CVImageBufferRef imageBufferToRelease;
CVBufferRetain(videoFrame);
@synchronized (self) {
imageBufferToRelease = mCurrentImageBuffer;
mCurrentImageBuffer = videoFrame;
}
CVBufferRelease(imageBufferToRelease);
}
9. 이제 addFrame: 액션 메소드를 설정해서 잡아낸 가장 최근의 프레임을 구한다. 가장 최근의 프레임을 설정하는 델리게이트 메소드가 메인 쓰레드에서 호출되는 것이 아니기 때문에 이 작업은 @synchronized 블럭 안에서 한다. CVImageBufferRef 오브젝트를 NSImage로 wrapping 했다는 것을 명심하라. NSIamge를 만들고 나서 그것을 무비에 더한다.
- (IBAction)addFrame:(id)sender
{
CVImageBufferRef imageBuffer;
@synchronized (self) {
imageBuffer = CVBufferRetain(mCurrentImageBuffer);
}
if (imageBuffer) {
//NSImage를 만들고 무비에 더한다
NSCIImageRep *imageRep = [NSCIImageRep imageRepWithCIImage:[CIImage imageWithCVImageBuffer:imageBuffer]];
NSImage *image = [[[NSImage alloc] initWithSize:[imageRep size]] autorelease];
[image addRepresentation:imageRep];
CVBufferRelease(imageBuffer);
[mMovie addImage:image forDuration:QTMakeTime(1, 10) withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
@"jpeg", QTAddImageCodecType, nil]];
[mMovie setCurrentTime:[mMovie duration]];
[mMovieView setNeedsDisplay:YES];
[self updateChangeCount:NSChangeDone];
}
}






