AVFoundation框架解析(二十七) —— 基于AVAudioEngine的簡(jiǎn)單使用示例(一)

版本記錄

版本號(hào) 時(shí)間
V1.0 2021.05.18 星期二

前言

AVFoundation框架是ios中很重要的框架,所有與視頻音頻相關(guān)的軟硬件控制都在這個(gè)框架里面,接下來(lái)這幾篇就主要對(duì)這個(gè)框架進(jìn)行介紹和講解。感興趣的可以看我上幾篇。
1. AVFoundation框架解析(一)—— 基本概覽
2. AVFoundation框架解析(二)—— 實(shí)現(xiàn)視頻預(yù)覽錄制保存到相冊(cè)
3. AVFoundation框架解析(三)—— 幾個(gè)關(guān)鍵問(wèn)題之關(guān)于框架的深度概括
4. AVFoundation框架解析(四)—— 幾個(gè)關(guān)鍵問(wèn)題之AVFoundation探索(一)
5. AVFoundation框架解析(五)—— 幾個(gè)關(guān)鍵問(wèn)題之AVFoundation探索(二)
6. AVFoundation框架解析(六)—— 視頻音頻的合成(一)
7. AVFoundation框架解析(七)—— 視頻組合和音頻混合調(diào)試
8. AVFoundation框架解析(八)—— 優(yōu)化用戶的播放體驗(yàn)
9. AVFoundation框架解析(九)—— AVFoundation的變化(一)
10. AVFoundation框架解析(十)—— AVFoundation的變化(二)
11. AVFoundation框架解析(十一)—— AVFoundation的變化(三)
12. AVFoundation框架解析(十二)—— AVFoundation的變化(四)
13. AVFoundation框架解析(十三)—— 構(gòu)建基本播放應(yīng)用程序
14. AVFoundation框架解析(十四)—— VAssetWriter和AVAssetReader的Timecode支持(一)
15. AVFoundation框架解析(十五)—— VAssetWriter和AVAssetReader的Timecode支持(二)
16. AVFoundation框架解析(十六)—— 一個(gè)簡(jiǎn)單示例之播放、錄制以及混合視頻(一)
17. AVFoundation框架解析(十七)—— 一個(gè)簡(jiǎn)單示例之播放、錄制以及混合視頻之源碼及效果展示(二)
18. AVFoundation框架解析(十八)—— AVAudioEngine之基本概覽(一)
19. AVFoundation框架解析(十九)—— AVAudioEngine之詳細(xì)說(shuō)明和一個(gè)簡(jiǎn)單示例(二)
20. AVFoundation框架解析(二十)—— AVAudioEngine之詳細(xì)說(shuō)明和一個(gè)簡(jiǎn)單示例源碼(三)
21. AVFoundation框架解析(二十一)—— 一個(gè)簡(jiǎn)單的視頻流預(yù)覽和播放示例之解析(一)
22. AVFoundation框架解析(二十二)—— 一個(gè)簡(jiǎn)單的視頻流預(yù)覽和播放示例之源碼(二)
23. AVFoundation框架解析(二十三) —— 向視頻層添加疊加層和動(dòng)畫(一)
24. AVFoundation框架解析(二十四) —— 向視頻層添加疊加層和動(dòng)畫(二)
25. AVFoundation框架解析(二十五) —— 播放、錄制和合并視頻簡(jiǎn)單示例(一)
26. AVFoundation框架解析(二十六) —— 播放、錄制和合并視頻簡(jiǎn)單示例(二)

開始

首先看下主要內(nèi)容:

了解如何使用AVAudioEngine構(gòu)建下一個(gè)最佳播客應(yīng)用程序! 實(shí)施音頻功能以暫停,跳過(guò),加快,放慢速度并更改應(yīng)用程序中音頻的音調(diào)。內(nèi)容來(lái)自翻譯。

下面就是寫作環(huán)境:

Swift 5, iOS 14, Xcode 12

接著就是正文了。

向大多數(shù)iOS開發(fā)人員提及音頻處理,他們會(huì)給您帶來(lái)恐懼的感覺(jué)。 這是因?yàn)?,?code>iOS 8之前,這意味著要深入研究底層的Core Audio框架 —— 只有少數(shù)勇敢的人敢于這樣做。 值得慶幸的是,隨著iOS 8AVAudioEngine的發(fā)布,這一切都在2014年發(fā)生了變化。 該AVAudioEngine教程將向您展示如何使用Apple的新的高級(jí)音頻工具包來(lái)制作音頻處理應(yīng)用程序,而無(wú)需深入研究Core Audio。

