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

core audioqueue player 에 대하여 2(추가설명)

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

core audioqueue player 에 대하여 2(추가설명)

 

지난번 글에서 오디오 큐로 오디오 플레이어를 구현하는 법을 포스팅 했다테스트를 하면서 확인해 본결과  오디오 파일을 읽을 읽을 패킷 값을 UInt32 numPackets = 10*1000;  이런식으로 고정값으로 해놓으니 런타임이 오디오에서는 콜백을 타지 않는 현상이 발견했다. 그래서  DeriveBufferSize 함수를 만들어서 버퍼 사이즈와 한번에 읽을 패킷 수를 유동적으로 구하기로 했다.

지난번에 로그를 확인하는 시간을 안가졌다오디오 큐가 오디오 파일을 어떻게 읽는지, 읽은 값은 어떻게 되는지. 오디오 파일을 읽은 오디오 큐가  콜백 함수를 어떻게 호출 하는지.. 콜백 함수 내부에서는 어떤일이 일어나는지 등등 로그를 통해서 알아보자.

6 크기의 caf 오디오 파일 사용했다.

[prepare 버튼눌렀을때]

아래 로그를 보면 오디오 큐가 읽을 오디오 파일을 열고 파일을 읽은 다음 전체 프레임 사이즈와 전체 프레임, 최대 패킷 크기, 버퍼의 사이즈, 읽을 패킷 수를 결정한다그리고 오디오에서 읽은 데이터를 버퍼에 3 할당해 준다 과정에서 콜백 함수에서, 오디오 파일의 데이터를 버퍼에 넣주기(버퍼에 읽은 데이터의 바이트 할당) -> 버퍼를 큐에 넣기 -> 큐는 버퍼의 오디오 데이터를 출력장치로 보냄.이런 과정이 발생한다. 

2019-03-29 16:59:35.750459+0900 ApplePlayer[1847:308425] open file file:///var/containers/Bundle/Application/45956846-0198-496B-BC44-9FA04D7E7650/ApplePlayer.app/01.caf success!

2019-03-29 16:59:35.750704+0900 ApplePlayer[1847:308425] outDataSize( 몇초??) : 6

2019-03-29 16:59:35.750885+0900 ApplePlayer[1847:308425] sizeOfTotalFrames : 8

2019-03-29 16:59:35.750906+0900 ApplePlayer[1847:308425] totalFrames : 264600

2019-03-29 16:59:35.752062+0900 ApplePlayer[1847:308425] AudioFileGetProperty - result : 0

2019-03-29 16:59:35.752092+0900 ApplePlayer[1847:308425] maxPacketSize : 4

2019-03-29 16:59:35.754947+0900 ApplePlayer[1847:308425] propertySize : 4

2019-03-29 16:59:35.754998+0900 ApplePlayer[1847:308425] pAqData.bufferByteSize : 88200

2019-03-29 16:59:35.755024+0900 ApplePlayer[1847:308425] pAqData.mNumPacketsToRead : 22050

 

2019-03-29 16:59:35.755384+0900 ApplePlayer[1847:308425] HandleOutputBuffer 콜백함수 진입 - !

2019-03-29 16:59:35.755422+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -1 : 88200

2019-03-29 16:59:35.756181+0900 ApplePlayer[1847:308425] 오디오 파일의 바이트 데이터를 버퍼에 넣었다.

2019-03-29 16:59:35.756226+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -2 : 88200

2019-03-29 16:59:35.756258+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷의 인덱스 -  : 0

2019-03-29 16:59:35.756288+0900 ApplePlayer[1847:308425] 실제로 읽힌 패킷수  -  : 22050

2019-03-29 16:59:35.756318+0900 ApplePlayer[1847:308425]  오디오 버퍼에 읽은 데이터의 바이트 수를 할당한 : 88200

2019-03-29 16:59:35.759313+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷의 인덱스 -3  : 22050

 

2019-03-29 16:59:35.759505+0900 ApplePlayer[1847:308425] HandleOutputBuffer 콜백함수 진입 - !

2019-03-29 16:59:35.759539+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -1 : 88200

2019-03-29 16:59:35.760260+0900 ApplePlayer[1847:308425] 오디오 파일의 바이트 데이터를 버퍼에 넣었다.

2019-03-29 16:59:35.760307+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -2 : 88200

2019-03-29 16:59:35.760339+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷의 인덱스 -  : 22050

2019-03-29 16:59:35.760369+0900 ApplePlayer[1847:308425] 실제로 읽힌 패킷수  -  : 22050

2019-03-29 16:59:35.760400+0900 ApplePlayer[1847:308425]  오디오 버퍼에 읽은 데이터의 바이트 수를 할당한 : 88200

2019-03-29 16:59:35.760630+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷킷수  -  : 22050

2019-03-29 16:59:35.760400+0900 ApplePlayer[1847:308425]  오디오 버퍼에 읽은 데이터의 바이트 수를 할당한 : 88200

2019-03-29 16:59:35.760630+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷\354\235 인덱스 -3  : 44100

 

2019-03-29 16:59:35.763228+0900 ApplePlayer[1847:308425] HandleOutputBuffer 콜백함수 진입 - !

2019-03-29 16:59:35.763270+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -1 : 88200

2019-03-29 16:59:35.764145+0900 ApplePlayer[1847:308425] 오디오 파일의 바이트 데이터를 버퍼에 넣었다.

2019-03-29 16:59:35.764195+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -2 : 88200

2019-03-29 16:59:35.764219+0900 ApplePlayer[1847:308425] 킷수  -  : 22050

2019-03-29 16:59:35.760400+0900 ApplePlayer[1847:308425]  오디오 버퍼에 읽은 데이터의 바이트 수를 할당한 : 88200

2019-03-29 16:59:35.760630+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷\354\235 인덱스 -3  : 44100

2019-03-29 16:59:35.763228+0900 ApplePlayer[1847:308425] HandleOutputBuffer 콜백함수 진입 - !

