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
'ios 뽀개기 > objective-c' 카테고리의 다른 글
uuid 구하기(ios 기기 고유번호) (1) | 2019.04.02 |
---|---|
카메라 줌 AVCaptureDevice Zoom (0) | 2019.04.02 |
core audioqueue player 에 대하여 (0) | 2019.03.28 |
core audio queue service (ios recording) (0) | 2019.03.26 |
ios 카메라 화면 자동회전 (camera auto rotation) (2) | 2019.03.08 |
댓글