這是正確的! 您不再需要搜索模糊的,基于指針的C / C ++結(jié)構(gòu)和內(nèi)存緩沖區(qū)來(lái)收集原始音頻數(shù)據(jù)。 如果您了解基本的Swift代碼,則本教程將指導(dǎo)您完成向應(yīng)用程序添加音頻功能的過(guò)程。

在本教程中,您將使用AVAudioEngine構(gòu)建下一個(gè)出色的播客應(yīng)用:Raycast

您將在此應(yīng)用中實(shí)現(xiàn)的功能包括:

  • 播放本地音頻文件。
  • 查看播放進(jìn)度。
  • VU表觀察音頻信號(hào)電平。
  • 向前或向后跳過(guò)。
  • 更改播放速率和音調(diào)。

完成后,您將擁有一個(gè)出色的應(yīng)用程序,可以收聽播客和音頻文件。

下載入門項(xiàng)目,在Xcode中構(gòu)建并運(yùn)行您的項(xiàng)目,您將看到基本的用戶界面:

控件尚無(wú)任何作用。 實(shí)際上,由于音頻尚未準(zhǔn)備好播放,因此暫時(shí)將其禁用。 但是,將控件設(shè)置為調(diào)用將要實(shí)現(xiàn)的各自的視圖模型方法。

1. Understanding iOS Audio Frameworks

在進(jìn)入項(xiàng)目之前,這里是iOS音頻框架的快速概述:

  • CoreAudioAudioToolbox是底層C框架。
  • AVFoundation是一個(gè)Objective-C / Swift框架。
  • AVAudioEngineAVFoundation的一部分。

AVAudioEngine是一個(gè)類,它定義一組連接的音頻節(jié)點(diǎn)。 您將在項(xiàng)目中添加兩個(gè)節(jié)點(diǎn):AVAudioPlayerNodeAVAudioUnitTimePitch。

通過(guò)利用這些框架,您可以避免深入研究音頻信息的底層處理,而專注于要添加到應(yīng)用程序中的高級(jí)功能。

2. Setting up Audio

打開Models / PlayerViewModel.swift并查看內(nèi)部。 在頂部的Public properties下,您將看到視圖中用于布置音頻播放器的所有屬性。 提供了用于制作播放器的方法供您填寫。

將以下代碼添加到setupAudio()

// 1
guard let fileURL = Bundle.main.url(
  forResource: "Intro",
  withExtension: "mp3")
else {
  return
}

do {
  // 2
  let file = try AVAudioFile(forReading: fileURL)
  let format = file.processingFormat
  
  audioLengthSamples = file.length
  audioSampleRate = format.sampleRate
  audioLengthSeconds = Double(audioLengthSamples) / audioSampleRate
  
  audioFile = file
  
  // 3
  configureEngine(with: format)
} catch {
  print("Error reading the audio file: \(error.localizedDescription)")
}

仔細(xì)看看發(fā)生了什么:

  • 1) 這將獲取應(yīng)用程序捆綁包中包含的音頻文件的URL
  • 2) 音頻文件將轉(zhuǎn)換為AVAudioFile,并從文件的元數(shù)據(jù)中提取一些屬性。
  • 3) 準(zhǔn)備要播放的音頻文件的最后一步是設(shè)置audio engine。

將此代碼添加到configureEngine(with :)

// 1
engine.attach(player)
engine.attach(timeEffect)

// 2
engine.connect(
  player,
  to: timeEffect,
  format: format)
engine.connect(
  timeEffect,
  to: engine.mainMixerNode,
  format: format)

engine.prepare()

do {
  // 3
  try engine.start()
  
  scheduleAudioFile()
  isPlayerReady = true
} catch {
  print("Error starting the player: \(error.localizedDescription)")
}

詳細(xì)看下:

  • 1) 將播放器節(jié)點(diǎn)附加到引擎,在連接其他節(jié)點(diǎn)之前必須執(zhí)行此操作。 這些節(jié)點(diǎn)將產(chǎn)生,處理或輸出音頻。 audio engine提供了一個(gè)主要的混音器節(jié)點(diǎn),以將其連接到播放器節(jié)點(diǎn)。 默認(rèn)情況下,主混音器連接到引擎默認(rèn)輸出節(jié)點(diǎn)iOS設(shè)備揚(yáng)聲器。
  • 2) 將播放器和時(shí)間效果連接到引擎。 prepare()預(yù)分配所需的資源。
  • 3) 啟動(dòng)引擎,這將使設(shè)備準(zhǔn)備播放音頻。 狀態(tài)也將更新以準(zhǔn)備可視界面。