2019-03-29 16:59:35.763270+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -1 : 88200

2019-03-29 16:59:35.764145+0900 ApplePlayer[1847:308425] 오디오 파일의 바이트 데이터를 버퍼에 넣었다.

2019-03-29 16:59:35.764195+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -2 : 88200

2019-03-29 16:59:35.764219+0900 ApplePlayer[1847:308425] \354오디오 파일에서 읽을 패킷의 인덱스 -  : 44100

2019-03-29 16:59:35.764241+0900 ApplePlayer[1847:308425] 실제로 읽힌 패킷수  -  : 22050

2019-03-29 16:59:35.764265+0900 ApplePlayer[1847:308425]  오디오 버퍼에 읽은 데이터의 바이트 수를 할당한 : 88200

2019-03-29 16:59:35.764380+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷의 인덱스 -3  : 66150

======================================================================================================

[Play 버튼 눌렀을 때]

이곳에서는 값들의 변화를 자세히 살펴보자파일에서 읽은 바이트 값은 88200 동일하다패킷인덱스는 계속 증가한다. 얼만틈?   22050만큼 그럼 숫자는 어디서 나왔나pAqData.mNumPacketsToRead : 22050  오디오 큐가 파일 내용을 읽을 이미 위에서 계산되었다.  이런식으로 콜백함수는 파일에서 읽은 바이트 값을 버퍼에 넣고 패킷을 읽고 늘려가면서 버퍼의 데이터를 출력장치로 보낸다. ,

2019-03-29 17:00:02.458513+0900 ApplePlayer[1847:308425] 플레이 버튼 클릭

2019-03-29 17:00:03.096833+0900 ApplePlayer[1847:308425] HandleOutputBuffer 콜백함수 진입 - !

2019-03-29 17:00:03.097001+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -1 : 88200

2019-03-29 17:00:03.099052+0900 ApplePlayer[1847:308425] 오디오 파일의 바이트 데이터를 버퍼에 넣었다.

2019-03-29 17:00:03.099208+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -2 : 88200

2019-03-29 17:00:03.099331+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷의 인덱스 -  : 66150

2019-03-29 17:00:03.099404+0900 ApplePlayer[1847:308425] 실제로 읽힌 패킷수  -  : 22050

2019-03-29 17:00:03.099475+0900 ApplePlayer[1847:308425]  오디오 버퍼에 읽은 데이터의 바이트 수를 할당한 : 88200

2019-03-29 17:00:03.099984+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷의 인덱스 -3  : 88200

 

2019-03-29 17:00:03.587594+0900 ApplePlayer[1847:308425] HandleOutputBuffer 콜백함수 진입 - !

2019-03-29 17:00:03.587771+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -1 : 88200

2019-03-29 17:00:03.590197+0900 ApplePlayer[1847:308425] 오디오 파일의 바이트 데이터를 버퍼에 넣었다.

2019-03-29 17:00:03.590332+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -2 : 88200

2019-03-29 17:00:03.590405+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷의 인덱스 -  : 88200

2019-03-29 17:00:03.590473+0900 ApplePlayer[1847:308425] 실제로 읽힌 패킷수  -  : 22050

2019-03-29 17:00:03.590543+0900 ApplePlayer[1847:308425]  오디오 버퍼에 읽은 데이터의 바이트 수를 할당한 : 88200

2019-03-29 17:00:03.590899+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷의 인덱스 -3  : 110250

 

2019-03-29 17:00:04.098978+0900 ApplePlayer[1847:308425] HandleOutputBuffer 콜백함수 진입 - !

2019-03-29 17:00:04.099032+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -1 : 88200

2019-03-29 17:00:04.100639+0900 ApplePlayer[1847:308425] 오디오 파일의 바이트 데이터를 버퍼에 넣었다.

2019-03-29 17:00:04.100678+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -2 : 88200

2019-03-29 17:00:04.100695+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷의 인덱스 -  : 110250

2019-03-29 17:00:04.100717+0900 ApplePlayer[1847:308425] 실제로 읽힌 패킷수  -  : 22050

2019-03-29 17:00:04.100791+0900 ApplePlayer[1847:308425]  오디오 버퍼에 읽은 데이터의 바이트 수를 할당한 : 88200

2019-03-29 17:00:04.100948+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷의 인덱스 -3  : 132300

 

2019-03-29 17:00:04.590124+0900 ApplePlayer[1847:308425] HandleOutputBuffer 콜백함수 진입 - !

2019-03-29 17:00:04.590293+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -1 : 88200

2019-03-29 17:00:04.592524+0900 ApplePlayer[1847:308425] 오디오 파일의 바이트 데이터를 버퍼에 넣었다.

2019-03-29 17:00:04.592640+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -2 : 88200

2019-03-29 17:00:04.592800+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷의 인덱스 -  : 132300

2019-03-29 17:00:04.592935+0900 ApplePlayer[1847:308425] 실제로 읽힌 패킷수  -  : 22050

2019-03-29 17:00:04.593011+0900 ApplePlayer[1847:308425]  오디오 버퍼에 읽은 데이터의 바이트 수를 할당한 : 88200

2019-03-29 17:00:04.593414+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷의 인덱스 -3  : 154350

(생략)

2019-03-29 17:00:07.086121+0900 ApplePlayer[1847:308425] HandleOutputBuffer 콜백함수 진입 - !

2019-03-29 17:00:07.086292+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -1 : 88200

2019-03-29 17:00:07.088785+0900 ApplePlayer[1847:308425] 오디오 파일의 바이트 데이터를 버퍼에 넣었다.

2019-03-29 17:00:07.088933+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -2 : 88200

2019-03-29 17:00:07.089010+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷의 인덱스 -  : 242550

2019-03-29 17:00:07.089080+0900 ApplePlayer[1847:308425] 실제로 읽힌 패킷수  -  : 22050

