uuid 구하기(ios 기기 고유번호)

과거 iOS 기기의 고유 번호를 얻기 위해서 udid가 존재했었다.

하지만 개인정보 문제 등으로 ios5 부터는 udid가 사라지고 uuid(임의로 생성한 고유값)가 생겨났다.

애플도 uuid를 이용해서 기기 고유번호를 생성하라고 권고한다.

하지만 문제는 uuid도 앱을 삭제하면 새롭게 생성 된다.  고유값이라고   없다.

개인정보 문제로 부터 자유로워졌긴 하지만, 개발자들이  고유번호를 다루기가 까다로워졌다.

여러 방법이 있겠지만 uuid를 최초로 생성하고 keychain에 저장시켜주는 방법 찾아보았다. 

/*
     File: KeychainItemWrapper.m 
 Abstract: 
 Objective-C wrapper for accessing a single keychain item.
  
  Version: 1.2 - ARCified
  
 Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple 
 Inc. ("Apple") in consideration of your agreement to the following 
 terms, and your use, installation, modification or redistribution of 
 this Apple software constitutes acceptance of these terms.  If you do 
 not agree with these terms, please do not use, install, modify or 
 redistribute this Apple software. 
  
 In consideration of your agreement to abide by the following terms, and 
 subject to these terms, Apple grants you a personal, non-exclusive 
 license, under Apple's copyrights in this original Apple software (the 
 "Apple Software"), to use, reproduce, modify and redistribute the Apple 
 Software, with or without modifications, in source and/or binary forms; 
 provided that if you redistribute the Apple Software in its entirety and 
 without modifications, you must retain this notice and the following 
 text and disclaimers in all such redistributions of the Apple Software. 
 Neither the name, trademarks, service marks or logos of Apple Inc. may 
 be used to endorse or promote products derived from the Apple Software 
 without specific prior written permission from Apple.  Except as 
 expressly stated in this notice, no other rights or licenses, express or 
 implied, are granted by Apple herein, including but not limited to any 
 patent rights that may be infringed by your derivative works or by other 
 works in which the Apple Software may be incorporated. 
  
 The Apple Software is provided by Apple on an "AS IS" basis.  APPLE 
 MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 
 THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 
 FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 
 OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 
  
 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 
 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 
 MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 
 AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 
 STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 
 POSSIBILITY OF SUCH DAMAGE. 
  
 Copyright (C) 2010 Apple Inc. All Rights Reserved. 
  
*/ 

#import "KeychainItemWrapper.h"
#import <Security/Security.h>

#if ! __has_feature(objc_arc)
#error THIS CODE MUST BE COMPILED WITH ARC ENABLED!
#endif

/*

These are the default constants and their respective types,
available for the kSecClassGenericPassword Keychain Item class:

kSecAttrAccessGroup			-		CFStringRef
kSecAttrCreationDate		-		CFDateRef
kSecAttrModificationDate    -		CFDateRef
kSecAttrDescription			-		CFStringRef
kSecAttrComment				-		CFStringRef
kSecAttrCreator				-		CFNumberRef
kSecAttrType                -		CFNumberRef
kSecAttrLabel				-		CFStringRef
kSecAttrIsInvisible			-		CFBooleanRef
kSecAttrIsNegative			-		CFBooleanRef
kSecAttrAccount				-		CFStringRef
kSecAttrService				-		CFStringRef
kSecAttrGeneric				-		CFDataRef
 
See the header file Security/SecItem.h for more details.

*/

@interface KeychainItemWrapper (PrivateMethods)
/*
The decision behind the following two methods (secItemFormatToDictionary and dictionaryToSecItemFormat) was
to encapsulate the transition between what the detail view controller was expecting (NSString *) and what the
Keychain API expects as a validly constructed container class.
*/
- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert;
- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert;

// Updates the item in the keychain, or adds it if it doesn't exist.
- (void)writeToKeychain;

@end

@implementation KeychainItemWrapper
{
    NSMutableDictionary *keychainItemData;		// The actual keychain item data backing store.
    NSMutableDictionary *genericPasswordQuery;	// A placeholder for the generic keychain item query used to locate the item.
}

- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup;
{
    if (self = [super init])
    {
        // Begin Keychain search setup. The genericPasswordQuery leverages the special user
        // defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain
        // items which may be included by the same application.
        genericPasswordQuery = [[NSMutableDictionary alloc] init];
        
		[genericPasswordQuery setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
        [genericPasswordQuery setObject:identifier forKey:(__bridge id)kSecAttrGeneric];
		
		// The keychain access group attribute determines if this item can be shared
		// amongst multiple apps whose code signing entitlements contain the same keychain access group.
		if (accessGroup != nil)
		{
#if TARGET_IPHONE_SIMULATOR
			// Ignore the access group if running on the iPhone simulator.
			// 
			// Apps that are built for the simulator aren't signed, so there's no keychain access group
			// for the simulator to check. This means that all apps can see all keychain items when run
			// on the simulator.
			//
			// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
			// simulator will return -25243 (errSecNoAccessForItem).
#else			
			[genericPasswordQuery setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
#endif
		}
		
		// Use the proper search constants, return only the attributes of the first match.
        [genericPasswordQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
        [genericPasswordQuery setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];
        
        NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];
        
        CFMutableDictionaryRef outDictionary = NULL;
        
        if (!SecItemCopyMatching((__bridge CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)
        {
            // Stick these default values into keychain item if nothing found.
            [self resetKeychainItem];
			
			// Add the generic attribute and the keychain access group.
			[keychainItemData setObject:identifier forKey:(__bridge id)kSecAttrGeneric];
			if (accessGroup != nil)
			{
#if TARGET_IPHONE_SIMULATOR
				// Ignore the access group if running on the iPhone simulator.
				// 
				// Apps that are built for the simulator aren't signed, so there's no keychain access group
				// for the simulator to check. This means that all apps can see all keychain items when run
				// on the simulator.
				//
				// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
				// simulator will return -25243 (errSecNoAccessForItem).
#else			
				[keychainItemData setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
#endif
			}
		}
        else
        {
            // load the saved data from Keychain.
            keychainItemData = [self secItemFormatToDictionary:(__bridge NSDictionary *)outDictionary];
        }
		if(outDictionary) CFRelease(outDictionary);
    }
    
	return self;
}

- (void)setObject:(id)inObject forKey:(id)key 
{
    if (inObject == nil) return;
    id currentObject = [keychainItemData objectForKey:key];
    if (![currentObject isEqual:inObject])
    {
        [keychainItemData setObject:inObject forKey:key];
        [self writeToKeychain];
    }
}

- (id)objectForKey:(id)key
{
    return [keychainItemData objectForKey:key];
}

- (void)resetKeychainItem
{
	OSStatus junk = noErr;
    if (!keychainItemData) 
    {
        keychainItemData = [[NSMutableDictionary alloc] init];
    }
    else if (keychainItemData)
    {
        NSMutableDictionary *tempDictionary = [self dictionaryToSecItemFormat:keychainItemData];
		junk = SecItemDelete((__bridge CFDictionaryRef)tempDictionary);
        NSAssert( junk == noErr || junk == errSecItemNotFound, @"Problem deleting current dictionary." );
    }
    
    // Default attributes for keychain item.
    [keychainItemData setObject:@"" forKey:(__bridge id)kSecAttrAccount];
    [keychainItemData setObject:@"" forKey:(__bridge id)kSecAttrLabel];
    [keychainItemData setObject:@"" forKey:(__bridge id)kSecAttrDescription];
    
	// Default data for keychain item.
    [keychainItemData setObject:@"" forKey:(__bridge id)kSecValueData];
}

- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert
{
    // The assumption is that this method will be called with a properly populated dictionary
    // containing all the right key/value pairs for a SecItem.
    
    // Create a dictionary to return populated with the attributes and data.
    NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
    
    // Add the Generic Password keychain item class attribute.
    [returnDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
    
    // Convert the NSString to NSData to meet the requirements for the value type kSecValueData.
	// This is where to store sensitive data that should be encrypted.
    NSString *passwordString = [dictionaryToConvert objectForKey:(__bridge id)kSecValueData];
    [returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(__bridge id)kSecValueData];
    
    return returnDictionary;
}

- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert
{
    // The assumption is that this method will be called with a properly populated dictionary
    // containing all the right key/value pairs for the UI element.
    
    // Create a dictionary to return populated with the attributes and data.
    NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
    
    // Add the proper search key and class attribute.
    [returnDictionary setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
    [returnDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
    
    // Acquire the password data from the attributes.
    CFDataRef passwordData = NULL;
    if (SecItemCopyMatching((__bridge CFDictionaryRef)returnDictionary, (CFTypeRef *)&passwordData) == noErr)
    {
        // Remove the search, class, and identifier key/value, we don't need them anymore.
        [returnDictionary removeObjectForKey:(__bridge id)kSecReturnData];
        
        // Add the password to the dictionary, converting from NSData to NSString.
        NSString *password = [[NSString alloc] initWithBytes:[(__bridge NSData *)passwordData bytes] length:[(__bridge NSData *)passwordData length] 
                                                     encoding:NSUTF8StringEncoding];
        [returnDictionary setObject:password forKey:(__bridge id)kSecValueData];
    }
    else
    {
        // Don't do anything if nothing is found.
        NSAssert(NO, @"Serious error, no matching item found in the keychain.\n");
    }
	if(passwordData) CFRelease(passwordData);

	return returnDictionary;
}

- (void)writeToKeychain
{
    CFDictionaryRef attributes = NULL;
    NSMutableDictionary *updateItem = nil;
	OSStatus result;
    
    if (SecItemCopyMatching((__bridge CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr)
    {
        // First we need the attributes from the Keychain.
        updateItem = [NSMutableDictionary dictionaryWithDictionary:(__bridge NSDictionary *)attributes];
        // Second we need to add the appropriate search key/values.
        [updateItem setObject:[genericPasswordQuery objectForKey:(__bridge id)kSecClass] forKey:(__bridge id)kSecClass];
        
        // Lastly, we need to set up the updated attribute list being careful to remove the class.
        NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:keychainItemData];
        [tempCheck removeObjectForKey:(__bridge id)kSecClass];
		
#if TARGET_IPHONE_SIMULATOR
		// Remove the access group if running on the iPhone simulator.
		// 
		// Apps that are built for the simulator aren't signed, so there's no keychain access group
		// for the simulator to check. This means that all apps can see all keychain items when run
		// on the simulator.
		//
		// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
		// simulator will return -25243 (errSecNoAccessForItem).
		//
		// The access group attribute will be included in items returned by SecItemCopyMatching,
		// which is why we need to remove it before updating the item.
		[tempCheck removeObjectForKey:(__bridge id)kSecAttrAccessGroup];
#endif
        
        // An implicit assumption is that you can only update a single item at a time.
		
        result = SecItemUpdate((__bridge CFDictionaryRef)updateItem, (__bridge CFDictionaryRef)tempCheck);
		NSAssert( result == noErr, @"Couldn't update the Keychain Item." );
    }
    else
    {
        // No previous item found; add the new one.
        result = SecItemAdd((__bridge CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL);
		NSAssert( result == noErr, @"Couldn't add the Keychain Item." );
    }
	
	if(attributes) CFRelease(attributes);
}

@end

/*
 
 ios 기기의 고유 번호를 얻는 것은
 */




 

*KeychainItemWrapper

/*
     File: KeychainItemWrapper.h
 Abstract: 
 Objective-C wrapper for accessing a single keychain item.
 
  Version: 1.2 - ARCified
 
 Disclaimer: IMPORTANT:  This Apple software is supplied to you by Apple
 Inc. ("Apple") in consideration of your agreement to the following
 terms, and your use, installation, modification or redistribution of
 this Apple software constitutes acceptance of these terms.  If you do
 not agree with these terms, please do not use, install, modify or
 redistribute this Apple software.
 
 In consideration of your agreement to abide by the following terms, and
 subject to these terms, Apple grants you a personal, non-exclusive
 license, under Apple's copyrights in this original Apple software (the
 "Apple Software"), to use, reproduce, modify and redistribute the Apple
 Software, with or without modifications, in source and/or binary forms;
 provided that if you redistribute the Apple Software in its entirety and
 without modifications, you must retain this notice and the following
 text and disclaimers in all such redistributions of the Apple Software.
 Neither the name, trademarks, service marks or logos of Apple Inc. may
 be used to endorse or promote products derived from the Apple Software
 without specific prior written permission from Apple.  Except as
 expressly stated in this notice, no other rights or licenses, express or
 implied, are granted by Apple herein, including but not limited to any
 patent rights that may be infringed by your derivative works or by other
 works in which the Apple Software may be incorporated.
 
 The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
 MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
 THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
 FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
 OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
 
 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
 MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
 AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
 STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGE.
 
 Copyright (C) 2010 Apple Inc. All Rights Reserved.
 
*/

#import <UIKit/UIKit.h>

/*
    The KeychainItemWrapper class is an abstraction layer for the iPhone Keychain communication. It is merely a 
    simple wrapper to provide a distinct barrier between all the idiosyncracies involved with the Keychain
    CF/NS container objects.
*/
@interface KeychainItemWrapper : NSObject

// Designated initializer.
- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *)accessGroup;
- (void)setObject:(id)inObject forKey:(id)key;
- (id)objectForKey:(id)key;

// Initializes and resets the default generic keychain item data.
- (void)resetKeychainItem;

@end

 

*viewdidload 에서 호출해서 사용하기


#import "ViewController.h"
#import "KeychainItemWrapper.h"

@interface ViewController ()

@end

@implementation ViewController

//uuid 생성 및 리턴하는 함수
- (NSString*) getAPInfo
{
    //uuid 저장하기 위해서 키 체이닝 생성 및 초기화
    KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"UUID" accessGroup:nil];

    NSString *uuid = [wrapper objectForKey:(__bridge id)(kSecAttrAccount)];

    if( uuid == nil || uuid.length == 0)
    {
        //키체인에 uuid 없으면 만들어서 저장
        CFUUIDRef uuidRef = CFUUIDCreate(NULL);
        CFStringRef uuidStringRef = CFUUIDCreateString(NULL, uuidRef);
        CFRelease(uuidRef);
        uuid = [NSString stringWithString:(__bridge NSString *) uuidStringRef];
        CFRelease(uuidStringRef);

        // 키체인에 uuid 저장
        [wrapper setObject:uuid forKey:(__bridge id)(kSecAttrAccount)];

    }

    return uuid;

}

//화면이 로딩될때 호출된다
- (void)viewDidLoad {
    [super viewDidLoad];
    //uuid 호출
    NSString *uuid = [self getAPInfo];
    NSLog(@"uuid 값: %@" , uuid);
}


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


@end

 

*결과

처음 설치했을때

3BE01CF3-E786-47DD-96B8-0B0895144B51

삭제후 재설치 했을때

3BE01CF3-E786-47DD-96B8-0B0895144B51

 

*파일

UUID.zip
0.07MB

 

*주의 : 키체인 쉐어링 꼭 체크를 해주자! ON !

 

 

  1. 유한별 2019.11.22 12:16

    좋은 글 잘 보았습니다!
    저도 키체인 데이터를 이용해서 기기 고유번호를 임의 생성하여 uuid 대신 사용한 적이 있었는데요,
    아이폰 전체 초기화를 할 경우 키체인 데이터 또한 초기화 된다는 문제가 있었습니다.
    그렇기 때문에 엄밀히 말하자면 키체인 데이터를 이용한 uuid 사용은 기기 고유번호라고 말할 수 없다고 판단했는데,
    이 부분에 대해 어떻게 생각하시는지 궁금합니다~

카메라 줌 AVCaptureDevice Zoom

예전시간에 카메라 자동 회전 기능을 소개했다.

https://abc1211.tistory.com/615

그곳에 카메라 줌기능을 넣기 위해서 이것저것 찾아보고 시도해보았다.

처음에는 카메라 캡쳐화면을 실시간으로 보여주는 PreviewLayer에 pinchegesture를 걸어서

제스쳐 변화 값을 이용해서 zoom해주면 된다고 생각했는데 거의 오전 내내 삽질을 했다.

결론은 previewlayer에는 gesture 를 걸 수 없다. Avcapturedevice 를 이용해야 한다.

아래는 해당 소스이다. 주석을 달아 놓았다.

*제스쳐 함수

//self.view 에 pinches 제스쳐 이벤트를 걸었다. view 화면을 두 손가락으로 움직이면 AVCaptureDevice의 줌을 변경한다.
-(void) handlePinchToZoomRecognizer2:(UIPinchGestureRecognizer*) pinchRecognizer {
    
    static CGFloat zoomFactorBegin = 0.0;                                                                                                   //시작 줌 값
    
    //제스쳐를 처음 시작했을때
    if ( UIGestureRecognizerStateBegan == pinchRecognizer.state ) {
        zoomFactorBegin = self.inputDevice.videoZoomFactor;                                                                      //     1.0
         NSLog(@"제스쳐 시작- 줌 값 : %f" , zoomFactorBegin);                                                                          //     1.0
         NSLog(@"제스쳐 시작 맥스- 줌 값 : %f" , self.inputDevice.activeFormat.videoMaxZoomFactor);         //     16.0
        
    //제스쳐  변화중 상태 값
    } else if (UIGestureRecognizerStateChanged == pinchRecognizer.state) {
        NSError *error = nil;
        
        //*lockForConfiguration
        //*설명: 아이폰의 하드웨어 속성을 변경하기 전에 이 메서드를 호출해야 한다.
        // 이 메서드를 호출 한 후 잠금을 해제 하고, 설정을 변경 후 잠금을 해준다.
        if ([self.inputDevice lockForConfiguration:&error]) {
            
            NSLog(@"제스쳐 변화- zoomFactorBegin줌 값 1 : %f" , zoomFactorBegin);                                          //1.0
            NSLog(@"제스쳐 pinchRecognizer.scale-  값  : %f" , pinchRecognizer.scale);                                     //1.02334~ 1.43343
            
            CGFloat desiredZoomFactor = zoomFactorBegin * pinchRecognizer.scale;
            
            NSLog(@"제스쳐 desiredZoomFactor- 줌 값  : %f" , desiredZoomFactor);                                             //1.02334~ 1.43343
            NSLog(@"제스쳐 변화 맥스- 줌 값 : %f" , self.inputDevice.activeFormat.videoMaxZoomFactor);           //16
            
            //*activeFormat : 활성화된 캡쳐 장치의 미디어 데이터 형식. 이 속성을 사용하여 현재 활성된 장치의 형식을 가져오거나 설정한다.
            //*videoMaxZoomFactor :  최대 zoom 값을 가져왔다.
            
            
            //zoomFactor의 최소 값이 1.0 이상이도록 설정.
            CGFloat zoomFactor = MAX(1.0, MIN(desiredZoomFactor, self.inputDevice.activeFormat.videoMaxZoomFactor));
            
             NSLog(@"제스쳐 zoomFactor- 줌 값  : %f" , zoomFactor);                                                                   //1.02334~ 1.43343
            
            //현재 확대,축소 비율에서 변경된 확대,축소 비율로 부드럽게 전환해주는 메소드
            [self.inputDevice rampToVideoZoomFactor:zoomFactor withRate:3.0];
            
            //하드웨어 속성을 잠궈준다.
            [self.inputDevice unlockForConfiguration];
        } else {
            NSLog(@"error: %@", error);
        }
    }
}

 

*viewdidload 함수 안

    //카메라 줌 제스쳐
    UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchToZoomRecognizer2:)];
    [self.view addGestureRecognizer:pinchGesture];

 

*파일

zoom.zip
0.08MB

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

 

예제파일

core audioqueue player 에 대하여

코어오디오를 이용해서 음원을 플레이 하는 어플을 간단하게 만들어보았다. 애플 오디오 프로그래밍 가이드 문서를 기반으로

구현을 하였다. 작동 순서는 prepare  버튼을 눌러서 먼저 재생할 오디오 파일 형식, 파일 위치와 관련된 설정을 해준다.

start 버튼을 눌러서 음원을 재생한다. 재생을 하면 재생이 완료 될때까지 끊임없이 콜백함수가 호출되면서 오디오 파일에서 읽은 

데이터를 버퍼에 넣고 출력한다. 그리고 음악이 종료되며면 dispose 버튼을 눌러서 메모리에 할당된 큐를 해제해준다.

처음에 프로세서를 알기까지 많은 시간이 걸렸다. 단순하게 오디오를 재생하려면 이미 

만들어져있는 MediaPlayer 오디오 프레임워크를 가져다 쓰면된다. 하지만  구간반복, 재생속도 조절 등과 같이 오디오를 

자유자재로 컨트롤하려면 코어오디오에 대한 개념이 반드시 필요하다. 

 

 

소스

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



@interface ViewController (){
    struct AQPlayerState pAqData;
}

@property (weak, nonatomic) IBOutlet UIButton *play;
@property (weak, nonatomic) IBOutlet UIButton *stop;
@property (weak, nonatomic) IBOutlet UIButton *prepare;

@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];
    
}

//오디오 파일 준비
- (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:@"03" ofType:@"m4a"]];
    }
    
    
    
    //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. 오디오 파일의 오디오 데이터 형식을 쿼리 할 때 사용할 예상 속성 값 크기를 가져온다.
    
    result = AudioFileGetProperty (                                                                                  //2. 오디오 파일의 지정된 속성의 값을 획득
                                   pAqData.mAudioFile,                                                               //3. AudioFileID 오디오 데이터 형식을 가져올 파일을 나타내는 오디오 파일 객체
                                   kAudioFilePropertyDataFormat,                                             //4. 오디오 파일의 데이터 형식 값을 얻기 위한 속성 ID.
                                   &dataFormatSize,                                                                    //5. 입력시 AudioStreamBasicDescription오디오 파일의 데이터 형식을 설명하는 구조체의 예상 크기. 출력시 사용 x
                                   &pAqData.mDataFormat                                                          //6. 출력시 AudioStreamBasicDescription 오디오 파일에서 가져온 전체 오디오 데이터 형식을 구조체 형식으로 출력. 파일의 오디오 데이터 형식을 오디오 큐의 사용자 지정 구조에 저장하여 오디오 대기열에 적용
                                   );
    VStatusBOOL(result, @"AudioFileGetProperty");
    
    
    //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");
    
    
    // kNumberPackages = 10*1000,
    pAqData.bufferByteSize = (10*1000) * maxPacketSize;
    //pAqData.mPacketDescs =(AudioStreamPacketDescription *) malloc((10*1000) * sizeof(AudioStreamPacketDescription));
    
    
        //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. 재생 된 오디오 파일을 닫는다.
    }
    
    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

 

파일

예제파일1

예제파일2

 

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



ios 카메라 화면 자동회전 (camera auto rotation)




코드



#import <AVFoundation/AVFoundation.h>

#import "ViewController.h"



//화면 전체 가로, 세로길이 구하기

#define kScreenWidth  [UIScreen mainScreen].bounds.size.width

#define kScreenHeight [UIScreen mainScreen].bounds.size.height


//screen orientation

bool g_isPortraitMode;                                      //현재 세로냐 가로냐

float _screenWidth_Landscape;                       // 가로일때 가로길이

float _screenHeight_Landscape;                      //가로일때 세로길이

float _screenWidth_Portrait;                            //세로일때 가로길이

float _screenHeight_Portrait;                           //세로일때 세로길이




@interface ViewController () <AVCaptureVideoDataOutputSampleBufferDelegate>

{

    CGRect s_frame;

}


@property (weak, nonatomic) IBOutlet UIButton *button;

@property (nonatomic,strong) AVCaptureSession *captureSession;

@property (nonatomic,strong) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;

@property (nonatomic,strong) AVCaptureVideoDataOutput *captureOutput;


@end


@implementation ViewController




- (void)viewDidLoad {

    [super viewDidLoad];

   NSLog(@"viewDidLoad 실행");

    

    // 가로, 세로 스크린사이즈를 전역 변수에 할당

    [self initScreenSizeParam];


    

    //새로 화면으로 초기화

    g_isPortraitMode = true;

    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isPortraitMode"];

    [[NSUserDefaults standardUserDefaults] synchronize];

    [self setScreenOrientation:UIInterfaceOrientationPortrait];

    //설정 해주고

    s_frame = CGRectMake(0, 0, _screenWidth_Portrait, _screenHeight_Portrait);


   

    //화면이 세로 or 가로로 바뀔때마다 특정 함수를 호출한다.

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChage:) name:UIDeviceOrientationDidChangeNotification object:nil];


    

    //카메라 초기화

    [self initCapture];


    //

   [self.view bringSubviewToFront:_button];

}