接下來(lái),將以下內(nèi)容添加到scheduleAudioFile()

guard
  let file = audioFile,
  needsFileScheduled
else {
  return
}

needsFileScheduled = false
seekFrame = 0

player.scheduleFile(file, at: nil) {
  self.needsFileScheduled = true
}

這樣可以安排播放整個(gè)音頻文件。 at:的參數(shù)是時(shí)間 —— AVAudioTime —— 將來(lái)您要播放音頻。 將其設(shè)置為nil立即開始播放。 該文件僅調(diào)度播放一次。 再次點(diǎn)擊播放不會(huì)從頭開始重新播放。 您需要重新調(diào)度才能再次播放。 音頻文件結(jié)束播放后,在完成block中設(shè)置了標(biāo)記needsFileScheduled。

調(diào)度音頻播放的其他方式包括:

  • scheduleBuffer(_:completionHandler :):這提供了一個(gè)預(yù)加載了音頻數(shù)據(jù)的緩沖區(qū)。
  • scheduleSegment(_:startingFrame:frameCount:at:completionHandler :):類似于scheduleFile(_:at :),不同之處在于您指定從哪個(gè)音頻幀開始播放以及要播放多少幀。

接下來(lái),您將解決用戶交互。 將以下內(nèi)容添加到playOrPause()中:

// 1
isPlaying.toggle()

if player.isPlaying {
  // 2
  player.pause()
} else {
  // 3
  if needsFileScheduled {
    scheduleAudioFile()
  }
  player.play()
}

這是在做什么:

  • 1) isPlaying屬性切換到下一個(gè)狀態(tài),該狀態(tài)會(huì)更改Play/Pause按鈕圖標(biāo)。
  • 2) 如果播放器當(dāng)前正在播放,則暫停。
  • 3) 如果播放器已經(jīng)暫停,它將繼續(xù)播放。 如果needsFileScheduledtrue,則需要重新調(diào)度音頻。

構(gòu)建并運(yùn)行。

輕按播放,您應(yīng)該會(huì)聽到RayThe raywenderlich.com Podcast播客精彩介紹。但是,沒(méi)有UI反饋-您不知道文件有多長(zhǎng)時(shí)間或文件在其中。


Adding Progress Feedback

現(xiàn)在您可以聽到音頻了,如何去看呢? 嗯,本教程未涵蓋內(nèi)容。 但是,您當(dāng)然可以查看音頻文件的進(jìn)度!

Models / PlayerViewModel.swift的底部,將以下內(nèi)容添加到setupDisplayLink()中:

displayLink = CADisplayLink(target: self, selector: #selector(updateDisplay))
displayLink?.add(to: .current, forMode: .default)
displayLink?.isPaused = true

提示:您可以通過(guò)按Control-6并鍵入要查找的名稱的一部分,在更長(zhǎng)的文件(如PlayerViewModel.swift)中找到方法和屬性!

CADisplayLink是一個(gè)計(jì)時(shí)器對(duì)象,可與顯示器的刷新率同步。 您可以使用selector updateDisplay實(shí)例化它。 然后,將其添加到run loop中(在本例中為默認(rèn)run loop)。 最后,它不需要開始運(yùn)行,因此請(qǐng)將isPaused設(shè)置為true

用以下代碼替換playOrPause()的實(shí)現(xiàn):

isPlaying.toggle()

if player.isPlaying {
  displayLink?.isPaused = true
  disconnectVolumeTap()
  
  player.pause()
} else {
  displayLink?.isPaused = false
  connectVolumeTap()
  
  if needsFileScheduled {
    scheduleAudioFile()
  }
  player.play()
}

此處的關(guān)鍵是通過(guò)在播放器狀態(tài)更改時(shí)設(shè)置displayLink?.isPaused來(lái)暫?;騿?dòng)displayLink。 您將在下面的VU計(jì)量器部分中了解connectVolumeTap()disconnectVolumeTap()

現(xiàn)在,您需要實(shí)現(xiàn)關(guān)聯(lián)的UI更新。 將以下內(nèi)容添加到updateDisplay()

// 1
currentPosition = currentFrame + seekFrame
currentPosition = max(currentPosition, 0)
currentPosition = min(currentPosition, audioLengthSamples)

