iOS coreaudio 재생 예제 - 아이폰
1.오디오 재생을 위한 변수 설정
#import <AudioToolbox/AudioToolbox.h>
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow * window;
/*
ASBD (오디오 스트림 기본 설명)를 구성하여 선형 PCM 형식 또는 동일한 크기의 채널이있는 CBR (고정 비트 전송률) 형식을 지정할 수 있습니다.
가변 비트 전송률 (VBR) 오디오 및 채널의 크기가 다른 CBR 오디오의 경우
각 패킷은 AudioStreamPacketDescription 구조에 의해 추가로 설명되어야합니다.
*/
@property (nonatomic, assign) AudioStreamBasicDescription streamFormat;
//UInt32 : 42억...
@property (nonatomic, assign) UInt32 bufferSize;
@property (nonatomic, assign) double currentFrequency;
@property (nonatomic, assign) double startingFramCount;
@property (nonatomic, assign) AudioQueueRef audioQueue;
-(OSStatus) fillBuffer:(AudioQueueBufferRef) buffer; //A pointer to an audio queue buffer.
@end
#참고
AudioQueueRef
오디오 대기열을 나타내는 불투명 한 데이터 유형을 정의합니다.
오디오 대기열은 macOS에서 오디오를 녹음하거나 재생하는 데 사용하는 소프트웨어 개체입니다. 다음 작업을 수행합니다.
1.오디오 하드웨어에 연결
2.메모리 관리
3.압축 된 오디오 형식을 위해 필요에 따라 코덱 사용
4.녹화 또는 재생 조정
오디오 대기열 서비스에 설명 된 기능을 사용하여 오디오 대기열을 생성, 사용 및 폐기합니다.
AudioQueueBufferRef
각 오디오 대기열에는 연결된 오디오 대기열 버퍼 세트가 있습니다.
버퍼를 할당하려면 AudioQueueAllocateBuffer 함수를 호출합니다.
버퍼를 삭제하려면 AudioQueueFreeBuffer 함수를 호출합니다.
VBR 압축 오디오 데이터 형식을 사용하는 경우 대신 AudioQueueAllocateBufferWithPacketDescriptions 함수를 사용할 수 있습니다.
이 함수는 패킷 설명을 위한 추가 공간이있는 버퍼를 할당합니다.
mPacketDescriptionCapacity, mPacketDescriptions 및 mPacketDescriptionCount 필드는
AudioQueueAllocateBufferWithPacketDescriptions로 할당 된 버퍼에만 사용할 수 있습니다.
AudioQueueBufferRef 프로퍼티 (속성)
- mAudioData
오디오 데이터는 오디오 대기열 버퍼를 소유했습니다. 버퍼 주소는 변경할 수 없습니다.
- mAudioDataByteSize
오디오 대기열 버퍼의 mAudioData 필드에있는 유효한 오디오 데이터의 바이트 수이며 처음에는 0으로 설정됩니다.
콜백은 재생 오디오 대기열에 대해이 값을 설정해야합니다. 녹음의 경우 녹음 오디오 대기열이 값을 설정합니다.
- mAudioDataBytesCapacity
오디오 대기열 버퍼의 크기 (바이트)입니다. 이 크기는 버퍼가 할당 될 때 설정되며 변경할 수 없습니다.
- mPacketDescriptionCapacity
mPacketDescriptions 필드에 저장할 수있는 최대 패킷 설명 수입니다.
- mPacketDescriptionCount
버퍼에있는 유효한 패킷 설명 수입니다. 재생을위한 버퍼를 제공 할 때이 값을 설정합니다. 오디오 대기열은 녹음 대기열에서 버퍼를 반환 할 때이 값을 설정합니다.
- mPacketDescriptions
버퍼에 대한 AudioStreamPacketDescription 구조의 배열입니다.
- mUserData
녹음 또는 재생 오디오 대기열을 만들 때 콜백 함수에서 사용하도록 지정하는 사용자 지정 데이터 구조입니다.
2.생성할 음원 데이터
#pragma mark - #defines
#define FOREGROUND_FREQUENCY 880.0
#define BACKGROUND_FREQUENCY 523.25
#define BUFFER_COUNT 3
#define BUFFER_DURATION 0.5
3. synthesizes
#pragma mark @synthesizes
@synthesize streamFormat = _streamFormat;
@synthesize bufferSize;
@synthesize currentFrequency;
@synthesize startingFramCount;
@synthesize audioQueue;
4.오류 처리 함수
#pragma mark helpers - 공통 에러 메소드. err 이 0이 아니면 , 에러 메시지 출력 후, 프로그램 종료
static void CheckError(OSStatus error, const char *operation){
//오류 없으면 return
if (error == noErr) return;
char str[20];
//32 비트 정수를 호스트의 기본 바이트 순서에서 빅 엔디안 형식으로 변환합니다.
*(UInt32 *)(str + 1) = CFSwapInt32HostToBig(error);
//c가 출력할 수 있는 문자의 ASCII 코드 값인지 판별
if (isprint(str[1]) && isprint(str[2]) && isprint(str[3]) && isprint(str[4])) {
str[0] = str[5] = '\'';
str[6] = '\0';
}else
//str 변수에 error 값 대입
sprintf(str, "%d" , (int)error);
//출력
fprintf(stderr, "오류 : %s (%s) \n" , operation , str);
exit(1);
}
5.오디오 큐 세션 인터럽트 함수
- (void)audioSessionDidChangeInterruptionType:(NSNotification *)notification
{
NSLog(@"Interrupted 발생 - !");
AVAudioSessionInterruptionType interruptionType =
[[[notification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
NSLog(@"interruptionType : %lu" , (unsigned long)interruptionType);
if (AVAudioSessionInterruptionTypeBegan == interruptionType){
printf("kAudioSessionBeginInterruption \n");
}else if(AVAudioSessionInterruptionTypeEnded == interruptionType){
printf("kAudioSessionEndInterruption \n");
CheckError(AudioQueueStart(self.audioQueue, 0), "오디오 큐를 재시작 할 수 없습니다...");
}
}
6.오디오 큐 콜백 함수
/*
- 오디오 큐 콜백 함수 -
1.파일 or 데이터 읽어서 빈 버퍼 채우기
2.채워진 버퍼를 재생 큐에 넣기 - AudioQueueEnqueueBuffer
*/
static void MyAQOutputCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inCompleteAQBuffer)
{
printf("MyAQOutputCallback 호출 \n");
NSLog(@"inCompleteAQBuffer : %u" , inCompleteAQBuffer->mAudioDataByteSize);
AppDelegate *appDelegate = (__bridge AppDelegate *)inUserData;
//ex) fillBuffer 함수 : 오디오 파일에서 읽은 데이터 버퍼에 채우기
//버퍼 다시 채우기
CheckError([appDelegate fillBuffer:inCompleteAQBuffer],
"버퍼를 다시 채울 수 없습니다...");
//inCompleteAQBuffer 에는 파일에서 읽은 데이터가 들어있다!
/* 채워진 버퍼를 재생 큐에 넣기 - 소리 출력*/
//param : 1.오디오 큐, 2.채워진 버퍼. 3. vbr이면 패킷값, cbr이면 0, NULL
CheckError(AudioQueueEnqueueBuffer(inAQ,
inCompleteAQBuffer,
0,
NULL),
"버퍼를 큐에 enqueue 할 수 없습니다...(refill)");
}
7.버퍼 채우기 함수
//버퍼 채우기
/*
참고 : 실제 파일에서 읽으려면 AudioFileReadPacketData 함수 사용가능
AudioFileReadPacketData(오디오파일, 캐시설정, 실제읽을바이트, packetDescs, currentPacket, 읽은패킷, 버퍼에서 읽은 데이터)
*/
-(OSStatus) fillBuffer:(AudioQueueBufferRef)buffer{
printf("fillBuffer 호출 \n");
double j = self.startingFramCount;
double cycleLength = 44100. / self.currentFrequency;
int frame = 0;
double frameCount = bufferSize / self.streamFormat.mBytesPerFrame;
printf("startingFramCount : %f \n" , j);
printf("currentFrequency : %f \n" , self.currentFrequency);
printf("cycleLength : %f \n" , cycleLength);
printf("bufferSize : %u \n" , bufferSize);
printf("self.streamFormat.mBytesPerFrame : %u \n" , self.streamFormat.mBytesPerFrame);
printf("frameCount : %f \n\n" , frameCount);
for (frame = 0; frame < frameCount; ++frame)
{
SInt16 *data = (SInt16*)buffer->mAudioData;
(data)[frame] = (SInt16) (sin (2 * M_PI * (j / cycleLength)) * SHRT_MAX);
j += 1.0;
if (j > cycleLength)
j -= cycleLength;
}
self.startingFramCount = j;
//버퍼 채우기
buffer->mAudioDataByteSize = bufferSize;
return noErr;
}
8. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions 내부
- 구현 순서 -
1.AVAudioSession 설정
2.ASBD 설정
3.오디오 출력 큐 생성(AudioQueueNewOutput) - param : 1.ASBD, 2.callback 함수 , 3.audioQueue
4.큐에 버퍼 할당(AudioQueueAllocateBuffer) - param : 1.오디오 큐, 2.버퍼 사이즈, 3. 버퍼_Ref
5.버퍼 채우기 - fillBerffer
6.버퍼를 큐에 넣기(AudioQueueEnqueueBuffer) - param : 1. 오디오 큐, 2.채워진 버퍼_Ref
7.오디오 큐 시작(AudioQueueStart)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSError *error = nil;
/* 1.AVAudioSession 설정 */
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioSessionDidChangeInterruptionType:)
name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
if (![[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&error]) {
NSLog(@"오디오 세션 카테고리를 셋팅할 수 없습니다 : %@" , error);
}else{
NSLog(@"오디오 세션 카테고리를 셋팅 성공");
}
if (![[AVAudioSession sharedInstance] setActive:YES error:&error]) {
NSLog(@"오디오 세션을 활성화 시킬 수 없습니다 : %@" , error);
}else{
NSLog(@"오디오 세션 활성화 성공");
}
/* 2.ASBD 설정 */
self.currentFrequency = FOREGROUND_FREQUENCY;
_streamFormat.mSampleRate = 44100.0;
_streamFormat.mFormatID = kAudioFormatLinearPCM;
_streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
_streamFormat.mChannelsPerFrame = 1;
_streamFormat.mFramesPerPacket = 1;
_streamFormat.mBitsPerChannel = 16;
_streamFormat.mBytesPerFrame = 2;
_streamFormat.mBytesPerPacket = 2;
/* 3.오디오 출력 큐 생성 */
/* param : 1.ASBD, 2.callback 함수 , 3.audioQueue */
CheckError(AudioQueueNewOutput(&_streamFormat,
MyAQOutputCallback,
(__bridge void * _Nullable)(self),
NULL,
kCFRunLoopCommonModes,
0,
&audioQueue),
"출력 오디오 큐를 생성할 수 없습니다.");
/* 4.버퍼 생성 및 큐에 넣어주기 */
AudioQueueBufferRef buffers[BUFFER_COUNT];
bufferSize = BUFFER_DURATION * self.streamFormat.mSampleRate * self.streamFormat.mBytesPerFrame;
NSLog(@"bufferSize : %u" , bufferSize);
for (int i = 0; i < BUFFER_COUNT; i++) {
//4-1 버퍼 할당
/* param : 1.오디오 큐, 2.버퍼 사이즈, 3. 버퍼 포인터 */
CheckError(AudioQueueAllocateBuffer(audioQueue,
bufferSize,
&buffers[i]),
"오디오 큐 버퍼를 할당할 수 없습니다...");
//4-2 버퍼 채우기
CheckError([self fillBuffer:buffers[i]],
"버퍼를 채울 수 없습니다...(priming)");
//4-3 버퍼를 큐에 넣기
/* param : 1. 오디오 큐, 2.채워진 버퍼_ Ref */
CheckError(AudioQueueEnqueueBuffer(audioQueue,
buffers[i],
0,
NULL),
"버퍼를 큐에 enqueue 할 수 없습니다...(priming)");
}
/* 5. 오디오 큐 시작 */
CheckError(AudioQueueStart(audioQueue,
NULL),
"오디오 큐를 시작할 수 없습니다...");
return YES;
}
9.백그라운드, 포그라운드 진입시 음원 변경 함수
//백그라운드 진입 감지 메소드
-(void)applicationDidEnterBackground:(UIApplication *)application{
//소리 변경
self.currentFrequency = BACKGROUND_FREQUENCY;
}
//포그라운드 진입 감지 메소드
-(void)applicationWillEnterForeground:(UIApplication *)application{
//1.세션 다시 활성화
NSError *error = nil;
if (![[AVAudioSession sharedInstance] setActive:YES error:&error]) {
NSLog(@"오디오 세션을 재 활성화 시킬 수 없습니다 : %@" , error);
}
//2. 오디오 큐 재시작
CheckError(AudioQueueStart(self.audioQueue,
0),
"오디오 큐를 다시 시작 할 수 없습니다....");
//3.소리 변경
self.currentFrequency = FOREGROUND_FREQUENCY;
}
참고사이트(현재 시간체크 관련)
https://stackoverflow.com/questions/30377711/precise-time-of-audio-queue-playback-finish
https://stackoverflow.com/questions/707377/how-to-retrieve-a-valid-mhosttime-using-audioqueues
https://stackoverflow.com/questions/3395244/how-to-check-current-time-and-duration-in-audioqueue
'아이폰 개발 > ios 개념&튜토리얼' 카테고리의 다른 글
iOS AudioFileStream 예제 (0) | 2021.01.22 |
---|---|
iOS 코어오디오 재생 예제자료 (0) | 2021.01.21 |
iOS coreaudio 재생 예제 - mac OS (0) | 2021.01.21 |
ios 파일생성 파일저장 파일삭제 (0) | 2021.01.12 |
ios Photos framework 로 사진첩만들기 (0) | 2021.01.09 |