- (void)viewWillAppear:(BOOL)animated {

    [super viewWillAppear:animated];

    NSLog(@"viewWillAppear 실행");

    NSLog(@"g_isPortraitMode : %@" , (g_isPortraitMode ? @"true - 세로" : @"false - 가로"));

    if (g_isPortraitMode) {

        [self rotateVideo:UIInterfaceOrientationPortrait];

        [self updateViewUIWhenRotatedWithPortraitMode:YES];

    }

}


//오리엔테이션이 바뀌면 위젯 상태를 조정할 수 있습니다.

- (void)updateViewUIWhenRotatedWithPortraitMode:(BOOL)isPortraitMode {

    if (isPortraitMode) {

        

    }else {

        

    }

}


#pragma step 7.

-(void)initCapture {

     NSLog(@"step 7. initCapture 실행");

    //후방카메라 객체 만들기

    AVCaptureDevice *inputDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

    

    //입력데이터객체 만들기

    AVCaptureDeviceInput *captureInput = [AVCaptureDeviceInput deviceInputWithDevice:inputDevice error:nil];

    

    //입력객체 null 체크

    if(!captureInput){

        NSLog(@"captureInput is null !");

        return;

    }

    

    //비디오 출력객체 만들기

    _captureOutput = [[AVCaptureVideoDataOutput alloc] init];

    [_captureOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];

    

    NSString *key = (NSString *)kCVPixelBufferPixelFormatTypeKey;

    NSNumber *value = [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA];

    NSDictionary *videoSettings = [NSDictionary dictionaryWithObject:value forKey:key];

    

    [_captureOutput setVideoSettings:videoSettings];

    

    //세션 객체 생성

    self.captureSession = [[AVCaptureSession alloc] init];

    

    NSString *preset;

    if(!preset) preset = AVCaptureSessionPreset1920x1080;

    

    //세션에 프리셋을 넣을 수 있다면

    if ([_captureSession canSetSessionPreset:preset]) {

        self.captureSession.sessionPreset = preset;

    }else{

        self.captureSession.sessionPreset = AVCaptureSessionPresetHigh;

    }

    

    //세션에 입력 객체를 넣을 수 있다면

    if ([self.captureSession canAddInput:captureInput]) {

        [self.captureSession addInput:captureInput];

    }

    

    //세션에 출력 객체를 넣을 수 있다면

    if ([self.captureSession canAddOutput:_captureOutput]) {

        [self.captureSession addOutput:_captureOutput];

    }

    

    //비디오 미리보기 레이어 객체가 없다면

    if (!self.captureVideoPreviewLayer) {

        self.captureVideoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];

    }

    

    //프레임 위치 및 크기 셋팅

    s_frame.origin.x = 0;

    s_frame.origin.y = 0;

    

    NSLog(@"initCapture - g_isPortraitMode : %@" , (g_isPortraitMode ? @"true - 세로" : @"false - 가로"));

    if (g_isPortraitMode) {

        NSLog(@"initCapture s_frame : 세로 셋팅 *" );

        s_frame.size.width = _screenWidth_Portrait;

        s_frame.size.height = _screenHeight_Portrait;

    }else{

         NSLog(@"initCapture s_frame : 가로 셋팅 *" );

        s_frame.size.width = _screenWidth_Landscape;

        s_frame.size.height = _screenHeight_Landscape;

    }

    

    self.captureVideoPreviewLayer.frame = s_frame;

    NSLog(@"initCapture s_frame : %@" , NSStringFromCGRect(s_frame));

    

    self.captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;

    if ([[self.captureVideoPreviewLayer connection] isVideoOrientationSupported]) {

         NSLog(@"isVideoOrientationSupported - 가능!");

        [self.captureVideoPreviewLayer.connection setVideoOrientation:AVCaptureVideoOrientationLandscapeRight];

    }

    

    [self.view.layer addSublayer:self.captureVideoPreviewLayer];

    [self.captureSession startRunning];

    

}