// 2
if currentPosition >= audioLengthSamples {
  player.stop()
  
  seekFrame = 0
  currentPosition = 0
  
  isPlaying = false
  displayLink?.isPaused = true
  
  disconnectVolumeTap()
}

// 3
playerProgress = Double(currentPosition) / Double(audioLengthSamples)

let time = Double(currentPosition) / audioSampleRate
playerTime = PlayerTime(
  elapsedTime: time,
  remainingTime: audioLengthSeconds - time
)

這是怎么回事:

  • 1) 屬性seekFrame是一個(gè)偏移量offset,該偏移量最初設(shè)置為零,從currentFrame中加或者減去。 確保currentPosition不超出文件范圍。
  • 2) 如果currentPosition位于文件的末尾,則:
    • 停止播放器。
    • 重置seek和當(dāng)前位置屬性。
    • 暫停display link并重置isPlaying
    • 斷開volume tap。
  • 3) 將playerProgress更新到音頻文件中的當(dāng)前位置。 通過(guò)將currentPosition除以音頻文件的audioSampleRate來(lái)計(jì)算時(shí)間。 更新playerTime,這是一個(gè)將兩個(gè)進(jìn)度值作為輸入的結(jié)構(gòu)體。

該接口已經(jīng)連接到顯示playerProgresselapsedTimeremainingTime。

構(gòu)建并運(yùn)行,然后點(diǎn)擊播放/暫停。 您會(huì)再次聽到Ray的介紹,但是這次進(jìn)度條和計(jì)時(shí)器標(biāo)簽會(huì)提供缺少的狀態(tài)信息。


Implementing the VU Meter

現(xiàn)在是時(shí)候添加VU Meter功能了。 VU Meter通過(guò)根據(jù)音頻的音量描繪跳動(dòng)圖形來(lái)指示實(shí)時(shí)音頻。

您將使用定位在暫停圖標(biāo)欄之間的視圖。 播放音頻的平均功率決定了視圖的高度。 這是您進(jìn)行音頻處理的第一個(gè)機(jī)會(huì)。

您將在1k音頻樣本緩沖區(qū)上計(jì)算平均功率。 確定音頻樣本緩沖區(qū)平均功率的一種常用方法是計(jì)算樣本的Root Mean Square (RMS)。

平均功率是音頻樣本數(shù)據(jù)范圍的平均值的分貝表示。 您還應(yīng)該注意峰值功率,它是樣本數(shù)據(jù)范圍內(nèi)的最大值。

用以下代碼替換scaledPower(power :)中的代碼:

// 1
guard power.isFinite else {
  return 0.0
}

let minDb: Float = -80

// 2
if power < minDb {
  return 0.0
} else if power >= 1.0 {
  return 1.0
} else {
  // 3
  return (abs(minDb) - abs(power)) / abs(minDb)
}

