iOS와 멀티미디어 프로그래밍

지난 2월, 많은 사람들이 관심을 갖고 있는 아이패드2와 iOS 4.3 버전, Xcode 4 등이 발표됐다. 특히 아이패드2의 경우 다른 애플 제품들이 발표됐을 때처럼 스티브잡스가 제품을 직접 들고 나와서 훌륭한 프리젠테이션으로 소개하기도 했다. 이번에도 역시 제품 출시 전 여러 가지 루머들이 있었지만 이들 소문이 무색할 정도로 뛰어난 사양의 제품이었다.

안경훈 linuxgood@gmail.com | 리눅스와 맥OS에 관심이 많으며, 모바일 디바이스에서 동작되는 사용자 중심의 프로그램을 개발하는 것에 흥미를 갖고 있다. 현재 삼성SDS에서 근무 중이다.

iOS에서 오디오와 비디오를 활용한 멀티미디어 프로그램을 작성하는 방법을 알아보자. 일반적으로 iOS에서 사용되는 오디오/비디오 리소스들은 멀티미디어 프레임워크를 사용해 프로그램 된다. 이를 통해 다음과 같은 기능들이 포함된 애플리케이션을 만들 수 있다.

- 오디오 레코딩과 플레이, 스트리밍 데이터를 활용한 AV 플레이
- 게임 등에 등장하는 간단한 사운드 플레이
- 실시간 음성 녹음
- 아이패드 라이브러리에서의 사운드 플레이
- 비디오 플레이와 레코딩의 지원
- 간단한 비디오 플레이와 레코딩의 지원

오디오 기능 사용
iOS 4.0이 나올 무렵 아이폰4와 아이패드 등의 보급이 활성화됐다. iOS에서 사용되는 오디오 프레임워크의 사용법을 살펴보자.
멀티미디어 데이터를 재생하기 위해 사용되는 프레임워크로는 다음과 같은 것들이 있다.

● MediaPlayer.framework
음악, 오디오 북, 오디오 팟 캐스트 등과 같은 리소스들을 플레이하기 위해 사용된다.

● AVFoundation.framework
간단히 음악을 플레이하거나 녹음을 할 때 사용되는 프레임워크로서 AVAnimation이나 캡처된 아이템 등을 플레이할 때도 자주 사용된다.

● AudioToolbox.framework
패킷을 통해 전달되는 오디오나 오디오 포맷을 변경할 때, 오디오 큐를 활용해 오디오 데이터 등을 변경할 때 사용된다.

● AudioUnit.framework
컴포넌트 입력이나 출력을 위해 오디오 데이터를 가공할 때 사용된다.