#pragma step 6.

-(void)adjustAVOutputDataOrientaion:(AVCaptureVideoOrientation)aOrientation{

    NSLog(@"step 6 adjustAVOutputDataOrientaion 진입");

    NSLog(@"aOrientation  :%ld" , (long)aOrientation);

    

    for (AVCaptureConnection *connection in _captureOutput.connections) {

        for (AVCaptureInputPort *port in [connection inputPorts]) {

            if ([[port mediaType] isEqual:AVMediaTypeVideo]) {

                if ([connection isVideoOrientationSupported]) {

                    [connection setVideoOrientation:aOrientation];

                }

            }

        }

    }

}


#pragma step 5.

//비디오 회전

-(int) rotateVideo:(UIInterfaceOrientation)interfaceOrientation {

     NSLog(@"step 5. rotateVideo 진입");

    NSLog(@"step 5 interfaceOrientation  :%ld" , (long)interfaceOrientation);

    if (_captureSession != NULL) {

        NSLog(@"_captureSession is not null when video rotate");

        //호출후에 는 출력을 추가 또는 제거 하거나 개별 캡처 입력 또는 출력 속성을 구성하거나 변경할 수 있습니다.

        //사용자가 호출 할 때까지 실제로 변경되지 않으며 , 이 때 변경 사항이 적용됩니다.begin

        [_captureSession beginConfiguration];

        CALayer *previewViewLayer = [self.view layer];

        NSArray *subviews= previewViewLayer.sublayers;

        

        int i = 0;

        NSLog(@"subviews count : %lu" , (unsigned long)[subviews count]);

        

        while (true) {

            NSLog(@" i  : %d" ,  i );

            

            //서브 레이어 수보다 i 가 크면

            if (i > [subviews count]) {

                break;

            }

            

            id layer = [subviews objectAtIndex:i];

            

            if ([layer isKindOfClass:[AVCaptureVideoPreviewLayer class]] == TRUE) {

                

                //변경된 프레임 할당하는 곳!!

                [layer setFrame:s_frame];

          

            

            switch (interfaceOrientation) {

                case UIInterfaceOrientationPortrait:

                    NSLog(@"UIInterfaceOrientationPortrait = Portrait");

                    [[layer connection] setVideoOrientation:AVCaptureVideoOrientationPortrait];

                    [self adjustAVOutputDataOrientaion:AVCaptureVideoOrientationPortrait];

                    break;

                case UIInterfaceOrientationPortraitUpsideDown:

                    NSLog(@"UIInterfaceOrientationPortrait = PortraitUpsideDown");

                    [[layer connection] setVideoOrientation:AVCaptureVideoOrientationPortraitUpsideDown];

                    [self adjustAVOutputDataOrientaion:AVCaptureVideoOrientationPortraitUpsideDown];

                    break;

                case UIInterfaceOrientationLandscapeLeft:

                    NSLog(@"UIInterfaceOrientationLandscapeLeft");

                    [[layer connection] setVideoOrientation:AVCaptureVideoOrientationLandscapeLeft];

                    [self adjustAVOutputDataOrientaion:AVCaptureVideoOrientationLandscapeLeft];

                    break;

                case UIInterfaceOrientationLandscapeRight:

                   NSLog(@"UIInterfaceOrientationLandscapeRight");

                    [[layer connection] setVideoOrientation:AVCaptureVideoOrientationLandscapeRight];

                    [self adjustAVOutputDataOrientaion:AVCaptureVideoOrientationLandscapeRight];

                    break;

                default:

                    break;

            }

                break;

              }

            i++;

        }

        //캡쳐 세션의 구성변경 커밋

        [_captureSession commitConfiguration];

        

    }else if(![_captureSession isRunning]){

        NSLog(@"_captureSession is not running when rotate video");

    }

    return 0;

}




#pragma step 4.

-(BOOL)rotateToInterfaceOrientation:(UIInterfaceOrientation) interfaceOrientation{

    

     NSLog(@"step 4. rotateToInterfaceOrientation 진입");

     NSLog(@"interfaceOrientation  :%ld" , (long)interfaceOrientation);

    

    //가로 왼쪽이거나, 가로오른쪽일때 비디오 회전하기!

    if((interfaceOrientation == UIInterfaceOrientationLandscapeLeft) || (interfaceOrientation == UIInterfaceOrientationLandscapeRight)|| (interfaceOrientation == UIInterfaceOrientationPortrait)) {

        //rotate video

         NSLog(@"비디오 회전해!!");

        [self rotateVideo:interfaceOrientation];

    }else{

        return NO;

    }

    return YES;

}



#pragma step 3.