2019-03-29 17:00:07.089152+0900 ApplePlayer[1847:308425]  오디오 버퍼에 읽은 데이터의 바이트 수를 할당한 : 88200

2019-03-29 17:00:07.089590+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷의 인덱스 -3  : 264600

 

(실제로 읽힌 패킷수 0) 세번

 

2019-03-29 17:00:07.598063+0900 ApplePlayer[1847:308425] HandleOutputBuffer 콜백함수 진입 - !

2019-03-29 17:00:07.598231+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -1 : 88200

2019-03-29 17:00:07.598313+0900 ApplePlayer[1847:308425] 오디오 파일의 바이트 데이터를 버퍼에 넣었다.

2019-03-29 17:00:07.598385+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -2 : 0

2019-03-29 17:00:07.598453+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷의 인덱스 -  : 264600

2019-03-29 17:00:07.598519+0900 ApplePlayer[1847:308425] 실제로 읽힌 패킷수  -  : 0

 

 

2019-03-29 17:00:08.090019+0900 ApplePlayer[1847:308425] HandleOutputBuffer 콜백함수 진입 - !

2019-03-29 17:00:08.090202+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -1 : 88200

2019-03-29 17:00:08.090295+0900 ApplePlayer[1847:308425] 오디오 파일의 바이트 데이터를 버퍼에 넣었다.

2019-03-29 17:00:08.090367+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -2 : 0

2019-03-29 17:00:08.090435+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷의 인덱스 -  : 264600

2019-03-29 17:00:08.090501+0900 ApplePlayer[1847:308425] 실제로 읽힌 패킷수  -  : 0

 

 

2019-03-29 17:00:08.624193+0900 ApplePlayer[1847:308425] HandleOutputBuffer 콜백함수 진입 - !

2019-03-29 17:00:08.624527+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -1 : 88200

2019-03-29 17:00:08.624633+0900 ApplePlayer[1847:308425] 오디오 파일의 바이트 데이터를 버퍼에 넣었다.

2019-03-29 17:00:08.624709+0900 ApplePlayer[1847:308425] 파일에서 읽은 바이트 -2 : 0

2019-03-29 17:00:08.624827+0900 ApplePlayer[1847:308425] 오디오 파일에서 읽을 패킷의 인덱스 -  : 264600

2019-03-29 17:00:08.624897+0900 ApplePlayer[1847:308425] 실제로 읽힌 패킷수  -  : 0

 

뷰화면

 

소스코드

/*
 - 순서
 1. 상태, 형식 및 경로 정보를 관리하는 사용자 지정구조를 정의한다.
 2. 실제 재생을 수행하는 오디오 큐 콜백 함수를 작성한다.
 3.오디오 큐 버퍼에 적합한 크기를 결정하는 코드를 작성한다.
 4.재생할 오디오 파일을 열고 오디오 데이터 형식을 결정한다.
 5. 재생오디오 큐를 생성하고 재생하기 위해서 셋팅한다.
 6. 오디오 큐 버퍼를 할당하고, 오디오 큐에 넣는다. 오디오 큐에 재생이 시작될 것을 알린다.
 재생이 완료되면 재생 콜백은 오디오 큐에 중지를 알린다.
 7. 오디오 큐 삭제, 리소스 해제하기
 */

#import <UIKit/UIKit.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#import <CoreAudioKit/CoreAudioKit.h>

//step 1. 시작 전 오디오 형식 및 오디오 큐 상태 정보를 관리하는 데 사용할 사용자 지정 구조를 정의 해야 한다.

static const int kNumberBuffers = 3;                                               //1. 사용할 오디오 큐 버퍼 수 정하기

struct AQPlayerState {
    AudioStreamBasicDescription mDataFormat;                            // 2.파일의 오디오 데이터 형식을 나타냄, mQueue 필드에 지정된 오디오 큐에 사용된다.
    AudioQueueRef mQueue;                                                            // 3. app에서 생성하는 재생 오디오 큐
    AudioQueueBufferRef mBuffers [kNumberBuffers];                  // 4.오디오 큐가 관리하는 오디오 큐 버퍼의 포인터를 보관하는 배열
    AudioFileID mAudioFile;                                                               // 5. 프로그램에서 재생하는 오디오 파일을 나타내는 오디오 파일 객체.
    UInt32 bufferByteSize;                                                                 // 6.각 오디오 큐 버퍼의 크기 (바이트)
    SInt64 mCurrentPacket;                                                               // 7.오디오 파일에서 재생할 다음 패킷의 패킷 인덱스
    UInt32 mNumPacketsToRead;                                                      // 8.오디오 큐의 재생 콜백이 호출될때마다 읽을 패킷의 수
    AudioStreamPacketDescription * mPacketDescs;                      // 9. vbr일 경우 재생중인 파일에 대한 패킷 설명. cbr일 경우 null
    bool mIsRunning;                                                                          // 10. 오디오 큐가 실행중인지 여부를 나타내는 부울 값.
};

@interface ViewController : UIViewController


@end

 

#import "ViewController.h"

#define VErr(err, msg)  do {\
if(nil != err) {\
NSLog(@"[ERR]:%@--%@", (msg), [err localizedDescription]);\
return ;\
}\
} while(0)


#define VStatus(err, msg) do {\
if(noErr != err) {\
NSLog(@"[ERR-%d]:%@", err, (msg));\
return ;\
}\
} while(0)


#define VStatusBOOL(err, msg) do {\
if(noErr != err) {\
NSLog(@"[ERR-%d]:%@", err, (msg));\
return NO;\
}\
} while(0)