scaledPower(power :)將負(fù)功率分貝值轉(zhuǎn)換為正值,以調(diào)整meterLevel值。 它的作用是:

  • 1) power.isFinite檢查以確保power是有效值(即不是NaN),如果不是,則返回0.0。
  • 2) 這會(huì)將VU表的動(dòng)態(tài)范圍[dynamic range(https://en.wikipedia.org/wiki/Dynamic_range#Audio)
    設(shè)置為80db。 對(duì)于低于-80.0的任何值,返回0.0。 iOS上的分貝值的范圍是-160db,接近無(wú)聲,最大功率為0db。 minDb設(shè)置為-80.0,動(dòng)態(tài)范圍為80db。 80提供了足夠的分辨率以像素為單位繪制界面。 更改此值以查看它如何影響VU表。
  • 3) 計(jì)算介于0.01.0之間的縮放比例值(scaled value)。

現(xiàn)在,將以下內(nèi)容添加到connectVolumeTap()

// 1
let format = engine.mainMixerNode.outputFormat(forBus: 0)
// 2
engine.mainMixerNode.installTap(
  onBus: 0,
  bufferSize: 1024,
  format: format
) { buffer, _ in
  // 3
  guard let channelData = buffer.floatChannelData else {
    return
  }
  
  let channelDataValue = channelData.pointee
  // 4
  let channelDataValueArray = stride(
    from: 0,
    to: Int(buffer.frameLength),
    by: buffer.stride)
    .map { channelDataValue[$0] }
  
  // 5
  let rms = sqrt(channelDataValueArray.map {
    return $0 * $0
  }
  .reduce(0, +) / Float(buffer.frameLength))
  
  // 6
  let avgPower = 20 * log10(rms)
  // 7
  let meterLevel = self.scaledPower(power: avgPower)

  DispatchQueue.main.async {
    self.meterLevel = self.isPlaying ? meterLevel : 0
  }
}

這里發(fā)生了很多事情,所以這里是細(xì)分:

  • 1) 獲取mainMixerNode輸出的數(shù)據(jù)格式。
  • 2) installTap(onBus:0,bufferSize:1024,format:format)使您可以訪問(wèn)mainMixerNode的輸出總線上的音頻數(shù)據(jù)。您請(qǐng)求的緩沖區(qū)大小為1024個(gè)字節(jié),但不能保證所請(qǐng)求的大小,尤其是當(dāng)您請(qǐng)求的緩沖區(qū)太小或太大時(shí)。 Apple的文檔沒(méi)有指定這些限制。completion block接收AVAudioPCMBufferAVAudioTime作為參數(shù)。您可以檢查buffer.frameLength以確定實(shí)際的緩沖區(qū)大小。
  • 3) buffer.floatChannelData為您提供了一個(gè)指向每個(gè)樣本數(shù)據(jù)的指針數(shù)組。 channelDataValueUnsafeMutablePointer <Float>的數(shù)組。
  • 4) 從UnsafeMutablePointer <Float>數(shù)組轉(zhuǎn)換為Float數(shù)組將使以后的計(jì)算更加容易。為此,請(qǐng)使用stride(from:to:by :)創(chuàng)建一個(gè)到channelDataValue的索引數(shù)組。然后,map {channelDataValue [$ 0]}訪問(wèn)數(shù)據(jù)值并將其存儲(chǔ)在channelDataValueArray中。
  • 5) 用均方根(Root Mean Square)計(jì)算功效涉及map/reduce/divide運(yùn)算。首先,map操作對(duì)數(shù)組中的所有值求平方,reduce操作將這些值求和。將平方和除以緩沖區(qū)大小,然后取平方根,在緩沖區(qū)中生成音頻樣本數(shù)據(jù)的RMS。該值應(yīng)介于0.01.0之間,但是在某些極端情況下,它可能是負(fù)值。
  • 6) 將RMS轉(zhuǎn)換為分貝。如果需要,這是聲學(xué)分貝參考acoustic decibel reference。分貝值應(yīng)在-1600之間,但是如果RMS為負(fù),則該分貝值為NaN。
  • 7) 將分貝縮放為適合您的VU儀表的值。

最后,將以下內(nèi)容添加到disconnectVolumeTap()

engine.mainMixerNode.removeTap(onBus: 0)
meterLevel = 0

AVAudioEngine每條總線僅允許單擊一次。 最好在不使用時(shí)將其刪除。

構(gòu)建并運(yùn)行,然后點(diǎn)擊播放/暫停:

VU表現(xiàn)在處于活動(dòng)狀態(tài),提供音頻數(shù)據(jù)的平均功率反饋。 播放音頻時(shí),您應(yīng)用的用戶將可以輕松地從視覺(jué)上辨別。


Implementing Skip

是時(shí)候?qū)崿F(xiàn)向前和向后跳過(guò)按鈕了。 在此應(yīng)用程序中,每個(gè)按鈕向前或向后搜索10秒鐘。

將以下內(nèi)容添加到seek(to :)

guard let audioFile = audioFile else {
  return
}

// 1
let offset = AVAudioFramePosition(time * audioSampleRate)
seekFrame = currentPosition + offset
seekFrame = max(seekFrame, 0)
seekFrame = min(seekFrame, audioLengthSamples)
currentPosition = seekFrame

// 2
let wasPlaying = player.isPlaying
player.stop()

if currentPosition < audioLengthSamples {
  updateDisplay()
  needsFileScheduled = false

  let frameCount = AVAudioFrameCount(audioLengthSamples - seekFrame)
  // 3
  player.scheduleSegment(
    audioFile,
    startingFrame: seekFrame,
    frameCount: frameCount,
    at: nil
  ) {
    self.needsFileScheduled = true
  }

  // 4
  if wasPlaying {
    player.play()
  }
}