-(void) deviceOrientationDidChage:(NSNotification *) notification{

     NSLog(@"step 3. deviceOrientationDidChage 진입");

    //현재 디바이스 오리엔테이션 구하기

    UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];

    

    //세로 : 1,   거꾸로 : 2, 가로왼쪽 : 3,   가로오른쪽 : 4

    NSLog(@"현재 디바이스 상태는 %ld 입니다", (long)orientation);

    

    //디바이스 상태가 변경되면 처리할 내용

    if (orientation == UIDeviceOrientationLandscapeLeft) {

         NSLog(@"현재 디바이스 상태는 가로 왼쪽 입니다.");

        //현재 오리엔테이션이 가로면 비디오 회전해주기

        g_isPortraitMode = false;

        [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"isPortraitMode"];

        [[NSUserDefaults standardUserDefaults] synchronize];

        s_frame = CGRectMake(0, 0, _screenWidth_Landscape, _screenHeight_Landscape);

        [self rotateToInterfaceOrientation:(UIInterfaceOrientation)orientation];

    }else if(orientation == UIDeviceOrientationLandscapeRight){

        NSLog(@"현재 디바이스 상태는 가로 오른쪽 입니다.");

        //현재 오리엔테이션이 가로면 비디오 회전해주기

        g_isPortraitMode = false;

        [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"isPortraitMode"];

        [[NSUserDefaults standardUserDefaults] synchronize];

        s_frame = CGRectMake(0, 0, _screenWidth_Landscape, _screenHeight_Landscape);

        [self rotateToInterfaceOrientation:(UIInterfaceOrientation)orientation];

    }else if (orientation == UIDeviceOrientationPortrait){

        NSLog(@"현재 디바이스 상태는 세로 입니다.");

        g_isPortraitMode = true;

        [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isPortraitMode"];

        [[NSUserDefaults standardUserDefaults] synchronize];

        //[self setScreenOrientation:UIInterfaceOrientationPortrait];

        //프레임을 재 설정 해주고

        s_frame = CGRectMake(0, 0, _screenWidth_Portrait, _screenHeight_Portrait);

        //비디오를 세로로 돌리기

          [self rotateToInterfaceOrientation:(UIInterfaceOrientation)orientation];

    }

    

    

    

   

   

    

    

    

//    NSLog(@"deviceOrientationDidChage - g_isPortraitMode : %@" , (g_isPortraitMode ? @"true - 세로" : @"false - 가로"));

//

//    //특정한 오리엔테이션 무시하기

//    if (g_isPortraitMode || orientation == UIDeviceOrientationFaceUp || orientation == UIDeviceOrientationFaceDown || orientation == UIDeviceOrientationUnknown  || orientation == UIDeviceOrientationPortrait || orientation == UIDeviceOrientationPortraitUpsideDown){

//         NSLog(@"특정한 오리엔테이션 무시하기");

//        return;

//    }

    

    

 

    

}




#pragma step 2.

- (void)initScreenSizeParam {

    NSLog(@"step 2. initScreenSizeParam 실행");

    

    CGFloat screenW = kScreenWidth;

    CGFloat screenH = kScreenHeight;

    

    //가로길이가 세로길이 보다 작으면 , 즉 디바이스가 portraite 모드 일때

    if (screenW < screenH) {

         NSLog(@"screenW < screenH" );

        screenW = kScreenHeight;                 //screenW = 667

        screenH = kScreenWidth;                  //screenH = 375

    }

    

    

    NSLog(@"screenW : %f" ,screenW );

    NSLog(@"screenH : %f" ,screenH );

    

    

    _screenWidth_Landscape = screenW;       // 667

    _screenHeight_Landscape = screenH;      //  375


    _screenHeight_Portrait = screenW;           // 667

    _screenWidth_Portrait =screenH;            //375

    

    NSLog(@"_screenWidth_Landscape : %f" , _screenWidth_Landscape );

    NSLog(@"_screenHeight_Landscape : %f" , _screenHeight_Landscape );

    NSLog(@"_screenHeight_Portrait : %f" , _screenHeight_Portrait );

    NSLog(@"_screenWidth_Portrait : %f" , _screenWidth_Portrait );

}




#pragma step 1.

//앱 은 먼저 supportedInterfaceOrientations 메소드를 실행 한 다음 ViewDidLoad 메소드를 실행합니다.

//g_isPortraitMode 값을 설정합니다. - 화면이 바뀔때 항상 먼저 호출됨


//-(UIInterfaceOrientationMask)supportedInterfaceOrientations {

//    NSLog(@"step 1. supportedInterfaceOrientations 호출");

//

//    //스크린 모드 변수

//    UIInterfaceOrientationMask screenMode;

//    // 세로인지 가로인지 체크

//    if([[NSUserDefaults standardUserDefaults] boolForKey:@"isPortraitMode"]){

//        g_isPortraitMode = true;

//        screenMode = UIInterfaceOrientationMaskPortrait;

//         NSLog(@" !! supportedInterfaceOrientations 실행 - Portrait");

//    }else{

//        g_isPortraitMode = false;

//        screenMode = UIInterfaceOrientationMaskLandscape;

//         NSLog(@" !!  supportedInterfaceOrientations 실행 - Landscape");

//    }

//    //defalt 스크린 모드 리턴 (세로 or 가로)

//    return screenMode;

//}



//버튼 클릭했을 때 -> 디바이스에 오리엔테이션 설정해주기 -> step 1 supportedInterfaceOrientations호출

- (IBAction)buttonAction:(UIButton *)sender {

    

     NSLog(@"buttonAction 작동" );

    

    sender.selected = !sender.selected;

    

    if(sender.isSelected) {    // Portrait

        g_isPortraitMode = true;

        [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isPortraitMode"];

        [[NSUserDefaults standardUserDefaults] synchronize];

        [self setScreenOrientation:UIInterfaceOrientationPortrait];

        //프레임을 재 설정 해주고

        s_frame = CGRectMake(0, 0, _screenWidth_Portrait, _screenHeight_Portrait);

        //비디오를 세로로 돌리기

        [self rotateVideo:UIInterfaceOrientationPortrait];

        

        [self updateViewUIWhenRotatedWithPortraitMode:YES];

        

    }else {  // Landscape

        g_isPortraitMode = false;

        [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"isPortraitMode"];

        [[NSUserDefaults standardUserDefaults] synchronize];

        

        [self setScreenOrientation:UIInterfaceOrientationLandscapeRight];

        s_frame = CGRectMake(0, 0, _screenWidth_Landscape, _screenHeight_Landscape);

        [self rotateVideo:UIInterfaceOrientationLandscapeRight];

        

        [self updateViewUIWhenRotatedWithPortraitMode:NO];

    }

    

}





//디바이스에 오리엔테이션 설정해주기 -> step 1 호출

- (void)setScreenOrientation:(UIInterfaceOrientation)orientation {

    

    NSLog(@"setScreenOrientation  :%ld" , (long)orientation);

    

    if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {

        

        SEL selector = NSSelectorFromString(@"setOrientation:");

        

        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];

        

        [invocation setSelector:selector];

        

        [invocation setTarget:[UIDevice currentDevice]];

        

        int val = orientation;//

        

        [invocation setArgument:&val atIndex:2];

        

        //step 1을 호출 ?

        [invocation invoke];

        NSLog(@"setScreenOrientation  end!");

    }

}



- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}


@end



결과화면





  1. 2019.03.08 13:27

    비밀댓글입니다

    • 2019.03.08 13:28

      비밀댓글입니다

MediaPlayer 구현


네비게이션


info.plist



storyboard





#import <UIKit/UIKit.h>


@interface ViewController : UIViewController <MPMediaPickerControllerDelegate>



@end




//MediaPlayer 임포트!

#import <MediaPlayer/MediaPlayer.h>

#import "ViewController.h"


@interface ViewController ()


@property (strong,nonatomic) MPMediaPickerController *mpPickerVC;                       //픽커 컨트롤러

@property (strong,nonatomic) MPMediaItemCollection *slctitems;                              //선택된 아이템

@property (strong,nonatomic) MPMusicPlayerController *appMusicPlayer;               //플레이어



@property (weak, nonatomic) IBOutlet UITextField *artistaSearchBar;

@property (weak, nonatomic) IBOutlet UITextView *soundsTV;

@property (weak, nonatomic) IBOutlet UIButton *previousBtn;

@property (weak, nonatomic) IBOutlet UIButton *playBtn;

@property (weak, nonatomic) IBOutlet UIButton *nextBtn;


@end


@implementation ViewController