//step2. 재생 오디오 큐 콜백 함수 작성
// 기능
//* 오디오 파일에서 지정된 양의 데이터를 읽어서 오디오 버퍼에 삽입.
//* 오디오 버퍼를 큐에 넣는다.
//* 오디오 파일에서 읽을 데이터가 없을 때 오디오 대기열에 중지 할 것을 알린다.
static void HandleOutputBuffer (
                                                        void * aqData,                                   // 1. 오디오 큐에 대한 상태 정보
                                                        AudioQueueRef inAQ,                      // 2. 이 콜백을 소유한 오디오 큐
                                                        AudioQueueBufferRef inBuffer       // 3. 콜백이 오디오 파일에서 읽은 데이터를 넣을 버퍼.
){
    

    NSLog(@"HandleOutputBuffer 콜백함수 진입 - !");
   
    //step 2-1. 오디오 큐에 대한 상태 정보
      struct AQPlayerState *pAqData = (struct AQPlayerState *) aqData;
    
    
    //NSLog(@"pAqData->mIsRunning : %@" , pAqData->mIsRunning ? @"yes" : @"no"); //no 면 정지 됨
   // if (pAqData->mIsRunning == 0) return;                                               //. 오디오 큐가 중지되면 리턴.
    
    
    UInt32 numBytesReadFromFile  = pAqData->bufferByteSize;          //. 재생중인 파일에서 읽은 오디오 데이터의 바이트 값을 가지는 변수
    //UInt32 numPackets = 10*1000;
    UInt32 numPackets = pAqData->mNumPacketsToRead;                 //. 재생중인 파일에서 읽을 패킷수.  -
    
    NSLog(@"파일에서 읽은 바이트 값 -1 : %d" , (int)numBytesReadFromFile);
  
    //step 2-2. 오디오 파일의 데이터를 판독해서 버퍼에 넣는 역할 하는 함수 작성 (오디오 데이터 -> 버퍼)
    OSStatus stts =  AudioFileReadPacketData (
                                              pAqData->mAudioFile,                         //2. 읽을 오디오 파일
                                              NO,                                                        // 3. 함수가 읽을 때 데이터를 캐시 (x)
                                              &numBytesReadFromFile,                   // 4. 출력시 오디오 파일에서 읽은 오디오 데이터의 바이트 수.
                                              pAqData->mPacketDescs,                  // 5. 출력시 오디오 파일에서 읽은 데이터에 대한 패킷의 설명. cbr = null
                                              pAqData->mCurrentPacket,               // 6. 오디오 파일에서 읽을 첫번째 패킷의 인덱스.
                                              &numPackets,                                      // 7. 입력시 오디오 파일에서 읽을 패킷수. 출력시 실재로 읽힌 패킷수.
                                              inBuffer->mAudioData                        // 8. 출력시 오디오 파일에서 읽은 데이터가 들어 있는 버퍼.
                                                                            );
    
    VStatus(stts, @"AudioFileReadPacketData");
    NSLog(@"오디오 파일의 바이트 데이터를 버퍼에 넣었다.");
    NSLog(@"파일에서 읽은 바이트 값 -2 : %d" , (int)numBytesReadFromFile);
    NSLog(@"오디오 파일에서 읽을 패킷의 인덱스 -  : %d" , (int)pAqData->mCurrentPacket);
    NSLog(@"실제로 읽힌 패킷수  -  : %d" , (int)numPackets);
    
    if (numPackets> 0) {                                                                            //.파일로 부터 오디오 데이터를 읽었는지 여부. 읽었다면 버퍼를 큐에 할당. 못읽었다면 중지.
        
        inBuffer-> mAudioDataByteSize = numBytesReadFromFile;        //. 오디오 큐 버퍼에 읽은 데이터의 바이트 수를 할당.

        NSLog(@" 오디오 큐 버퍼에 읽은 데이터의 바이트 수를 할당한 값 : %d" ,  (int)inBuffer-> mAudioDataByteSize);
      
        //step 2-3. 오디오 파일 데이터가 읽혀서 오디오 큐 버퍼에 저정되어 있는 상태이다. (버퍼 -> 큐)
        //아래 함수 처럼, 콜백이 버퍼를 큐에 넣는다. 큐에 들어가면 버퍼의 오디오 데이터를 큐에서 출력장치로 보낼 수 있다.
        //버퍼를 큐에 추가하는 함수
        AudioQueueEnqueueBuffer (
                                 pAqData-> mQueue,                                                // 2. 버퍼가 담긴 큐를 소유하고 있는 오디오 큐
                                 inBuffer,                                                                    // 3. 큐에 넣을 오디오 버퍼.
                                 (pAqData-> mPacketDescs? numPackets : 0),      // 4. 오디오 큐 버퍼의 데이터  패킷의 수. cbr = 0
                                 pAqData-> mPacketDescs                                       // 5
                                 );
        
        
        pAqData-> mCurrentPacket += numPackets;                                 //.읽은 패킷 수에 따라 패킷 인덱스 증가.
        
        NSLog(@"오디오 파일에서 읽을 패킷의 인덱스 -3  : %d" , (int)pAqData->mCurrentPacket);
     
    } else {
        
        //콜백이 하는 마지막 작업은 재생중인 오디오가 더 이상 읽을 데이터가 없는지 확인하는 것이다.
        //파일의 끝을 발견하면 콜백은 재생 오디오 큐에 멈추라고 이야기 한다.
        //AudioFileReadPackets함수에 의해 읽힌 패킷의 수가 콜백에 의해 이전에 호출 되었는지를 검사한다  패킷이 0 이면 읽을 파일 x.
        AudioQueueStop (                                                    // 2.오디오 큐를 정지하는 함수
                                        pAqData-> mQueue,               // 3.중지할 오디오 큐
                                        false                                         //  4.큐에 저장된 모든 버퍼가 재생되면 오디오 큐를 비동기 적으로 중지한다.
                                    );
        
        pAqData-> mIsRunning = false;                           // 5. 재생이 완료되었음을 나타낸다.
    }
}