● OpenAL.framework
게임이나 특별한 목적으로 사용되는 오디오 데이터를 가공할 때 사용된다. iOS는 현재 OpenAL 1.1(http://openal.org/) 버전을 지원한다.

오디오 프레임워크 사용
오디오 프레임워크를 사용하기 위해서는 다른 프레임워크를 사용할 때와 마찬가지로 #import! 명령으로 해당 프레임워크를 코드에 포함하면 된다. 다음과 같은 형식으로 코드에 포함한다.
#import! <AVFoundation/AVFoundation.h>

iOS에서 지원되는 오디오 코덱
이제 iOS에서 지원되는 오디오 코덱을 살펴보자.

● 플레이를 위한 코덱
오디오 데이터와 같은 것이 iOS에서 사용될 때에는 기본적으로 압축을 하게 되는데 이러한 압축을 실시간으로 해제해 소리가 나도록 하는 것이 코덱이다. iOS에서 코덱을 사용해 플레이 될 수 있는 오디오 포맷에는 <표 1>과 같은 것들이 있다. 일반적으로 자주 사용되는 코덱은 모두 지원한다고 생각하면 된다. <표 1>에서 볼 수 있는 하드웨어 코덱과 소프트웨어 코덱들은 동시에 같은 타입의 디코딩을 지원할 수는 없다. 즉, 하드웨어로 오디오 데이터들을 디코딩하는 중에 다른 오디오 소스를 디코딩해야 할 일이 생기면 소프트웨어 코덱을 사용해야 한다는 말이다. 반대로 소프트웨어 코덱을 사용해 디코딩할 때도 마찬가지다.

<표 1> 지원되는 하드웨어, 소프트웨어 코덱

<표 2> 녹음을 위한 하드웨어, 소프트웨어 코덱

● 녹음을 위한 코덱
iOS에서 지원되는 녹음을 위한 오디오 포맷에는 <표 2>와 같은 것들이 있다. 하드웨어를 사용한 녹음은 AAC 타입으로만 사용될 수 있다. 다른 타입으로 녹음하려면 소프트웨어를 기반으로 인코딩해야 한다.

환경에 따른 오디오 세션의 활용
오디오가 플레이 될 경우 우리는 여러 가지 예외 상황을 맞을 때가 있다. 오디오를 순간적으로 음소거(Mute)시키거나 사용자가 정지시켰을 때, 헤드폰 잭을 꽂았을 경우와 뺀 경우, 스크린 락을 걸었을 때, 시계의 알람을 울릴 때 등 각종 오디오를 재시작했을 경우에 오디오를 조절하는 API들이 있는데 이것을 오디오 세션 API라고 한다. 오디오 세션 API 중 중요한 클래스들을 살펴보자. 

<화면 1>과 같이 디자인된 애플리케이션이 있다. (http:// developer.apple.com/library/ios/#samplecode/avTouch/Listings/Classes_avTouchController_mm.html)에서 가져온 ‘avTouch’라는 샘플코드로 AVAudioSession 클래스를 사용하는 좋은 예제다.

<화면 1> avTouchController 프로그램

<화면 1>에서 보는 바와 같이 각각의 기능들이 동작하면서 오디오 세션의 조절 기능을 하게 된다. sample.m4a라는 오디오 파일을 사용자의 조작에 의해 플레이하는 역할이다. 사용되는 오브젝트들을 중심으로 알아보자.

1.  다음과 같은 헤더 파일을 불러온다.

<UIKit/UIKit.h>
<AudioToolbox/AudioToolbox.h>
<AVFoundation/AVFoundation.h>

2. 오디오 플레이를 위한 델리게이트를 추가하고 각종 버튼들을 위한 코드를 추가한다. 추가돼야 할 것은 AVAudioPlayer Delegate와 다음에 열거된 오브젝트들이다. 각각의 버튼 이름들이 화면에서 보이는 기능을 의미한다.

...
IBOutlet UIButton *playButton
IBOutlet UIButton *ffwButton
IBOutlet UIButton *rewButton
IBOutlet UISlider *volumeSlider
IBOutlet UISlider *progressBar
IBOutlet UILabel *currentTime
IBOutlet UILabel *duration
...
AVAudioPlayer *player
UIImage *playBtnBG
UIImage *pauseBtnBG
NSTimer *updateTimer
NSTimer *rewTimer
NSTimer *ffwTimer
...

음악이 연주되면서 LevelMeter 등의 애니메이션이 동작하게 되는데 그 자세한 설명을 생략하겠다(시뮬레이터에서는 레벨메터 애니메이션이 동작하지 않을 수도 있다). 또한 재생을 멈췄다가 다시 플레이 할 경우에 사용되는 타이머도 있다(NSTimer).

3.  플레이와 앞으로 가기, 볼륨 슬라이더 등에 대한 액션을 처리하기 위해 다음의 함수들을 만든다. 앞으로 감거나 뒤로 감는 동작을 위해 각각의 버튼들에 대해 press와 release 함수를 만들었다. 볼륨 슬라이드와 프로그래스 바를 위한 함수도 있다. 백그라운드로 동작하거나 포그라운드로 동작할 때의 동작을 등록하기 위해 Notification 함수도 만든다.

(IBAction)playButtonPressed:(UIButton*)sender;
(IBAction)rewButtonPressed:(UIButton*)sender;
(IBAction)rewButtonReleased:(UIButton*)sender;
(IBAction)ffwButtonPressed:(UIButton*)sender;
(IBAction)ffwButtonReleased:(UIButton*)sender;
(IBAction)volumeSliderMoved:(UISlider*)sender;
(IBAction)progressSliderMoved:(UISlider*)sender;

(void)registerForBackgroundNotifications;

4. 각 함수에 해당되는 동작에 관한 코드를 작성한다. 플레이 버튼이 눌렸을 경우 AVAudioPlayer의 객체인 player의 playing 플래그를 사용해 현재 오디오가 플레이 중인지의 여부를 판단, 플레이 중이라면 pausePalybackForPlayer라는 함수를 동작시켜 오디오 플레이를 임시로 중단시키고 플레이 중이 아니라면 startPlaybackForPlayer라는 함수로 계속 소리가 나도록 한다. 각각의 함수가 동작할 때 버튼 이미지나 현재 상황을 업데이트 해 주며 업데이트를 위한 타이머가 작동한다(updateCurrentTimeForPlayer).

<리스트 1> 오디오 플레이 버튼 입력 함수

- (IBAction)playButtonPressed:(UIButton *)sender
{
   if (player.playing == YES)
      [self pausePlaybackForPlayer: player];
   else
      [self startPlaybackForPlayer: player];
}


오디오 볼륨을 위해 볼륨 슬라이드를 움직일 경우 UISlider로부터 전달된 player 객체의 볼륨 조절 함수가 동작해 볼륨값이 조절된다.

<리스트 2> 볼륨 슬라이더 조작 함수

- (IBAction)volumeSliderMoved:(UISlider *)sender
{
   player.volume = [sender value];
}


오디오가 재생되는 도중에 슬라이드 바를 움직일 경우 해당되는 지점으로 이동하는 동작이 필요하다. 이 때 사용되는 것이 progressSliderMoved 함수다.

<리스트 3> 프로그래스 바 조작 함수

- (IBAction)progressSliderMoved:(UISlider *)sender
{
   player.currentTime = sender.value;
   [self updateCurrentTimeForPlayer:player];
}


뒤로 감기 버튼이나 전진 버튼을 눌렀을 경우(Pressed)와 놓았을 경우(Released)에는 <리스트 4>와 같은 함수들이 사용된다. 실제로 동작할 때는 NSTimer를 사용해 스킵 간격을 조절하게 된다.

<리스트 4> 앞/뒤 감기 버튼 함수

- (IBAction)rewButtonPressed:(UIButton *)sender
{
   if (rewTimer) [rewTimer invalidate];
   rewTimer = [NSTimer scheduledTimerWithTimeInterval:SKIP_INTERVAL target:self selector:@selector(rewind) userInfo:player repeats:YES];
}

(IBAction)rewButtonReleased:(UIButton *)sender
{
   if (rewTimer) [rewTimer invalidate];
   rewTimer = nil;
}


오디오가 재생되는 중에 화면보호 기능 등이 동작하거나 사용자가 임의로 버튼을 눌렀더라도 지속적으로 오디오가 나와야 할 경우가 있을 것이다. 이를 위해 애플리케이션이 초기화되는 부분에서 AVAudioSessionCategoryPlayback을 사용, <리스트 5>와 같은 코드를 작성해 준다. 

<리스트 5> 오디오 세션의 지속적 플레이 함수

NSError *setCategoryErr = nil;
NSError *activationErr = nil;

[[AVAudioSession sharedInstance]
setCategory : AVAudioSessionCategoryPlayback
error: &setCategoryErr];
[[AVAudioSession sharedInstance]
setActive: YES
error : &activationErr];


다음으로 전화가 오거나 알람 설정 등으로 인해 발생되는 인터럽트 처리 방법을 알아보자.
오디오 재생 중에 발생되는 여러 가지 인터럽트들은 <표 3>과 같은 기술들로 처리된다.

<표 3> 오디오 기술과 인터럽트 처리 방식

AAC 타입을 위한 하드웨어 인코딩 방법
앞에서 오디오를 재생하고 녹음을 하는 동안 하드웨어의 인터럽트를 사용한 인코딩/디코딩 방식이 있다고 설명했다. 아이폰의 여러 가지 오프라인 모드에서도(알람이나 전화가 왔을 경우 벨소리) 별도로 오디오를 동작시키는 방법이 필요하다. 이러한 경우 주로 사용되는 오디오 포맷이 AAC인데 코덱을 이용할 수 있는 장치로는 아이폰 3GS 이상의 모델, 아이팟 2세대 이상, 아이패드 등이 있다. AudioConverterRef라는 메소드를 활용하면 오디오 변환을 위한 기능들을 사용할 수 있다. 오디오 변환과 관련된 서비스들에 대해서는 ‘Audio Converter Services Reference and Extended Audio File Services Reference’라는 문서를 참조하면 된다. 

오디오 재생이나 녹음이 진행될 때 인터럽트가 발생됐을 경우의 라이프 사이클은 <그림 1>과 같이 설명할 수 있다. 진행되는 순서에 따라 살펴보자.

<그림 1> 오디오 재생시 앱의 라이프사이클(출처 AudioSessionProgrammngGuide.pdf)

1. 애플리케이션의 오디오를 동작시킨다.
2. 전화가 와서 인터럽트가 걸린다. 폰 애플리케이션의 오디오 세션이 활성화된다.
3. 이 때 시스템이 사용자의 오디오 세션을 비활성화 시킨다.
4. 시스템이 사용자의 인터럽트 리스너 콜백 함수를 invoke시키거나 인터럽트가 시작됐다는 델리게이트 메소드를 호출한다. 사용중이던 애플리케이션은 잠시 비활성화 된다.
5. 사용자의 콜백 또는 델리게이트 메소드는 액션에 해당되는 동작을 하게 된다.
6. 만약 사용자가 인터럽트를 무시하는 동작을 하면(전화를 받지 않거나 할 경우) 시스템은 인터럽트가 끝났다고 간주하고 다음 동작을 할 것이다.
7. 사용자의 콜백 또는 델리게이트 메소드는 인터럽트 종료시 동작된다. 예를 들면 사용자 인터페이스를 업데이트하거나 오디오 재생 등을 다시 시작할 경우 쓸 수 있다.

오디오 세션을 위한 팁들
이제 오디오 세션을 동작시키기 위한 코드들을 살펴보면서 실제 예를 통해 어떤 방식으로 활용할 수 있을지 알아보자.

● 오디오 세션의 초기화
오디오 인터럽트를 위한 AV 델리게이트 등을 사용하려고 한다면 오디오 세션을 초기화 한 상태에서 가능할 것이다. 오디오 인터럽트와 연관된 여러 가지 함수들을 잘 사용하기 위해 AudioSessionInitialize라는 함수를 호출해야 한다. 이것은 애플리케이션이 시작될 때 한 번만 불리도록 만들면 된다. 애플리케이션에서 오디오 세션의 초기화 등을 하지 않았을 경우에는 iOS에서 기본으로 제공하는 싱글톤 오디오 세션 오브젝트가 로딩이 끝난 후에 사용할 수 있도록 제공된다. 함수는 <리스트 6>과 같이 제공되며 Audioservice.h에 정의돼 있다.

<리스트 6> 오디오 세션 초기화 함수
AudioSessionInitialize(CFRunLoopRef inRunLoop,
                     CFStringRef inRunLoopMode,
                     AudioSessionInterruptionListener inInterruptionListener,
                     void *inClientData
   );


첫 번째 인자는 main 함수의 loop를 위해 사용된다. 두 번째 인자는 main이 아닌 run loop 모드에서 사용된다. 세 번째 인자는 리스너 콜백 함수에 해당되며 <리스트 7>과 같이 정의돼 있다.

<리스트 7> 오디오세션 인터럽트 리스너 콜백 함수

typedef void (*AudioSessionInterruptionListener)(
                               void *  inClientData,
                               UInt32  inInterruptionState
   );


각종 인터럽트는 다음과 같은 장치들을 연결하거나 동작시키는 상황에서 발생된다.

“Headset” “Headphone” “Speaker” “SpeakerAndMicrophone” “HeadphonesAndMicrophone” “HeadsetInOut” “ReceiverAnd Microphone” “Lineout”

네 번째는 사용자 데이터로 정의돼 있는데 오디오 세션 오브젝트가 invoke됐을 때 사용자의 인터럽트 리스너 콜백 함수로 연결시키도록 만들어져 있다(간단히 말하면 사용자가 의도한 함수를 실행하게 만든다).

● 사용자 오디오 세션의 활성화/비활성화
AVFoundation framework를 활용해 오디오 세션을 활성화 할 경우의 코드를 보자.

NSError *activationError = nil;
[[AVAudioSession sharedInstance] setActive: YES error: &activationError];

이 경우 비활성화 할 때는 setActive 속성을 “NO”로 변경해 주면 된다. 다음과 같은 스타일로 오디오 세션을 활성화/비활성화 할 수도 있다.

OSStatus activationResult = NULL;
result = AudioSessionSetActive (true);

● 애플리케이션이 시작될 때 다른 오디오가 재생되고 있는지 확인
사용자가 애플리케이션을 시작할 때 이미 오디오 장치를 통해 소리가 나고 있다면 어떻게 할지에 대해 생각해 보자. 이런 경우는 주로 게임을 시작할 때 등에 해당될텐데 게임을 시작하기 전에 오디오 세션이 잘 초기화돼 있는지 등을 살펴보고 다음 동작으로 이어진다.

<리스트 8> 오디오 세션 체크 함수

UInt32 otherAudioIsPlaying;
UInt32 propertySize = sizeof (otherAudioIsPlaying); AudioSessionGetProperty(kAudioSessionProperty_OtherAudioIsPlaying, &propertySize, &otherAudioIsPlaying);

if (otherAudioIsPlaying) { [[AVAudioSession sharedInstance]
   setCategory: AVAudioSessionCategoryAmbient error: nil];
}
else { [[AVAudioSession sharedInstance]
   etCategory: AVAudioSessionCategorySoloAmbient error: nil];
}


오디오가 플레이되고 있는지의 여부를 판단하는 프로퍼티 ID에는 AudioServices.h에 다음과 같이 enum 값이 정의돼 있다. 이 값이 otherAudioIsPlaying 변수에 할당된다.

kAudioSessionProperty_OtherAudioIsPlaying = ‘othr’,
// UInt32(get only)

또한 AVAudioSession.h에는 <리스트 9>와 같이 정의돼 있다.

<리스트 9> 오디오 세션 플레이 체크 상수

extern NSString *const AVAudioSessionCategoryAmbient;     // 다른 오디오가 재생되고 있을  경우
extern NSString *const AVAudioSessionCategorySoloAmbient;   // 재생되고 있지 않을 경우


● 재생 중 믹싱 동작
두 가지 오디오 소스로부터 입력을 받아 믹싱한 후에 재생할 필요가 있을 경우에는 특별히 <리스트 10>과 같이 동작을 설정할 수 있다. 이 예는 Voicemail.caf 파일과 음악 파일을 동시에 재생할 경우 어떻게 처리하는지에 대해 설명한다.

<리스트 10> 오디오 믹싱과 재생 함수

AudioSessionInitialize( NULL, NULL, NULL, NULL );  
// 오디오 세션 초기화
UInt32 sessionCategory = kAudioSessionCategory_AmbientSound; AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(sessionCategory), &sessionCategory);
AudioSessionSetProprty(true);   //오디오 세션의 프로퍼티를 동작으로 설정한다. 이 경우 음악 등을 재생하고 있으면 계속 소리가 나오는 상태가 된다.

NSURL* musicFile = [NSURL fileURLWithPath:[[NSBundle mainBundle]
pathForResource:@"Voicemail"
ofType:@"caf"]];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:musicFile error:nil];
[audioPlayer play];   // 믹서의 동작으로 Voicemail.caf 파일을 재생하게 된다.


