본문 바로가기
ios 뽀개기/objective-c

core audio queue service (ios recording)

by 인생여희 2019. 3. 26.
반응형

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
 



파일

AppleRecording.zip

MyAudioQRecorder.zip



반응형

댓글