//오디오 데이터를 오디오 큐에 전달하기 위해서는 AudioQueueBuffer 라는 객체를 이용한다.
//이 버퍼를 생성하기 전에 오디오 데이터를 분석한다.
//그 후, 어떤 사이즈의 버퍼를 사용할지 결정한다.
// 또, 한번에 몇개의 packet을 읽을지 결정해야 한다.
//아래 함수는 사용할 버퍼의 사이즈를 outBufferSize에 저장하고, 읽을 패킷의 개수를 outNumPacketsToRead에 저장한다.
void DeriveBufferSize (
                       AudioStreamBasicDescription ASBDesc,
                       UInt32                      maxPacketSize,
                       Float64                     seconds,
                       UInt32                      *outBufferSize,
                       UInt32                      *outNumPacketsToRead
                       )
{
    static const int maxBufferSize = 0x50000;                                                                   // 320KB
    static const int minBufferSize = 0x4000;                                                                     //  16KB
    
    if (ASBDesc.mFramesPerPacket != 0) {                                                                        // 1 . 오디오 데이터의 ASBD의 패킷당 프레임 개수가 일정하다는 뜻.
        
        //주어진 시간에 처리할 수 있는 패킷의 개수(numPacketsForTime)를 구할 수 있다.
        //주어진 시간에 필요한 버퍼의 크기(outBufferSize)를 계산할 수 있다.
        Float64 numPacketsForTime = ASBDesc.mSampleRate / ASBDesc.mFramesPerPacket * seconds;
        *outBufferSize = numPacketsForTime * maxPacketSize;
        
    } else {                                                                                                                             // 2. 패킷당 프레임 개수가 다르다면
       
        *outBufferSize = maxBufferSize > maxPacketSize ? maxBufferSize : maxPacketSize;  //maxPacketSize는 (AudioFileGetProperty 함수로 구함.)
    }
    
    if (*outBufferSize > maxBufferSize &&                                                                       // 3.얻은값이 너무 크다면 maxBufferSize에 맞춰준다.
        *outBufferSize > maxPacketSize
        )
        *outBufferSize = maxBufferSize;
    
    else {                                                                                                                              // 4. 얻은 값이 너무 작다면 minBufferSize에 맞춰 준다.
        if (*outBufferSize < minBufferSize){
            *outBufferSize = minBufferSize;
        }
    }
    
    *outNumPacketsToRead = *outBufferSize / maxPacketSize;                                    // 5.버퍼의 크기와 최대 패킷 크기를 알면 콜백함수가 한번에 읽을 수 있는 패킷 개수를 구할 수 있다.
                                                                                                                                          //maxPacketSize는 (AudioFileGetProperty 함수로 구함.).  4,   1052
}

@interface ViewController (){
    struct AQPlayerState pAqData;
}
@property (weak, nonatomic) IBOutlet UILabel *sizeOfTotalFrame;     //전체 프레임 크기
@property (weak, nonatomic) IBOutlet UILabel *totalFrame;               //전체 프레임
@property (weak, nonatomic) IBOutlet UILabel *maxPacketSize;       // 최대 패킷 사이즈
@property (weak, nonatomic) IBOutlet UILabel *bufferByteSize;       //버퍼의 크기
@property (weak, nonatomic) IBOutlet UILabel *packetToRead;         //읽을 패킷
@property (weak, nonatomic) IBOutlet UITextView *logs;

@property (weak, nonatomic) IBOutlet UIButton *play;
@property (weak, nonatomic) IBOutlet UIButton *stop;
@property (weak, nonatomic) IBOutlet UIButton *prepare;
@property (weak, nonatomic) IBOutlet UILabel *totalDuration;        //전체 길이 (초)

@property (strong, nonatomic) NSURL *musicURL;                          //오디오 파일 경로
@end

@implementation ViewController

//step3. AVAudioSession 설정 -AVAudioSessionCategorySoloAmbient 로 설정.
- (void) setAudioSession {
    
    NSError *error;
    AVAudioSession *session = [AVAudioSession sharedInstance];
    [session setActive:YES error:&error];
   
    if (nil != error) {
        NSLog(@"AudioSession setActive 에러 발생:%@", error.localizedDescription);
        return;
    }
    
    error = nil;
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategorySoloAmbient error:&error];
    if (nil != error) {
        NSLog(@"AudioSession setCategory(AVAudioSessionCategoryPlayAndRecord) 에러 발생:%@", error.localizedDescription);
        return;
    }
}


- (void)viewDidLoad {
    [super viewDidLoad];
    _logs.editable = NO; //텍스트 뷰  readonly
}