這是逐一播放:

  • 1) 通過(guò)將時(shí)間乘以audioSampleRate,將時(shí)間(以秒為單位)轉(zhuǎn)換為幀位置,并將其添加到currentPosition。 然后,確保seekFrame不在文件的開頭之前,也不在文件的結(jié)尾之后。
  • 2) player.stop()不僅停止播放,而且清除所有先前安排的事件。 調(diào)用updateDisplay()將UI設(shè)置為新的currentPosition值。
  • 3) player.scheduleSegment(_:startingFrame:frameCount:at :)調(diào)度從音頻文件的seekFrame位置開始播放。 frameCount是要播放的幀數(shù)。 您要播放到文件末尾,因此將其設(shè)置為audioLengthSamples-seekFrame。 最后,at:nil指定立即開始播放,而不是在將來(lái)的某個(gè)時(shí)間開始播放。
  • 4) 如果在調(diào)用跳過(guò)之前正在播放音頻,請(qǐng)調(diào)用player.play()以繼續(xù)播放。

是時(shí)候用這種方法去seek了。 添加以下內(nèi)容到skip(forwards:)

let timeToSeek: Double

if forwards {
  timeToSeek = 10
} else {
  timeToSeek = -10
}

seek(to: timeToSeek)

視圖中的兩個(gè)跳過(guò)按鈕均調(diào)用此方法。 如果forwards參數(shù)為true,則音頻會(huì)向前跳過(guò)10秒鐘。 相反,如果參數(shù)為false,則音頻向后跳。

構(gòu)建并運(yùn)行,然后點(diǎn)擊播放/暫停。 點(diǎn)擊向前跳過(guò)和向后跳過(guò)按鈕以向前和向后跳過(guò)。 觀察progressBar和計(jì)數(shù)標(biāo)簽的變化。


Implementing Rate Change

下一個(gè)要添加的功能是任何音頻應(yīng)用程序的良好質(zhì)量。 如今,以高于1x倍的速度收聽播客是一種流行的功能。

將以下內(nèi)容添加到updateForRateSelection()

let selectedRate = allPlaybackRates[playbackRateIndex]
timeEffect.rate = Float(selectedRate.value)

在界面中,用戶將點(diǎn)擊分段選擇器以選擇播放速度。 您將選定的選項(xiàng)轉(zhuǎn)換為乘法器以發(fā)送到音頻播放器。

構(gòu)建并運(yùn)行,然后播放音頻。 調(diào)整速率控制,以聽取RayDru咖啡過(guò)多或過(guò)少時(shí)的聲音。


Implementing Pitch Change

要實(shí)現(xiàn)的最后一件事是更改播放的音調(diào)。 盡管音調(diào)控制不如改變播放速率實(shí)用,但仍然很有趣。

將以下內(nèi)容添加到updateForPitchSelection()

let selectedPitch = allPlaybackPitches[playbackPitchIndex]

timeEffect.pitch = 1200 * Float(selectedPitch.value)

根據(jù)AVAudioUnitTimePitch.pitch的文檔,該值以cents為單位。 一個(gè)度等于1200 cents。 在文件頂部聲明的allPlaybackPitches的值為-0.5、0、0.5。 將音調(diào)改變半個(gè)度可以使音頻保持完整,因此您仍然可以聽到每個(gè)單詞。 隨意玩這個(gè)數(shù)量或多或少會(huì)使聲音失真。

構(gòu)建并運(yùn)行。 調(diào)整音調(diào)以聽到令人毛骨悚然和/或松鼠的聲音。

回顧一下AVAudioEngine的簡(jiǎn)介,主要關(guān)注點(diǎn)是:

  • 從文件創(chuàng)建AVAudioFile。
  • AVAudioPlayer連接到AVAudioEngine。
  • 調(diào)度AVAudioFile通過(guò)·AVAudioPlayer·播放。

有了這些,您就可以在設(shè)備上播放音頻。在創(chuàng)建自己的播放器時(shí)有用的其他關(guān)鍵主題是:

  • 使用音頻單元(audio units)(例如AVAudioUnitTimePitch)向引擎添加效果。
  • 連接volume tap以使用來(lái)自AVAudioPCMBuffer的數(shù)據(jù)創(chuàng)建VU表。
  • 使用AVAudioFramePosition在音頻文件中seek位置。

要了解有關(guān)AVAudioEngine和相關(guān)的iOS音頻主題的更多信息,請(qǐng)查看:

有關(guān)媒體播放的更多信息,請(qǐng)參閱AppleAVFoundation上的文檔。

后記

本篇主要講述了基于AVAudioEngine的簡(jiǎn)單使用示例,感興趣的給個(gè)贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容