- (void)viewDidLoad {

    [super viewDidLoad];

    _mpPickerVC = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeAnyAudio];

    _mpPickerVC.prompt = @"음악을 선택해 주세요";

    _mpPickerVC.allowsPickingMultipleItems = YES;

    _mpPickerVC.showsCloudItems = YES;

    _mpPickerVC.delegate = self;

    _appMusicPlayer = [MPMusicPlayerController systemMusicPlayer];

    

    

    

    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];

    

    //불륨이 변경될때

    [notificationCenter  addObserver: self

                            selector: @selector (onVolumeChange:)

                                name: MPMusicPlayerControllerVolumeDidChangeNotification

                              object: _appMusicPlayer];

     //상태가 변경될때

    [notificationCenter  addObserver: self

                            selector: @selector (onStateChange:)

                                name: MPMusicPlayerControllerPlaybackStateDidChangeNotification

                              object: _appMusicPlayer];

    

    //재생되는 음원이 변경될때

    [notificationCenter  addObserver: self

                            selector: @selector (onNowPlaying:)

                                name: MPMusicPlayerControllerNowPlayingItemDidChangeNotification

                              object: _appMusicPlayer];

    // 시작

    [_appMusicPlayer beginGeneratingPlaybackNotifications];

    

}



#pragma mark Notifaction

//상태가 변경될때

- (void) onStateChange: (NSNotification*) notification {

    NSLog(@"onStateChange - notification");

}

//플레이 음원이  변경될때

- (void) onNowPlaying: (NSNotification*) notification {

    NSLog(@"onNowPlaying - notification");

    MPMusicPlayerController *player = notification.object;

    MPMediaItem *item = [player nowPlayingItem];

    NSLog(@"now playing %@", [item valueForKey:MPMediaItemPropertyTitle]);

}

//볼륨이  변경될때

- (void) onVolumeChange: (NSNotification*) notification {

    NSLog(@"onVolumeChange - notification");

}



//미디어 픽커에서 곡이 선택될때 - 업데이트

-(void)updateList:(MPMediaItemCollection *) collection {

    

    NSString *txt = @"";

    NSString *tag = @"";

    for (MPMediaItem *item in [collection items]) {

        if(NULL == item){

            NSLog(@"null이 존재합니다.");

            continue;

        }

        

        NSString *title = [item valueForKey:MPMediaItemPropertyTitle];

        NSString *artist = [item valueForKey:MPMediaItemPropertyArtist];

        txt = [NSString stringWithFormat:@"%@ %@ %@ - %@",txt,tag,title,artist];

        tag = @"\n";

    }

    

    _soundsTV.text = txt;

    

}



//미디어 픽커 선택

- (IBAction)onPick:(id)sender {

    NSLog(@"%@", @"onPick 작동");

    

//     _mpPickerVC = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeAnyAudio];

//    _mpPickerVC.prompt = @"음악을 선택해 주세요";

//    _mpPickerVC.allowsPickingMultipleItems = YES;

//    _mpPickerVC.showsCloudItems = YES;

//    _mpPickerVC.delegate = self;

    

    [self presentViewController:_mpPickerVC animated:YES completion:^{

       NSLog(@"%@", @"미디어 픽커 라이브러리를 선택했습니다.");

    }];

    

}


//가수이름으로 곡 찾기

-(void)quertArtist : (NSString *)artist {

    //초기화

    MPMediaQuery *artistQry = [[MPMediaQuery alloc]init];

    MPMediaPropertyPredicate *artistNamePredicate =

    [MPMediaPropertyPredicate predicateWithValue:artist forProperty:MPMediaItemPropertyArtist];

    [artistQry addFilterPredicate:artistNamePredicate];

    

    //null 체크

    for (MPMediaItem *item in [artistQry items]) {

        if (NULL == item) {

            NSLog(@"null 값이 존재합니다.");

            continue;

        }

        //정보 추출

        NSString *title = [item valueForKey:MPMediaItemPropertyTitle];

        NSURL  *url = [item valueForKey:MPMediaItemPropertyAssetURL];

        NSString *artist = [item valueForKey:MPMediaItemPropertyArtist];

        NSString *lyrics = [item valueForKey:MPMediaItemPropertyLyrics];

        MPMediaItemArtwork *artwork = [item valueForProperty: MPMediaItemPropertyArtwork];

        UIImage *artworkImage = [artwork imageWithSize: CGSizeMake(200, 200)];

        

        NSLog(@"After search with sound: %@ with url %@ artist is %@", title, [item valueForKey:MPMediaItemPropertyAssetURL], [item valueForKey:MPMediaItemPropertyArtist]);

        

    }// forin -end

    

    

    _slctitems = [MPMediaItemCollection collectionWithItems:artistQry.items];

    [_previousBtn setEnabled:NO];

    //목록 업데이트

    [self updateList:_slctitems];

    [_appMusicPlayer setQueueWithItemCollection:_slctitems];

    

    

}



- (IBAction)onQuery:(id)sender {

     NSLog(@"%@", @"onQuery 버튼 작동");

    

    if (_artistaSearchBar.text == NULL || _artistaSearchBar.text.length == 0) {

        return;

    }

    

    //검색바의 텍스트를 쿼리아티스트 메소드에 넘겨줌

    [self quertArtist:_artistaSearchBar.text];

    

    

}


- (IBAction)onPrevious:(id)sender {

     NSLog(@"%@", @"onPrevious 버튼 클릭");

    NSLog(@"현재 재생되고 있는 노래 index : %lu", (unsigned long)_appMusicPlayer.indexOfNowPlayingItem);

    NSLog(@"queue에 존재하는 음악 목록 총 개수: %lu", (_slctitems.count));

    if (NULL == _appMusicPlayer) {

        return;

    }

    

    if (_appMusicPlayer.indexOfNowPlayingItem <=1) {

        [_previousBtn setEnabled:NO];

    }

    [_appMusicPlayer skipToPreviousItem];

    

    [_nextBtn setEnabled:YES];


}





- (IBAction)onPlay:(id)sender {

    NSLog(@"%@", @"onPlay 버튼 클릭");

    NSLog(@"현재 재생되고 있는 노래 index : %lu", (unsigned long)_appMusicPlayer.indexOfNowPlayingItem);

    NSLog(@"queue에 존재하는 음악 목록 총 개수: %lu", (_slctitems.count));

    

    static BOOL isPlay = NO;

    if (NULL == _appMusicPlayer) {

         NSLog(@"%@", @"null이 존재합니다.");

        return;

    }

    


    

    /*

     //MPMusicPlayerController에 큐가 없을 경우 에러 발생

     2019-02-28 16:33:03.475540+0900 MediaPlayer[7370:1670897] [SDKPlayback] -[MPMusicPlayerController prepareToPlay] completed error: Error Domain=MPMusicPlayerControllerErrorDomain Code=1 "prepareToPlay without a queue" UserInfo={NSDebugDescription=prepareToPlay without a queue}

     */

    

    

    if (!isPlay) {

        isPlay = YES;

        [_playBtn setTitle:@"stop" forState:UIControlStateNormal];

        [_appMusicPlayer play];

    }else{

        isPlay = NO;

        [_playBtn setTitle:@"Play" forState:UIControlStateNormal];

        [_appMusicPlayer pause];

    }

    

}





- (IBAction)onNext:(id)sender {

     NSLog(@"%@", @"onNext 버튼 클릭");

    NSLog(@"현재 재생되고 있는 노래 index : %lu", (unsigned long)_appMusicPlayer.indexOfNowPlayingItem);

    NSLog(@"queue에 존재하는 음악 목록 총 개수: %lu", (_slctitems.count));

    if (_appMusicPlayer.indexOfNowPlayingItem >= (_slctitems.count -2)) {

        [_nextBtn setEnabled:NO];

    }

    

    [_appMusicPlayer skipToNextItem];

    

    [_previousBtn setEnabled:YES];

}







#pragma mark  MPMediaPickerControllerDelegate


//미디어 픽커 선택

- (void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection{

     NSLog(@"%@", @"didPickMediaItems - 선택되었습니다.");

    

    //선택한 음원 개수

    NSLog(@"picked with %lu items", (unsigned long)[mediaItemCollection count]);

    

    //null 값 체크

    if (NULL == mediaItemCollection) {

        NSLog(@"mediaItemCollection이 null 입니다.");

        return;

    }

    

    //선택한 음원들 하나하나 null 값 체크

    for (MPMediaItem *item in [mediaItemCollection items]) {

        if (NULL == item) {

            NSLog(@"item이 null 입니다.");

            continue;

        }

        

        NSString *title = [item valueForKey:MPMediaItemPropertyTitle];

        NSString *url = [item valueForKey:MPMediaItemPropertyAssetURL];

        NSString *artist = [item valueForKey:MPMediaItemPropertyArtist];

        

        NSLog(@"title : %@" , title);

        NSLog(@"url : %@" , url);

        NSLog(@"artist : %@" , artist);

    } //for - end

    

    _slctitems = mediaItemCollection;

    [_appMusicPlayer setQueueWithItemCollection:_slctitems];

    [self updateList:_slctitems];

    [_previousBtn setEnabled:NO];

    [_mpPickerVC dismissViewControllerAnimated:YES completion:^{

        //

    }];

}



//미디어 픽커 취소 델리게이트 메소드

- (void)mediaPickerDidCancel:(MPMediaPickerController *)mediaPicker{

     NSLog(@"%@", @"mediaPickerDidCancel -  취소클릭");

    [_mpPickerVC dismissViewControllerAnimated:YES completion:^{

        NSLog(@"%@", @"mediaPickerDidCancel -  닫혔습니다.");

    }];

}





- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}