현재 오디오의 동작 상태를 결정하는 AudioSessionSetActive 함수는 AudioServices.h에 다음과 같이 정의돼 있다.

extern OSStatus AudioSessionSetActive(Boolean active);  

이제 코드를 통해 애플리케이션에서 사용되는 사운드 재생법을 살펴보자(연재를 작성하는 중에 Xcode 4 버전이 발표돼 화면들은 새로운 버전을 기준으로 작성됐다).

1. View-based Application 프로젝트를 생성한다. 프로젝트의 이름은 AudioSample01로 만들었다(Xcode 4는 프로젝트의 이름을 정하면서 자체적으로 메소드 등에 대해 Unit Test를 할 수 있는 옵션을 제공한다).

<화면 2> View based template 선택

<화면 3> 프로젝트 이름 설정

<화면 4>와 같이 프로젝트가 생성됐다.

<화면 4> 프로젝트 정보 화면

2.  이번 예제에서는 화면 구성을 위해 Interface Builder를 사용했다. <화면 5>와 같이 재생중인 오디오 파일의 이름이 나오게 될 라벨과 오디오 재생 이벤트를 설정할 <확인> 버튼을 위치시켰다(Xcode 4에서는 인터페이스 빌더도 하나의 윈도우에 나타나도록 디자인됐다).

