일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 |
- audio record
- 달러예금
- ChatGPT
- 3rd framework
- Android Studio
- MG더뱅킹정기예금
- KB
- JetPack
- 청약
- java
- Data Binding
- ndk r24
- Mac
- RETROFIT
- ios
- gradle
- Swift
- BindingAdapter
- 예금
- 사전청약
- ndkVersion
- EditText
- Android
- arm-linux-androideabi
- CHAT GPT
- 고금리
- 새마을금고
- kotlin
- Andorid
- 용산 호반써밋 에이디션
- Today
- Total
pear
[iOS] record(swift) 응용 본문
Mobile/Web 등에서 간단한 녹음 API들은 일반적으로 FLOAT32LE/FLOAT32BE 사용한다.
내게 필요한 스펙은 PCM format은 S16LE 이다.
swift record - 1을 참고하여 내 스펙에 맞게 변환
import Foundation
import AVFoundation
//음성버퍼와 녹음 이벤트를 전달할 프로토콜
protocol RecordProtocol: class
{
func onBuffer(len:UInt32, buffer:[Int16])
func onEvent(type:typeRecord, value:Int)
}
//녹음 이벤트 상태 값 정의
enum typeRecord: Int{
case start = 1, stop = 0, used = 2, err = 3
}
class RecordAudio{
public static let shared = RecordAudio()
private init(){}
static let SEND_BUFFER_UNIT = 1
static let SAMPLE_RATE_16K = 16000
//byte size Unit
//음성버퍼를 전달할 버퍼 단위
let FRAME_SIZE:Int = getBuffer50ms() * SEND_BUFFER_UNIT
let SAMPLE_RATE:Double = getSampleRate()
let TAG:String = "RecordAudio"
//음성버퍼와 녹음 이벤트를 전달할 프로토콜 등록을 위해
weak var delegate : RecordProtocol?
var isRecording = false
var mAudioUnit: AudioUnit? = nil
//녹음을 위한 입력 버퍼 타입 아이폰에서는 1로 고정 인것 같다.
private let INPUT_BUS: UInt32 = 1
var mSaveBuffer = [Int16]()
var mRecordBuffer = [Int16]()
func startRecording(){
if isRecording {
delegate?.onEvent(type: typeRecord.used, value: 0)
return
}
DispatchQueue.global(qos: .userInitiated).async {
self.mRecordBuffer = [Int16]()
self.mSaveBuffer = [Int16]()
//오디오 세션설정
if(AudioSession.shared.startAudioSession() != 1)
{
self.delegate?.onEvent(type: typeRecord.err, value: 1)
return
}
self.startAudioUnit()
}
}
func stopRecording() {
ALOG.i(tag: TAG, msg: "stopRecording")
if(!isRecording)
{
return
}
//백스라운드에서 호출 될경우 메인스레드에서 처리가 필요한 부분
//DispatchQueue.global(qos: .userInitiated).async {
DispatchQueue.main.async {
AudioOutputUnitStop(self.mAudioUnit!)
AudioUnitUninitialize(self.mAudioUnit!)
// //remove audio
AudioComponentInstanceDispose(self.mAudioUnit!)
self.mAudioUnit = nil
self.delegate?.onEvent(type: .stop, value: 1)
self.delegate = nil
AudioSession.shared.stopAudioSession()
self.isRecording = false
}
}
public func getSaveBuffer()->[Int16]{
return mSaveBuffer
}
static func getBuffer50ms() -> Int
{
return Int(getSampleRate()) / 20
}
// desired audio sample rate
static func getSampleRate() -> Double
{
return Double( SAMPLE_RATE_16K )
}
func startAudioUnit(){
ALOG.d(tag: TAG, msg: "startAudioUnit called")
var err: OSStatus = noErr
if self.mAudioUnit == nil {
setAudioUnit() // setup once
}
guard let au = self.mAudioUnit
else {
//오디오 유닛 생성 실패
delegate?.onEvent(type: .err, value: 1)
return
}
err = AudioUnitInitialize(au)
if err != noErr {
//오디오 유닛 초기화 실패
delegate?.onEvent(type: .err, value: 1)
return
}
//실제 녹음 시작 요청
err = AudioOutputUnitStart(au) // start
if err == noErr {
isRecording = true
delegate?.onEvent(type: .start, value: 1)
}
else{
delegate?.onEvent(type: .start, value: 1)
}
}
//녹음 음성 데이터 타입설정
private func setAudioUnit() {
var componentDesc: AudioComponentDescription
= AudioComponentDescription(
componentType: OSType(kAudioUnitType_Output),
componentSubType: OSType(kAudioUnitSubType_RemoteIO),
componentManufacturer: OSType(kAudioUnitManufacturer_Apple),
componentFlags: UInt32(0),
componentFlagsMask: UInt32(0) )
let component: AudioComponent! = AudioComponentFindNext(nil, &componentDesc)
var osErr: OSStatus = noErr
osErr = AudioComponentInstanceNew(component, &self.mAudioUnit )
if( osErr != noErr)
{
ALOG.w(tag: TAG, msg: "[Error]setAudioUnit \(osErr)")
return
}
//maybe not need
guard let au = self.mAudioUnit
else {
ALOG.w(tag: TAG, msg: "[FAIL]AudioComponentInstanceNew \(osErr)")
return }
// Enable I/O for input.
var enableInput: UInt32 = 1
//let packet_size = MemoryLayout<UInt32>.size
let packet_size = MemoryLayout<UInt16>.size
//EnableIO
osErr = AudioUnitSetProperty(au,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input,
INPUT_BUS,
&enableInput,
UInt32(MemoryLayout<UInt32>.size))
// Set format to 16-bit, linear PCM
let channel = 1 //1 // 2 channel stereo
var streamFormatDesc:AudioStreamBasicDescription = AudioStreamBasicDescription(
mSampleRate: Double( SAMPLE_RATE ),
mFormatID: kAudioFormatLinearPCM,
mFormatFlags: ( kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked ),
mBytesPerPacket: UInt32(channel * packet_size),
mFramesPerPacket: 1,
mBytesPerFrame: UInt32(channel * packet_size),
mChannelsPerFrame: UInt32(channel),
mBitsPerChannel: UInt32(8 * packet_size),
mReserved: UInt32(0)
)
//StreamFormat
osErr = AudioUnitSetProperty(au,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
INPUT_BUS,
&streamFormatDesc,
UInt32(MemoryLayout<AudioStreamBasicDescription>.size))
//recordCallback 등록
var inputCallbackStruct
= AURenderCallbackStruct(inputProc: recordCallback,
inputProcRefCon:
UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()))
//SetInputCallback
osErr = AudioUnitSetProperty(au,
kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global,
INPUT_BUS,
&inputCallbackStruct,
UInt32(MemoryLayout<AURenderCallbackStruct>.size))
ALOG.d(tag: TAG, msg: "one_ui32 : \(enableInput)")
ALOG.d(tag: TAG, msg: "ShouldAllocateBuffer : \(UInt32(MemoryLayout<UInt32>.size))")
}
//AURenderCallbackStruct에서 등록할 콜백 타입
let recordCallback: AURenderCallback = { (
inRefCon,
ioActionFlags,
inTimeStamp,
inBusNumber,
frameCount,
ioData ) -> OSStatus in
let audioObject = unsafeBitCast(inRefCon, to: RecordAudio.self)
var err: OSStatus = noErr
//ALOG.d(tag: "AURenderCallback", msg: "recordingCallback called")
// set mData to nil, AudioUnitRender() should be allocating buffers
var bufferList = AudioBufferList(
mNumberBuffers: 1,
mBuffers: AudioBuffer(
mNumberChannels: UInt32(1),
mDataByteSize: 16,
mData: nil))
// ALOG.d(tag: "AURenderCallback", msg: "frameCount : \(frameCount)")
if let au = audioObject.mAudioUnit {
err = AudioUnitRender(au,
ioActionFlags,
inTimeStamp,
inBusNumber,
frameCount,
&bufferList)
}
//ALOG.d(tag: "AURenderCallback", msg: "inBusNumber : \(inBusNumber)")
//send delegate
audioObject.onBufferShort(bufferList:&bufferList, frameCount: frameCount)
return 0
}
func onBufferShort(bufferList : UnsafeMutablePointer<AudioBufferList>, frameCount:UInt32){
let point = bufferList[0].mBuffers.mData!
let pPointer = point.bindMemory(to: Int16.self, capacity: Int(frameCount))
let pBufPointer = UnsafeBufferPointer(start: pPointer, count: Int(frameCount));
let bufferArray = [Int16](pBufPointer)
mRecordBuffer.append(contentsOf: bufferArray)
mSaveBuffer.append(contentsOf: bufferArray)
//음성버퍼가 지정한 만큼 모이면 등록된 Procotol로 전달
if( FRAME_SIZE <= mRecordBuffer.count)
{
ALOG.d(tag: TAG, msg: "onBufferShort unit")
let range = 0..<FRAME_SIZE
let one_frame = Array( mRecordBuffer[range] )
mRecordBuffer.removeSubrange(range)
self.delegate?.onBuffer(len: bufferList[0].mBuffers.mDataByteSize, buffer: one_frame)
}
}
}
참고
FLOAT32LE - 32 bit IEEE floating point PCM little endian
FLOAT32BE - 32 bit IEEE floating point PCM big endian
S16LE - Signed 16 integer bit PCM little endian
S16BE - 1Signed 16 integer bit PCM bing endian
'iOS > swift' 카테고리의 다른 글
[iOS] UI portrait fixed(세로 고정) (0) | 2023.06.30 |
---|---|
[iOS]Swift Value Memory Study (0) | 2023.06.16 |
[iOS]DispatchQueue.global (0) | 2023.03.08 |
[swift]3rd part framework - Undefined symbols for architecture (0) | 2022.05.27 |
iOS issue - Error creating LLDB target at path (0) | 2022.01.20 |