@end






파일

MediaPlayer.zip




  1. 2019.03.26 11:04

    비밀댓글입니다

이미지 자르기


- (IBAction)CaptureAction:(id)sender {

    NSLog(@"캡쳐 버튼 눌렸습니다.");


        CGSize screenS = myImageView.frame.size;

        CGFloat delX = myImageView.image.size.width / screenS.width;

        CGFloat delY = myImageView.image.size.height / screenS.height;


        NSLog(@"myImageView.image.size.width : %f" ,myImageView.image.size.width );

        NSLog(@"myImageView.image.size.height : %f" ,myImageView.image.size.height );

        NSLog(@"screenS.width : %f" ,screenS.width );

        NSLog(@"screenS.height : %f" ,screenS.height );

        NSLog(@"delX : %f" ,delX );

        NSLog(@"delY : %f" ,delY );

    

        NSLog(@"viewFinder.frame.origin.x : %f" ,viewFinder.frame.origin.x );

        NSLog(@" viewFinder.frame.origin.y : %f" ,viewFinder.frame.origin.y );

        NSLog(@"viewFinder.bounds.size.width : %f" ,viewFinder.bounds.size.width );

        NSLog(@"viewFinder.bounds.size.height : %f" ,viewFinder.bounds.size.height );

    

        NSLog(@"***********************************************************************" );

    

        NSLog(@"viewFinder.frame.origin.x  * delX : %f" ,viewFinder.frame.origin.x * delX );

        NSLog(@" viewFinder.frame.origin.y * delY : %f" ,viewFinder.frame.origin.y * delY );

        NSLog(@"viewFinder.bounds.size.width * delX : %f" ,viewFinder.bounds.size.width * delX);

        NSLog(@"viewFinder.bounds.size.height  * delY : %f" ,viewFinder.bounds.size.height * delY );

    


    CGRect sourceRect = CGRectMake(viewFinder.frame.origin.x * delX, (viewFinder.frame.origin.y -110)* delY, viewFinder.bounds.size.width * delX, viewFinder.bounds.size.height * delY);

    CGImageRef imageRef = CGImageCreateWithImageInRect(myImageView.image.CGImage, sourceRect);

    UIImage *cropImage = [UIImage imageWithCGImage:imageRef];

    CGImageRelease(imageRef);


    ImageVC *imageVC = [[ImageVC alloc]init];

    imageVC.cropedimage = cropImage;

    [self presentViewController:imageVC animated:YES completion:nil];

    

}

UIpangesture로 UIView 옮기기& 특정 프레임 넘어가지 않게 설정



소스



#import "ViewController.h"


@interface ViewController (){

    

    //사진틀

    IBOutlet UIImageView *myImageView;

    //뷰파인더를 옮기기 전 위치

    CGPoint beforPoint;


    

    //뷰파인더

    UIView *viewFinder;

    //뷰파인더를 감싸고 있는 네 개의 점.

    UIView *leftTopPoint;

    UIView *rightTopPoint;

    UIView *leftDownPoint;

    UIView *rightDownPoint;

    

    

    UIBezierPath *blackPath;    //검은색 블러

    UIBezierPath *framePath;    //포커스

    CAShapeLayer *fillLayer;

    

}


@end


@implementation ViewController


- (void)viewDidLoad {

    [super viewDidLoad];

   //1.

    blackPath = [UIBezierPath bezierPathWithRect:self.view.frame];  //흐리게 블러 처리할 부분 : View 전체 지정

    

        //이미지뷰의 제일 왼쪽 모서리 구하기

        float startXpoint =  myImageView.frame.origin.x;

        float startYpoint =  myImageView.frame.origin.y;

    

    //뷰파인더

    viewFinder = [[UIView alloc] initWithFrame:CGRectMake(startXpoint, startYpoint, 100, 100)];

    viewFinder.backgroundColor = [UIColor clearColor];

    //viewFinder.center = self.view.center;

    viewFinder.layer.borderColor = [UIColor lightGrayColor].CGColor;

    viewFinder.layer.borderWidth= 1.0f;

    [self.view addSubview:viewFinder];

    

    framePath = [UIBezierPath bezierPathWithRect:viewFinder.frame];

    [blackPath appendPath:framePath];

    blackPath.usesEvenOddFillRule = YES;

    

    

    //오버레이뷰 : 약간 어둡게

    fillLayer = [CAShapeLayer layer];

    fillLayer.path = blackPath.CGPath;                                       //1.번에서 지정한 범위 그대로 설정

    fillLayer.fillRule = kCAFillRuleEvenOdd;

    fillLayer.fillColor = [UIColor blackColor].CGColor;               //검은색 바탕

    fillLayer.opacity = 0.4;                                                            //약간 흐리게

    [self.view.layer addSublayer:fillLayer];                                 //루트뷰 레이어에 오버레이뷰 추가

    

    

    //뷰파인더 위치 이동 제스쳐 이벤트

    UIPanGestureRecognizer *panResizeGestureViewFinder =[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(resizeTranslateViewFinder:)];

    [viewFinder addGestureRecognizer:panResizeGestureViewFinder];

    

}




//파인더뷰 드래그

-(void)resizeTranslateViewFinder:(UIPanGestureRecognizer *) recognizer{

    

    //드래그 후 위치값

    CGPoint afterPoint = [recognizer locationInView:viewFinder.superview];

    float finderX =  viewFinder.frame.origin.x;

    float finderY = viewFinder.frame.origin.y;

//    float beforX = beforPoint.x;

//    float beforY = beforPoint.y;

    float afterX = afterPoint.x;

    float afterY = afterPoint.y;

//    NSLog(@"finderX : %f  ,  finderY: %f" ,finderX , finderY);

//    NSLog(@"beforX : %f  ,  beforY: %f" ,beforX , beforY);

//    NSLog(@"afterX : %f  ,  afterY: %f" ,afterX , afterY);

//

//

//

        if ([recognizer state] == UIGestureRecognizerStateBegan) {

            NSLog(@"시작");

            viewFinder.center = afterPoint;                                 //뷰파인더 재할당

            [self setTransParentViewFinderSetting];                 //뷰파인더 셋팅

            beforPoint = [recognizer locationInView:viewFinder.superview];

            //[viewFinder setNeedsDisplay];

        }else if([recognizer state] == UIGestureRecognizerStateChanged){

            NSLog(@"중간");

            //뷰 파인더가 이미지 뷰 가이드라인 넘어가지 않게 설정

            [self actionViewFinderGuideLine:finderX and:finderY and:afterX and:afterY and :afterPoint];

            

        }else if ([recognizer state] == UIGestureRecognizerStateEnded ){

            NSLog(@"종료");

              //뷰 파인더가 이미지 뷰 가이드라인 넘어가지 않게 설정

           [self actionViewFinderGuideLine:finderX and:finderY and:afterX and:afterY and :afterPoint];

    }


};



////뷰파인더(투명색) 위치 설정을 위한 메소드

-(void) setTransParentViewFinderSetting {

    //검은색배경 위치와 오버레이뷰 해제

    [blackPath removeAllPoints];

    [fillLayer removeFromSuperlayer];

    //검은색 바탕화면 다시 셋팅

    blackPath = [UIBezierPath bezierPathWithRect:self.view.frame];

    

    

    //드래그된 뷰파인더 위치를 framepath에(위치경로) 대입

    framePath = [UIBezierPath bezierPathWithRect:viewFinder.frame];

    

    //뷰파인더(투명) 경로를 검은색 바탕화면 경로에 더해주기

    [blackPath appendPath:framePath];

    blackPath.usesEvenOddFillRule = YES;

    

    //오버레이뷰 : 약간 어둡게

    fillLayer = [CAShapeLayer layer];

    fillLayer.path = blackPath.CGPath;                                       //1.번에서 지정한 범위 그대로 설정

    fillLayer.fillRule = kCAFillRuleEvenOdd;

    fillLayer.fillColor = [UIColor blackColor].CGColor;               //검은색 바탕

    fillLayer.opacity = 0.4;                                                            //약간 흐리게

    [self.view.layer addSublayer:fillLayer];                                 //루트뷰 레이어에 오버레이뷰 추가

    

}




  //뷰 파인더가 이미지 뷰 가이드라인 넘어가지 않게 설정