//오디오 파일 준비
- (BOOL) prepareAudioFile {
    //step 3-1 읽을 오디오 파일 셋팅 및 초기화
    [self setAudioSession];
    
    //step4.오디오 파일에 대한 CFURL 객체 가져 오기
    CFURLRef audioFileURL = (__bridge CFURLRef) _musicURL;
    //nill 이면 로컬에 있는 파일 재생하기
    if (nil == audioFileURL) {
        audioFileURL = (__bridge CFURLRef) [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"01" ofType:@"caf"]];
    }
    
    
    //step5. 재생을 위해 오디오 파일열기
    OSStatus result =
    AudioFileOpenURL (                                                                                  // 2. 재생할 파일을 여는 함수
                      audioFileURL,                                                                          // 3.  재생할 파일 참조
                      kAudioFileReadPermission,                                                     //4.  재생 중인 파일에 사용할 파일 권한. File Access Permission Constants열거 형에 정의되어있음.
                      0,                                                                                               //5. 파일 타입 관련 옵션. 0은 이 기능을 사용하지 않겠다.
                      &pAqData.mAudioFile                                                              //6. 출력시 오디오 파일에 대한 참조가 사용자 정의 구조 mAudioFile 필드에 배치 된다.
                      );
    
    //CFRelease (audioFileURL);                                                                     //7. 위에서 만든 CFURL 개체 해제.
    VStatusBOOL(result, @"AudioFileOpenURL");
    NSLog(@"open file %@ success!", audioFileURL);
    
    //step6. 파일의 오디오 데이터 형식 얻기
    UInt32 dataFormatSize = sizeof (pAqData.mDataFormat);                                // 1. 오디오 파일의 오디오 데이터 형식을 쿼리 할 때 사용할 예상 속성 값 크기를 가져온다.
    NSLog(@"dataFormatSize : %u" , (unsigned int)dataFormatSize);
    result = AudioFileGetProperty (                                                                             //2. 오디오 파일의 지정된 속성의 값을 획득
                                   pAqData.mAudioFile,                                                               //3. AudioFileID 오디오 데이터 형식을 가져올 파일을 나타내는 오디오 파일 객체
                                   kAudioFilePropertyDataFormat,                                             //4. 오디오 파일의 데이터 형식 값을 얻기 위한 속성 ID.
                                   &dataFormatSize,                                                                    //5. 입력시 AudioStreamBasicDescription오디오 파일의 데이터 형식을 설명하는 구조체의 예상 크기. 출력시 사용 x
                                   &pAqData.mDataFormat                                                          //6. 출력시 AudioStreamBasicDescription 오디오 파일에서 가져온 전체 오디오 데이터 형식을 구조체 형식으로 출력. 파일의 오디오 데이터 형식을 오디오 큐의 사용자 지정 구조에 저장하여 오디오 대기열에 적용
                                   );
    VStatusBOOL(result, @"AudioFileGetProperty - kAudioFilePropertyDataFormat");
    NSLog(@"result : %d" , (int)result);
    NSLog(@"dataFormatSize : %u" , (unsigned int)dataFormatSize);
    
    
    
    //step6-2. 파일의 오디오 길이 얻기
    Float64 outDataSize = 0;
    UInt32 thePropSize = sizeof(Float64);
    result = AudioFileGetProperty(
                                                      pAqData.mAudioFile,
                                                      kAudioFilePropertyEstimatedDuration,
                                                      &thePropSize,
                                                      &outDataSize
                                                    );
    
    VStatusBOOL(result, @"AudioFileGetProperty - duration");
    NSLog(@"result : %d" , (int)result);
    NSLog(@"thePropSize : %u" , (unsigned int)thePropSize);
    NSLog(@"outDataSize( 몇초??) : %d" , (int)outDataSize);
    NSString *myOutDataSizeString =  [[NSNumber numberWithInt: (int)outDataSize] stringValue];
    [_totalDuration setText:myOutDataSizeString];                                                       //전체 길이에 셋팅해주기
    
    
     //step6-3. 파일의 전체 오디오 데이터 패킷
    UInt64 totalFrames;     //전체 패킷
    UInt32 sizeOfTotalFrames = sizeof(UInt64);
    AudioFileGetProperty (
                                          pAqData.mAudioFile,
                                          kAudioFilePropertyAudioDataPacketCount,
                                          &sizeOfTotalFrames,
                                          &totalFrames
                                        );
    VStatusBOOL(result, @"kAudioFilePropertyAudioDataPacketCount");
    NSLog(@"result : %d" , (int)result);
    NSLog(@"sizeOfTotalFrames : %d" , ( int)sizeOfTotalFrames);                         //
    NSLog(@"totalFrames : %d" , (int)totalFrames);                                                 //
    
    NSString *mySizeOfTotalFrames =  [[NSNumber numberWithInt:(int)sizeOfTotalFrames] stringValue];
    NSString *myTotalFrames =  [[NSNumber numberWithInt: (int)totalFrames] stringValue];
    [_sizeOfTotalFrame setText:mySizeOfTotalFrames];   //전체 프래레임 사이즈 셋팅
    [_totalFrame setText:myTotalFrames];                        //전체 프레임 셋팅
    
    
    //step7. 재생오디오 큐 만들기: 이전 단계에서 구성한 사용자 지정 구조 및 콜백과 재생할 파일의 오디오 데이터 형식을 사용한다.
    result = AudioQueueNewOutput (                                                           // 1
                                  &pAqData.mDataFormat,                                         // 2. 오디오 큐가 재생되도록 설정된 파일의 오디오 데이터 형식이다.
                                  HandleOutputBuffer,                                                // 3. 재생 오디오 큐와 함께 사용할 콜백 함수.
                                  &pAqData,                                                                // 4. 재생 오디오 큐의 사용자 지정 데이터 구조.
                                  CFRunLoopGetCurrent (),                                      // 5. 현재 실행중인 루프와 오디오 큐 재생 콜백이 호출되는 루프.
                                  kCFRunLoopCommonModes,                                // 6. 콜백을 호출 할 수 있는 실행 루프 모드.
                                  0,                                                                             // 7.
                                  &pAqData.mQueue                                                // 8. 출력시 새로 할당 된 재생 오디오 큐.
                                  );
    VStatusBOOL(result, @"AudioQueueNewOutput");
    

    //step 8. 재생 오디오 큐의 크기 설정 : 오디오 큐에 버퍼를 할당할때와 오디오 파일을 읽기 시작하기 전에 사용
    //설정방법: 오디오 큐의 버퍼 크기, 재생 오디오 큐 콜백 호출 마다 읽을 패킷 수, 버퍼 하나의 오디오 데이터 패킷 설명을 보관 유지 하기 위한 배열 사이즈
    //읽을 버퍼 크기 및 패킷 수 설정 : 여기에서 각 오디오 큐 버퍼의 크기를 설정하고, 재생 오디오 큐 콜백을 호출할때 마다 읽을 패킷 수를 결정한다.
    UInt32 maxPacketSize;
    UInt32 propertySize = sizeof (maxPacketSize);
    result = AudioFileGetProperty (                                                                                                // 1.  재생하려는 파일의 오디오 데이터 패킷 크기에 대한 max를 바이트 단위로 구하기
                                                       pAqData.mAudioFile,                                                             //2. 재생할 파일을 나타내는 오디오 파일 객체.
                                                       kAudioFilePropertyPacketSizeUpperBound,                       //3.오디오 파일 패킷 사이즈를 얻기위한 프로퍼티 id
                                                       &propertySize,                                                                    // 4. 출력시 kAudioFilePropertyPacketSizeUpperBound 속성의 크기
                                                       &maxPacketSize                                                                 // 5. 출력시 재생할 파일의 패킷 크기에 대한 max 값
                                   );
    VStatusBOOL(result, @"AudioFileGetProperty");
    
    
    //bufferByteSize 와 mNumberPacketToRead을 구한다.
    DeriveBufferSize (
                                  pAqData.mDataFormat,
                                  maxPacketSize,
                                  0.5,
                                  &pAqData.bufferByteSize,
                                  &pAqData.mNumPacketsToRead
                                );
    
    
    // kNumberPackages = 10*1000,
    //pAqData.bufferByteSize = (10*1000) * maxPacketSize;
    //pAqData.mPacketDescs =(AudioStreamPacketDescription *) malloc((10*1000) * sizeof(AudioStreamPacketDescription));
    
    NSLog(@"AudioFileGetProperty - result : %d" , (int)result);
    NSLog(@"maxPacketSize : %d" , (int)maxPacketSize);
    NSLog(@"propertySize : %d" , (int)maxPacketSize);
    NSLog(@"pAqData.bufferByteSize : %d" ,(int)pAqData.bufferByteSize);
     NSLog(@"pAqData.mNumPacketsToRead : %d" ,(int)pAqData.mNumPacketsToRead);
    
    NSString *myMaxPacketSize =  [[NSNumber numberWithInt:(int)maxPacketSize] stringValue];
    NSString *myBufferByteSize =  [[NSNumber numberWithInt:(int)pAqData.bufferByteSize] stringValue];
    NSString *myNumPacketsToRead =  [[NSNumber numberWithInt: (int)pAqData.mNumPacketsToRead] stringValue];
    [_maxPacketSize setText:myMaxPacketSize];                        //최대 패킷 사이즈
    [_bufferByteSize setText:myBufferByteSize];                        //버퍼 사이즈
     [_packetToRead setText:myNumPacketsToRead];                //읽을 패킷 수
    
    
        //step 9. 패킷 설명 배열에 메모리 할당
        //하나의 버퍼에 들어있는 오디오 데이터에 대한 패킷 설명을 보관할 배열의 메모리를 할당한다.
        //오디오 파일의 데이터 형식이 vbr인지 cbr인지 결정.  vbr 데이터에서 패킷당 바이트 수 or 패킷당 프레임 수 중 하나 또는 모두가 가변적이므로 0 이다.
        bool isFormatVBR = (pAqData.mDataFormat.mBytesPerPacket == 0 || pAqData.mDataFormat.mFramesPerPacket == 0);
    
        //vbr데이터가 포함된 오디오 파일의 경우 패킷 설명 배열에 메모리를 할당한다.
        if (isFormatVBR) {
            //재생 콜백을 호출 할 때마다 읽을 오디오 데이터 패킷 수에 따라 필요한 메모리를 계산한다.
            //pAqData.mPacketDescs =(AudioStreamPacketDescription *) malloc((10*1000) * sizeof(AudioStreamPacketDescription));
            pAqData.mPacketDescs = (AudioStreamPacketDescription *) malloc (pAqData.mNumPacketsToRead * sizeof (AudioStreamPacketDescription)); //apple 문서
        } else {
            pAqData.mPacketDescs = NULL;  //선형 pcm과 같은 cbr 데이터가 포함된 오디오 파일의 경우 오디오 대기열은 패킷 설명 배열을 사용하지 않는다.
        }
    
    
    //step 10. 매직 쿠키 설정
    //mpeg 4 aac 와 같은 일부 압축 오디오 포맷은 구조를 사용해서 오디오 메타 데이터를 포함한다. (이 구조를 매직 쿠키라고 함)
    //오디오 큐 서비스를 사용해서 이러한 형식의 파일을 재생할 때 재생을 시작하기 전에 오디오 파일에서 매직쿠키를 가져와서 오디오 큐에 추가.
    //아래는 매직 쿠키를 얻어 오디오 큐에 적용하는 방법. 재생을 시작하기 전에 이 함수를 호출 한다.
    
    UInt32 cookieSize = sizeof (UInt32);                                                                  //1. 매직 쿠키 데이터의 예상 크기 설정
    
    bool couldNotGetProperty =                                                                                //2. AudioFileGetPropertyInfo 함수의 결과를 캡쳐한다. 성공하면 false 반환.
    AudioFileGetPropertyInfo (                                                                                   //3.
                                              pAqData.mAudioFile,                                                   //4.재생할 오디오 파일을 나타내는 오디오 파일 객체.
                                              kAudioFilePropertyMagicCookieData,                        //5.오디오 파일의 매직 쿠키 데이터를 나타내는 속성 ID
                                              &cookieSize,                                                                 //6.입력시 마법 쿠키 데이터의 예상 크기, 출력시 실제 크기.
                                              NULL                                                                             //7.
                              );
    
    if (!couldNotGetProperty && cookieSize) {                                                             //8. 오디오 파일에 매직쿠키가 포함되어 있으면 이를 보관할 메모리 할당.
        
        char* magicCookie = (char *) malloc (cookieSize);
        
        AudioFileGetProperty (                                                                                          // 9. 파일의 매직 쿠키를 가져온다.
                                          pAqData.mAudioFile,                                                       // 10.재생할 오디오 파일을 나타내는 오디오 파일 객체.
                                          kAudioFilePropertyMagicCookieData,                             //11.오디오 파일의 마법 쿠키 데이터를 나타내는 속성 id
                                          &cookieSize,                                                                      //12.입력시 함수 매직 쿠키를 사용하여 얻은 변수의 크기, 출력시 매직 쿠키 변수에 기록된 바이트 크기.
                                          magicCookie                                                                       // 13. 출력시 오디오 파일의 매직쿠키.
                              );
        
        AudioQueueSetProperty (                                                                                // 14. 오디오 큐 속성 설정. 오디오 큐의 매직 쿠키를 설정하여 재생할 오디오 파일의 마법 쿠키와 일치 시킨다.
                               pAqData.mQueue,                                                                // 15. 매직 쿠키를 설정하려는 오디오 큐.
                               kAudioQueueProperty_MagicCookie,                                 // 16.오디오 큐의 매직 쿠키를 나타내는 속성 id
                               magicCookie,                                                                       // 17. 재생할 오디오 파일의 매직 쿠키
                               cookieSize                                                                            // 18. 매직 쿠키의 크기(바이트)
                               );
        
        free (magicCookie);                                                                                    // 19.매직 쿠키에 할당 된 메모리를 해제.
    }
    
    pAqData.mCurrentPacket = 0;                                                                   // 1. 오디오 큐 콜백이 버퍼를 채우기 시작할때, 오디오 파일의 시작 부분에서 시작되도록 패킷 인덱스를 0으로 설정.
    
    for (int i = 0; i < kNumberBuffers; ++i) {                                                   // 2. 오디오 큐 버퍼 세트를 할당 및 준비
        
        AudioQueueAllocateBuffer (                                                                  // 3. 이 함수는 메모리를 할당하여 오디오 큐 버퍼를 만듭니다.
                                                      pAqData.mQueue,                                    // 4. 오디오 큐 버퍼를 할당하고 있는 오디오 큐.
                                                      pAqData.bufferByteSize,                         // 5. 새로운 오디오 큐 버퍼의 사이즈.(바이트)
                                                      &pAqData.mBuffers[i]                             // 6. 출력시 mBuffers 사용자 지정 구조의 배열에 새 오디오 큐 버퍼를 추가한다.
                                                    );


        HandleOutputBuffer (                                                                                            // 7. 재생 오디오 큐 콜백.
                                            &pAqData,                                                                          // 8. 오디오 큐의 사용자 지정 데이터 구조
                                            pAqData.mQueue,                                                             // 9. 콜백하고 있는 오디오 큐
                                            pAqData.mBuffers[i]                                                         // 10. 오디오 큐 콜백에 전달할 오디오 큐 버퍼.
                                            );
    }
    
    Float32 gain = 1.0;                                                                             // 1. , 0 ~ 1.0 (무음~크게)
    
    // 선택적으로 사용자가 설정을 재정의 할 수 있음
    AudioQueueSetParameter (                                                                //2. 오디오 큐에 대한 매개 변수 값을 설정.
                            pAqData.mQueue,                                                      //3.  매개 변수를 설정하려는 오디오 큐.
                            kAudioQueueParam_Volume,                                    // 4. 설정된 매개 변수의 ID
                            gain                                                                             // 5. 오디오 대기열에 적용 할 gain 설정
                            );
    
    return YES;
}


