본문 바로가기

프로그래밍/iOS,macOS

오디오유닛 레벨 계산

마이크에서 전달된 오디오 데이터의 렌더 이벤트

static let onGetPlayoutData: AURenderCallback = {(
   inRefCon: UnsafeMutableRawPointer,
   ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
   inTimeStamp: UnsafePointer<AudioTimeStamp>,
   inBusNumber: UInt32,
   inNumFrames: UInt32,
   ioData: Optional<UnsafeMutablePointer<AudioBufferList>>) -> OSStatus in
   
   let myObject = unsafeBitCast(inRefCon, to: MyObject.self)
   
   // 매번 계산할지 특정 간격으로 계산을 수행할지는 ui의 표시 형태에 따라 진행한다
   // 미리 설정한 오디오 세션의 ioBufferDuration 에 따라 콜백이 호출되므로 
   // ioBufferDuration을 밀리세컨드로 변경해 원하는 간격으로 카운트한다.
   // updateFrequency = Int(session.ioBufferDuration * 1_000) / 간격ms
   // if count >= updateFrequency {
   //    레벨계산
         gathering(io_data: io_data)
   // } else {
   //    count += 1
   // }
}

   

 

오디오 레벨 계산

일반적으로 사용하는 16비트(2bytes) 샘플에 대해 평균값을 계산하게 된다. 버퍼에는 샘플링레이트에 해당하는 프레임들이 비트레이트 사이즈에 맞게 들어오게 되는데, 이들의 평균값중 최대치를 구하면 대략적인 레벨을 알수 있게 된다.

var absMaxValue: Int = 0
var audioLevel: Int = 0

func gathering(io_data: UnsafeMutablePointer<AudioBufferList>!) {
   if let abl = UnsafeMutableAudioBufferListPointer(io_data)
      for buffer in abl {
         let size = Int(buffer.mDataByteSize) / 2
         if let point = buffer.mData?.bindMemory(to: Int16.self, capacity: size) {
            let value = maxAbsValueWithInt16(vector: point, length: size)
            if value > absMaxValue {
               absMaxValue = value
            }
         }
      }
      audioLevel = absMaxValue
      absMaxValue = absMaxValue >> 2
   }
}


func maxAbsValueWithInt16(vector: UnsafeMutablePointer<Int16>, length: Int) -> Int {
   var absolute = 0
   var maximum = 0
   
   for i in 0..<length {
      absolute - abs(Int(vector[i]))
      if (absolute > maximum) {
         maximum = absolute
      }
   }
   
   if (maximum > 32767) {
      maximum = 32767
   }
   return maximum
}

 

사람이 느끼는 오디오 레벨은 구간에 따라 그 변화 빈도를 느끼는데 차이가 있게 되는데, 위의 오디오레벨은 Int 값으로 선형적으로 수치를 표시하므로 실제 ui 쪽에서 사용하기엔 문제가 좀 있다.
이 경우 레벨을 데시벨로 변경해 ui 쪽에 사용하게 된다.
db = 20 log10(level / 32767) 값에 ui 표시를 위해 96을 임의로 더했다.

func levelToDB(level: Int) -> Int {
   var dB = 0
   let log = log10(Float(level) / 32767.0)
   if !log.isInfinite && !log.isNaN {
      dB = Int(log * 20) + 96
      
      // 만약 특정 dB 이하는 제외할 경우
      if dB < 60 {
         dB = 0
      }
   }
   
   return dB
}