<화면 5> 인터페이스 빌더 화면

3. 오디오를 재생하게 될 <확인> 버튼과 오디오 파일의 정보를 표시할 라벨에 대해‘AudioSample01ViewController.h’에 <리스트 11>과 같이 코드를 작성한다.
    그리고 AudioToolbox.frame work을 추가한다.

<리스트 11> 버튼과 라벨 설정

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}


4. PlaySound라는 함수에 오디오 파일명을 <리스트 12>와 같이 입력한다. SystemSoundID라는 타입을 정의해 AudioServices CreateSystemSoundID, AudioServicesPlaySystemSound라는 형식으로 플레이하게 된다. 화면에서 <확인> 버튼을 누르면 type.wav 파일이 재생되면서 파일명이 라벨에 표시된다.
<리스트 12> 오디오 재생 함수

-(IBAction)PlaySound:(id)sender
{
   NSString *path = [[NSBundle mainBundle] pathForResource:@"type" ofType:@"wav"];
   SystemSoundID soundID;
   label.text = path;
      
   NSLog(@"path is %@", path); //null print on iphone
      
   AudioServicesCreateSystemSoundID((CFURLRef) [NSURL fileURLWithPath:path], &soundID);
   AudioServicesPlaySystemSound(soundID);
}

Xcode 4에 프레임워크 추가

