core audio queue service
본글은 애플의 오디오큐서비스프로그래밍 가이드 문서를 참고해서 작성했습니다.
뷰
코드
//step 1. 라이브러리 임포트
#import <AudioToolbox/AudioToolbox.h>
#import <CoreAudioKit/CoreAudioKit.h>
#import <MediaPlayer/MediaPlayer.h>
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
//step1. 상태를 관리하기 위한 사용자정의 구조
static const int kNumberBuffers = 3; // 1
struct AQRecorderState {
AudioStreamBasicDescription mDataFormat; // 2 . 오디오 데이터 포멧, mQueue에 사용된다.
AudioQueueRef mQueue; // 3. 앱에 의해 생성되는 레코딩 오디오 큐
AudioQueueBufferRef mBuffers[kNumberBuffers]; // 4. 오디오 큐가 관리하는 버퍼 포인터
AudioFileID mAudioFile; // 5. 오디오 파일 객체
UInt32 bufferByteSize; // 6. 버퍼 사이즈 (DeriveBufferSize에서 계산), 오디오 큐가 만들어진 후, 시작되기전
SInt64 mCurrentPacket; // 7. 현재 오디오 큐 버퍼에서 부터 기록되어지는 최초의 패킷의 패킷 인덱스
bool mIsRunning; // 8. 오디오 큐가 실행되고 있는지 여부
};
@interface ViewController : UIViewController
@end
#import "ViewController.h"
//step2. 레코딩 오디오 큐 콜백 함수 작성
// - 새롭게 채워진 오디오 큐 버퍼의 내용을 녹음 중인 오디오 파일에 기록한다.
// - 방금 디스크에 쓰여진 버퍼를 버퍼 큐에 넣는다.
static void HandleInputBuffer (
void *aqData, // 1. 오디오 큐의 상태관련 정보. step.1에 기록되어 있다.
AudioQueueRef inAQ, // 2. 이 콜백을 소유한 오디오 큐
AudioQueueBufferRef inBuffer, // 3. 녹음할 수신오디오 데이터를 가지고 있는 오디오 큐 버퍼
const AudioTimeStamp *inStartTime, // 4. 오디오 큐 버퍼에 있는 첫 번째 샘플의 샘플 시간
UInt32 inNumPackets, // 5. 패킷을 설명할 매개변수, cbr = 0
const AudioStreamPacketDescription *inPacketDesc // 6. 패킷설명이 필요한 압축된 오디오 데이터 형식의 경우, 버퍼의 패킷에 대해 인코더가 생성한 패킷의 설명이다.
){
NSLog(@"HandleInputBuffer 콜백 함수 진입");
//유저데이터(녹음 구조체) 가져와서 변수에 할당
//다양한 상태 데이터, 기록 할 오디오 파일을 나타내는 오디오 파일 객체
struct AQRecorderState *pAqData = (struct AQRecorderState *) aqData;
//오디오 큐 버퍼에 CBR 데이터가 포함되어 있으면 버퍼 안의 패킷 수를 계산해야한다.
//이 숫자는 버퍼 안의 데이터의 총 바이트 수를 패킷 당 (상수) 바이트 수로 나눈 값입니다.
//VBR 데이터의 경우 오디오 큐는 콜백을 호출 할 때 버퍼에있는 패킷 수를 제공합니다.
if (inNumPackets == 0 && pAqData-> mDataFormat.mBytesPerPacket != 0){
inNumPackets = inBuffer-> mAudioDataByteSize / pAqData-> mDataFormat.mBytesPerPacket;
NSLog(@"inNumPackets : %u" , (unsigned int)inNumPackets );
}
NSLog(@"inNumPackets - VBR : %u" , (unsigned int)inNumPackets ); //1000
NSLog(@"inBuffer-> mAudioDataByteSize : %u" , (unsigned int)inBuffer-> mAudioDataByteSize );
NSLog(@"pAqData-> mCurrentPacket : %u" , (unsigned int)pAqData-> mCurrentPacket );
//step.3 오디오 큐 콜백의 첫번째 작업은 오디오 큐 버퍼를 디스크에 기록하는것.
//이 버퍼는 콜백의 오디오 큐가 방금 디바이스로부터 새로운 오디오 데이터 채우기를 마친 버퍼다.
OSStatus stt = AudioFileWritePackets ( // 1 . 버퍼의 콘텐츠를 오디오 데이터 파일에 기록하는 함수
pAqData-> mAudioFile, // 2. pAqData는 step1의 데이터 구조를 참조한다.
false, // 3. 데이터가 쓰이는 동안 함수가 캐시하지 않게
inBuffer-> mAudioDataByteSize, // 4.쓰여질 오디오 데이터의 바이트 수. inBuffer 는 오디오 큐에 의해 콜백함수로 전달되는 오디오 큐 버퍼이다.
inPacketDesc, // 5. 오디오 데이터를 위한 패킷설명의 배열. cbr = null, 패킷 설명이 필요없음.
pAqData-> mCurrentPacket, // 6. 쓰여질 첫번째 패킷을 위한 패킷 인덱스
& inNumPackets, // 7. 입력시 쓸 패킷의 수, 출력시 실제로 쓴 패킷의 수
inBuffer-> mAudioData // 8. 오디오 파일에 쓰기 위한 새로운 오디오 데이터
);
//If successful in writing the audio data, increment the audio data file’s packet index to be ready for writing the next buffer's worth of audio data.
//오디오 데이터를 성공적으로 기록한 경우, 오디오 데이터 파일의 패킷 인덱스를 증가시켜, 다음 버퍼의 오디오 데이터를 쓸 준비를합니다.
if (stt == noErr) {
pAqData->mCurrentPacket += inNumPackets;
NSLog(@" pAqData->mCurrentPacket : %lld" , pAqData->mCurrentPacket );
}
//오디오 큐가 정지된 경우 return;
if (pAqData->mIsRunning == 0)
return;
//내용이 방금 작성된 오디오 큐 버퍼를 오디오 파일에 큐잉합니다.
//오디오 큐 버퍼 큐에 넣기
//오디오 큐 버퍼의 오디오 데이터가 오디오 파일에 기록되었으므로 콜백이 버퍼를 큐에 넣습니다 .
//일단 버퍼 큐에 다시 들어가면, 버퍼는 라인에 있고 더 많은 수신 오디오 데이터를 받아 들일 준비가되어 있습니다.
//step.4 디스크에 기록한 후 오디오 큐 버퍼를 큐에 넣기.
AudioQueueEnqueueBuffer ( // 1. 버퍼를 오디오 큐에 추가한다.
pAqData-> mQueue, // 2. 지정된 오디오 버퍼를 추가하는 오디오 큐
inBuffer, // 3. 큐에 들어갈 오디오 버퍼
0, // 4. 오디오 대기열 버퍼의 데이터에있는 패킷 설명 수입니다.
NULL // 5.오디오 큐 버퍼의 데이터를 기술하는 패킷 기술의 배열. NULL이 매개 변수가 녹음에 사용되지 않았으므로로 설정하십시오 .
);
}
@interface ViewController (){
struct AQRecorderState aqData;
}
@property (weak, nonatomic) IBOutlet UIButton *startBtn;
@property (weak, nonatomic) IBOutlet UIButton *endBtn;
@end
@implementation ViewController
//시작
- (IBAction)startBtn:(id)sender {
aqData.mCurrentPacket = 0; // 1.패킷 인덱스를 0로 초기화하여 오디오 파일의 시작 부분에서 녹음을 시작합니다.
aqData.mIsRunning = true; // 2. 오디오 큐가 실행 중임을 나타내는 사용자 지정 구조의 플래그를 설정합니다. 이 플래그는 녹음 오디오 큐 콜백에 사용됩니다.
AudioQueueStart ( // 3.자체 스레드에서 오디오 큐를 시작한다.
aqData.mQueue, // 4.시작할 오디오 큐
NULL // 5. 오디오 큐가 즉시 녹음을 시작해야 함을 의미
);
}
- (IBAction)endBtn:(id)sender {
NSLog(@"disposeAudioRecorder 진입");
if (aqData.mQueue) {
NSLog(@"disposeAudioRecorder - mQueue 해제");
AudioQueueDispose(aqData.mQueue, false);
aqData.mQueue = NULL;
}
if (aqData.mAudioFile) {
NSLog(@"disposeAudioRecorder - mAudioFile 해제");
AudioFileClose(aqData.mAudioFile);
aqData.mAudioFile = NULL;
}
}
- (void)viewDidLoad {
[super viewDidLoad];
//녹음을 위한 오디오 포맷 설정
aqData.mDataFormat.mFormatID = kAudioFormatLinearPCM; // 2. 오디오 데이터 형식 비압축 형식 (오디오 데이터 형식 유형 (예 : 선형 PCM, AAC 등))
aqData.mDataFormat.mSampleRate = 44100.0; // 3.샘플 속도 44.1 khz (샘플 속도 (예 : 44.1 kHz))
aqData.mDataFormat.mChannelsPerFrame = 2; // 4.채널 수 2 (오디오 채널 수 (예 : 2, 스테레오 용))
aqData.mDataFormat.mBitsPerChannel = 16; // 5. 채널당 비트 깊이 16
aqData.mDataFormat.mBytesPerPacket = // 6.
aqData.mDataFormat.mBytesPerFrame = aqData.mDataFormat.mChannelsPerFrame * sizeof (SInt16);
aqData.mDataFormat.mFramesPerPacket = 1; // 7.패킷 당 프레임 수를 1로 정의합니다.
AudioFileTypeID fileType = kAudioFileAIFFType; // 8. 파일 유형을 AIFF로 정의합니다
aqData.mDataFormat.mFormatFlags = // 9. 지정된 파일 형식에 필요한 형식 플래그를 설정합니다.
kLinearPCMFormatFlagIsBigEndian
| kLinearPCMFormatFlagIsSignedInteger
| kLinearPCMFormatFlagIsPacked;
NSLog(@" sizeof (SInt16) : %lu" , sizeof (SInt16)); //2.
NSLog(@"viewDidLoad - mBytesPerPacket : %d , mBytesPerFrame : %d" ,
aqData.mDataFormat.mBytesPerPacket, aqData.mDataFormat.mBytesPerFrame); //mBytesPerPacket : 4 , mBytesPerFrame : 4
// 1 packet : 4 bytes
// 1 frame : 4 bytes
//녹음 오디오 큐
AudioQueueNewInput ( // 1
&aqData.mDataFormat, // 2. 녹음에 사용할 오디오 데이터 형식
HandleInputBuffer, // 3. 녹음 오디오 큐에 사용할 콜백 함수
&aqData, // 4. 오디오 큐의 사용자 지정 데이터 구조
NULL, // 5. 콜백이 호출될 실행루프?
kCFRunLoopCommonModes, // 6
0, // 7
&aqData.mQueue // 8. 출력시에 새로 할당 된 녹음 대기열
);
//완전한 형식 설명을 얻으려면 아래와 같이 함수를 호출한다 .
//1. 오디오 큐에 오디오 데이터 형식을 쿼리 할 때 사용할 예상되는 속성 값 크기를 가져옵니다.
UInt32 dataFormatSize = sizeof (aqData.mDataFormat);
NSLog(@"dataFormatSize: %u" , (unsigned int)dataFormatSize);
AudioQueueGetProperty ( // 2. 오디오 큐의 지정된 속성에 대한 값을 가져온다.
aqData.mQueue, // 3. 오디오 데이터 형식을 취득하기 위한 오디오 큐.
kAudioQueueProperty_StreamDescription, // 4.오디오 큐의 데이터 형식의 값을 얻기 위한 프로퍼티 id
// in Mac OS X, instead use
// kAudioConverterCurrentInputStreamDescription
&aqData.mDataFormat, // 5. 출력시 AudioStreamBasicDescription오디오 큐에서 얻은 전체 오디오 데이터 형식을 구조체 형식으로 얻습니다.
&dataFormatSize // 6. 입력시 AudioStreamBasicDescription구조 의 예상 크기입니다 . 출력시 실제 크기. 녹음 응용 프로그램은이 값을 사용할 필요가 없습니다.
);
//오디오 파일은 이전에 오디오 큐의의 사용자 지정 구조에 저장된 데이터 형식 및 파일 형식 사양을 사용합니다.
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *filePath = [NSString stringWithFormat:@"%@/%@", docDir, @"voice.wav"];
NSLog(@" * 저장경로 : %@" , filePath);
NSURL * tmpURL = [NSURL URLWithString:filePath];
CFURLRef url = (__bridge CFURLRef) tmpURL;
// CFURLRef audioFileURL = (__bridge CFURLRef)
// CFURLCreateFromFileSystemRepresentation ( // 1. 녹음할 파일 나타내는 CFURL 객체 생성
// NULL, // 2. 현재 기본 메모리 할당자
// (const UInt8 *) tmpURL, // 3. CFURL 객체로 변환 할 파일 시스템 경로입니다.
// strlen (tmpURL), // 4.파일 시스템 경로의 바이트 수
// false // 5. filePath가 폴더가 아니라 파일임을 나타냄
// );
AudioFileCreateWithURL ( // 6.새 오디오 파일을 만들거나 기존 파일을 초기화합니다.
url, // 7. 새 오디오 파일을 만들거나 기존 파일의 경우 초기화 할 URL입니다.
fileType, // 8. 새 파일의 파일 유형입니다. AIFF
&aqData.mDataFormat, // 9. 파일로 기록 될 오디오의 데이터 형식으로 AudioStreamBasicDescription구조 로 지정됩니다 .
kAudioFileFlags_EraseFile, // 10.파일이 이미있는 경우 파일을 지 웁니다.
&aqData.mAudioFile // 11. 출력시 AudioFileID녹음 할 오디오 파일을 나타내는 오디오 파일 객체 (유형 )입니다.
);
//오디오 큐 버퍼 크기 설정
//10000 * 4
aqData.bufferByteSize = 10000 * aqData.mDataFormat.mBytesPerPacket;
NSLog(@"aqData.bufferByteSize (버퍼 사이즈 ) : %u" , (unsigned int)aqData.bufferByteSize);
//오디오 큐 버퍼셋트 준비
for (int i = 0; i <kNumberBuffers; ++ i) {
//1. 각 오디오 큐 버퍼를 할당하고 큐에 넣기 위해 반복합니다.
AudioQueueAllocateBuffer ( //2. 오디오 큐에 오디오 큐 버퍼를 할당하도록 요청하는 함수
aqData.mQueue, // 3. 할당을 수행하고 버퍼를 소유 할 오디오 큐.
aqData.bufferByteSize, // 4. 할당되고있는 새로운 오디오 큐 버퍼의 사이즈 (바이트 단위).
& aqData.mBuffers [i] // 5. 출력시에, 새롭게 할당 된 오디오 큐 버퍼. 버퍼에 대한 포인터는 오디오 큐와 함께 사용중인 사용자 정의 구조에 배치됨
);
AudioQueueEnqueueBuffer ( //6.오디오 대기열 버퍼를 버퍼 큐 끝에 추가하는 함수.
aqData.mQueue, // 7.버퍼를 추가 할 버퍼 큐가 있는 오디오 큐입니다.
aqData.mBuffers [i], // 8. 큐에 넣을 오디오 큐 버퍼
0, // 9. 이 매개 변수는 레코딩 할 버퍼를 대기열에 넣을 때 사용되지 않습니다
NULL // 10. 이 매개 변수는 레코딩 할 버퍼를 대기열에 넣을 때 사용되지 않습니다
);
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
파일
'ios 뽀개기 > objective-c' 카테고리의 다른 글
core audioqueue player 에 대하여 2(추가설명) (0) | 2019.03.29 |
---|---|
core audioqueue player 에 대하여 (0) | 2019.03.28 |
ios 카메라 화면 자동회전 (camera auto rotation) (2) | 2019.03.08 |
코어오디오 MediaPlayer 구현 (1) | 2019.03.04 |
엔터프라이즈 계정으로 배포 (0) | 2019.02.15 |
댓글