- (IBAction)playBtn:(id)sender {
    NSLog(@"플레이 버튼 클릭");
    pAqData.mIsRunning = true;                                      //1. 큐가 실행중
    
    OSStatus stts = AudioQueueStart (                          // 2. 자체 스레드에서 오디오 큐 시작.
                     pAqData.mQueue,                                      // 3. 시작할 오디오 대기열.
                     NULL                                                           // 4.큐가 즉시 시작해야 함.
                     );
    
    do {                                                                                 // 5. mIsRunning을 정기적으로 체크해서 오디오 큐가 중지되었는지 확인.
        CFRunLoopRunInMode (                                            // 6. 오디오 큐의 스레드가 포함 된 실행 루프를 실행.
                            kCFRunLoopDefaultMode,                     // 7.실행 루프의 기본 모드 사용.
                            0.25,                                                       // 8. 실행 루프의 시간을 0.25초 단위로 설정
                            false                                                       // 9.실행루프가 지정된 전체 시간 동안 계속되어야 함.
                            );
    } while (pAqData.mIsRunning);
    
    CFRunLoopRunInMode (                                             // 10.오디오 큐가 중지된 후 실행 루프를 조금 더 오래 실행해서 현재 재생중인 오디오 대기열 버퍼에 완료 시간이 있는 지 확인.
                        kCFRunLoopDefaultMode,
                        1,
                        false
                        );
     VStatus(stts, @"AudioQueueStart");
}