기존에는 프로젝트 내비게이션 트리에서 오른 쪽 클릭으로 프레임워크를 추가할 수 있었다. 새로운 버전에서는 <화면 6>과 같이 프로젝트 메뉴를 선택하고 TARGETS 항목의 Build Project 영역을 선택해 Link Binary With Libraries라는 곳에서 ‘+’를 선택함으로써 프레임워크를 추가하면 된다. 프로젝트에서 사용되는 여러 가지 프로파일들을 한 곳에 모아두기 위한 방법이라고 생각된다.

<화면 6> 프레임워크 추가 화면


아이폰 시뮬레이터

아이폰이나 아이패드 개발 뿐만 아니라 임베디드 디바이스의 애플리케이션을 개발할 때는 테스트를 위해 소스 코드를 빌드한 후에 이미지를 실제 장비에 설치하게 된다. 하지만 간단한 UI나 디자인 등을 테스트하기 위해 실제 장비에 매번 이미지를 설치하는 작업은 시간이 필요 이상으로 많이 걸리는 일이다. 이럴 경우 임베디드 환경에서는 보통 시뮬레이터라고 하는 소프트웨어적인 가상의 장치를 사용하게 된다. 

아이폰에서 사용되는 시뮬레이터는 <화면 7>과 같은 형태인데 재미있는 기능이 있다. 아이폰(아이패드)에는 모바일 키노트 애플리케이션이나 동영상 애플리케이션의 화면을 TV 화면으로 보낼 수 있는 기능이 있다. ‘iOS Simulator’에도 이같은 기능이 있다. ‘Hardware’ 메뉴에 ‘TV Out’ 항목이 그것으로, 해상도별로 세 가지 모드를 시뮬레이션해 준다.

<화면 7> iOS 시뮬레이터

유튜브 화면을 플레이해 봤다. 시뮬레이터는 <화면 8>과 같은 화면으로 변경되고 <화면 9>와 같은 TV Out 화면이 나타난다.

<화면 8> 동영상 플레이 화면

<화면 9> TV Out 화면


참고자료

1. MultimediaProgrammingGuide.pdf
https://developer.apple.com/library/ios/#documentation/AudioVideo/Conceptual/MultimediaPG/Introduction/Introduction.html%23//apple_ref/doc/uid/TP40009767
2. https://developer.apple.com/library/ios/#referencelibrary/GettingStarted/ GS_AudioVideo_iPhone/




출처 : http://blog.daum.net/_blog/hdn/ArticleContentsView.do?blogid=05JzW&articleno=16056926&looping=0&longOpen=
Posted by 오늘마감

댓글을 달아 주세요