-(void)actionViewFinderGuideLine : (float) finderX and:(float)finderY and:(float)afterX and:(float)afterY and:(CGPoint)afterPoint{

    //*왼쪽테두리 넘지 못함

    if(finderX <= 0.0){

        viewFinder.center = CGPointMake(50, afterY);

        [self setTransParentViewFinderSetting];

        //왼쪽테두리 + 위쪽 테두리 넘지 못한다.

        if(finderY <= myImageView.frame.origin.y){

            viewFinder.center = CGPointMake(50, myImageView.frame.origin.y + 50);

            [self setTransParentViewFinderSetting];

        }

        //왼쪽테두리 + 아래쪽 테두리 넘지 못함.

        if((finderY+100) >= myImageView.frame.origin.y+myImageView.frame.size.height){

            viewFinder.center = CGPointMake(50,( myImageView.frame.origin.y+myImageView.frame.size.height) - 50);

            [self setTransParentViewFinderSetting];

        }

        

        //*위쪽 테두리 넘지 못한다.

    }else  if(finderY <= myImageView.frame.origin.y){

        viewFinder.center = CGPointMake(afterX, myImageView.frame.origin.y + 50);

        [self setTransParentViewFinderSetting];

        //위쪽 테두리 + 오른쪽 테두리 넘지 못함

        if((finderX+100) >= myImageView.frame.size.width){

            viewFinder.center = CGPointMake(myImageView.frame.size.width - 50, myImageView.frame.origin.y + 50);

            [self setTransParentViewFinderSetting];

        }

        //위쪽 테두리 + 왼쪽 테두리 넘지 못함

        if(finderX <= 0.0){

            viewFinder.center = CGPointMake(50, afterY);

            [self setTransParentViewFinderSetting];

        }

        

        //*오른쪽 테두리 넘지 못한다.

    }else if((finderX+100) >= myImageView.frame.size.width){

        viewFinder.center = CGPointMake(myImageView.frame.size.width - 50, afterY);

        [self setTransParentViewFinderSetting];

        //오른쪽 테두리 + 위쪽 테두리 넘지 못함

        if(finderY <= myImageView.frame.origin.y){

            viewFinder.center = CGPointMake(myImageView.frame.size.width - 50, myImageView.frame.origin.y + 50);

            [self setTransParentViewFinderSetting];

            

        }

        //오른쪽 테두리 + 아래쪽 테두리 넘지 못함

        if((finderY+100) >= myImageView.frame.origin.y+myImageView.frame.size.height){

            viewFinder.center = CGPointMake(myImageView.frame.size.width - 50,( myImageView.frame.origin.y+myImageView.frame.size.height) - 50);

            [self setTransParentViewFinderSetting];

        }

        

        //*아래쪽 테두리 넘지 못함

    }else if((finderY+100) >= myImageView.frame.origin.y+myImageView.frame.size.height){

        viewFinder.center = CGPointMake(afterX,( myImageView.frame.origin.y+myImageView.frame.size.height) - 50);

        [self setTransParentViewFinderSetting];

        

        //아래쪽 + 왼쪽 테두리 넘지 못함

        if(finderX <= 0.0){

            viewFinder.center = CGPointMake(50, afterY);

            [self setTransParentViewFinderSetting];

        }

        

        //아래쪽 + 오른쪽 테두리 넘지 못함

        if((finderX+100) >= myImageView.frame.size.width){

            viewFinder.center = CGPointMake(myImageView.frame.size.width - 50, afterY);

            [self setTransParentViewFinderSetting];

        }

        

        

    }else{

        viewFinder.center = afterPoint;                                 //뷰파인더 재할당

        [self setTransParentViewFinderSetting];

        // [viewFinder setNeedsDisplay];

    }

}



- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}



@end





일정시간이 지난 파일 자동 삭제하기



앱실행할때 호출

//일정 시간 지난 미디어 파일 지우기


   [self deleteOverDateMediaFile];



//삭제 기준 날짜 가져오기

-(float)getDeleteStandardDateSeconds{

    

    //기준이 되는 날짜

    // 1. 3일 전 날짜를 구함 -> 데이터 포멧 변환 -> string으로 변환 -> 초 로변환 (568393200.000000)

    NSDateComponents* deltaComps = [[NSDateComponents alloc] init];

    [deltaComps setDay: -3];

    NSDate* tomorrow = [[NSCalendar currentCalendar] dateByAddingComponents:deltaComps toDate:[NSDate date] options:0];

    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];

    dateFormatter.dateFormat = @"yyyyMMdd";

    NSString *dateString = [dateFormatter stringFromDate:tomorrow];

    NSLog(@"날짜 스트링 타입 : %@" , dateString);

    NSDate *dateDate = [dateFormatter dateFromString:dateString];

    NSLog(@"날짜 데이트 타입 : %@" , dateDate);

    NSTimeInterval seconds = [dateDate timeIntervalSinceReferenceDate];

    NSLog(@"초로변환 : %f" , seconds);

    return seconds;

}


//일정 시간 지난 미디어 파일 지우기

-(void)deleteOverDateMediaFile{


   //삭제 기준이 되는 초로 변환된 데이터

    float deleteDateStandardSeconds = [self getDeleteStandardDateSeconds];

    [self startDeleteMediaFile:@"/video" deleteStandardSeconds:deleteDateStandardSeconds ];

    [self startDeleteMediaFile:@"/audio" deleteStandardSeconds:deleteDateStandardSeconds ];

    [self startDeleteMediaFile:@"/photo" deleteStandardSeconds:deleteDateStandardSeconds ];

}



//비디오, 오디오, 사진 파일 삭제

-(void) startDeleteMediaFile :(NSString *) fileName  deleteStandardSeconds:(float) second{

    //파일 매니져

    NSFileManager *fm = [NSFileManager defaultManager];

    

    //최종 문서  폴더

    NSArray *DocumentDrectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    

    NSString *bigDir = [DocumentDrectory[0] stringByAppendingFormat:@"%@",fileName];

    

    //디렉토리 존재여부

    if ([fm isWritableFileAtPath:bigDir] ) {

        NSLog(@" %@ Dir가 존재합니다." , bigDir);

    }else{

        NSLog(@" %@ Dir가 존재하지 않습니다." ,bigDir);

    }

    

    //폴더 안에 있는 날짜 폴더들

    NSError *err;

    NSArray *smallDateFileArray = [fm contentsOfDirectoryAtPath:bigDir error:&err];

    if (err == nil) {

        

        //날짜 폴더 개수별로 개수

        for (int i = 0 ; i < (unsigned long)[smallDateFileArray count]; i++) {


            

            NSString *folderPathToDelete = [bigDir stringByAppendingFormat:@"/%@/",[smallDateFileArray objectAtIndex:i] ];

            

            //String 폴더명 가져와서 날짜 타입으로 바꾸고 숫자로 바꿔주기

            NSString *dateFileNameString =   [smallDateFileArray objectAtIndex:i];

            NSDateFormatter *fileDateFormatter = [[NSDateFormatter alloc] init];

            [fileDateFormatter setDateFormat:@"yyyyMMdd"];

            NSDate *fileNameDate = [fileDateFormatter dateFromString:dateFileNameString];

            NSTimeInterval haveToDeleteSeconds = [fileNameDate timeIntervalSinceReferenceDate];

            NSLog(@"초로변환2 : %f" , haveToDeleteSeconds);

            

            //삭제기준 날짜와 비교해서 작으면 (앞의 날짜) 경로를 가져와서 삭제 해준다.

            if (second > haveToDeleteSeconds) {

                

                NSError *deleteErr;


               [[NSFileManager defaultManager] removeItemAtPath:folderPathToDelete error:&deleteErr];

               

                if (deleteErr != nil) {

                    NSLog(@"이전 파일 삭제 중 오류가 발생했습니다.");

                    return;

                }else{

                    NSLog(@"삭제 성공!");

                    NSLog(@"%@ 폴더는 3일이 지난 파일이기 때문에 삭제 했습니다." , dateFileNameString);

                    NSLog(@"삭제 경로 : %@ " , folderPathToDelete);

                }

            }

        }

    }

}



+ Recent posts