//큐 정지
- (IBAction)stopBtn:(id)sender {
    OSStatus stts = AudioQueuePause(pAqData.mQueue);
    VStatus(stts, @"AudioQueuePause");
}

//준비 or 해제
- (IBAction)preparebtn:(id)sender {
    
    static BOOL once = NO;
    if (! once ) {
        if (! [self prepareAudioFile]) {
            NSLog(@"dipose error!");
            return ;
        }
        [_prepare setTitle:@"Dispose" forState:UIControlStateNormal];
        once = YES;
    } else {
        if (! [self dipose]) {
            NSLog(@"dipose error!");
            return ;
        }
        [_prepare setTitle:@"Prepare" forState:UIControlStateNormal];
        once = NO;
    }
    
}


- (BOOL) dipose {
    if (pAqData.mQueue) {
        AudioQueueDispose (                                          // 1. 오디오 큐와 해당 버퍼를 포함하여 모든 리소스 삭제.
                           pAqData.mQueue,                             // 2. 삭제할 큐
                           true                                                     // 3. 오디오 큐를 동기적으로 처리
                           );
    }
    
    if (pAqData.mAudioFile) {
        AudioFileClose (pAqData.mAudioFile);                 // 4. 재생 된 오디오 파일을 닫는다. * !주의 CFRelease 하면 이곳에서 에러남.
    }
    
    if (pAqData.mPacketDescs) {
        free (pAqData.mPacketDescs);                            // 5. 패킷 설명을 유지하는데 사용된 메모리 해제.
        pAqData.mPacketDescs = NULL;
    }
    
    return YES;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

 

예제파일

반응형

댓글