Tam*_*gel 2 avfoundation swift watchos
我正在使用TinySoundFont在 watchOS 上使用 SF2 文件。我想实时播放框架生成的原始音频(这意味着按下tsf_note_on相应按钮就调用,tsf_render_short需要新数据就调用)。我正在使用 AVAudioSourceNode 来实现这一点。
尽管当我将其渲染到文件中时声音渲染得很好,但使用 AVAudioSourceNode 播放时确实很吵。(根据Rob Napier 的回答,这可能是因为我忽略了时间戳属性 - 我正在寻找解决该问题的解决方案。)是什么导致了此问题以及如何修复它?
我正在寻找一种实时渲染音频而不是预先计算音频的解决方案,因为我也想正确处理循环声音。
import SwiftUI
import AVFoundation
struct ContentView: View {
@ObservedObject var settings = Settings.shared
init() {
settings.prepare()
}
var body: some View {
Button("Play Sound") {
Settings.shared.playSound()
if !settings.engine.isRunning {
do {
try settings.engine.start()
} catch {
print(error)
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
import SwiftUI
import AVFoundation
class Settings: ObservableObject {
static let shared = Settings()
var engine: AVAudioEngine!
var sourceNode: AVAudioSourceNode!
var tinySoundFont: OpaquePointer!
func prepare() {
let soundFontPath = Bundle.main.path(forResource: "GMGSx", ofType: "sf2")
tinySoundFont = tsf_load_filename(soundFontPath)
tsf_set_output(tinySoundFont, TSF_MONO, 44100, 0)
setUpSound()
}
func setUpSound() {
if let engine = engine,
let sourceNode = sourceNode {
engine.detach(sourceNode)
}
engine = .init()
let mixerNode = engine.mainMixerNode
let audioFormat = AVAudioFormat(
commonFormat: .pcmFormatInt16,
sampleRate: 44100,
channels: 1,
interleaved: false
)
guard let audioFormat = audioFormat else {
return
}
sourceNode = AVAudioSourceNode(format: audioFormat) { silence, timeStamp, frameCount, audioBufferList in
guard let data = self.getSound(length: Int(frameCount)) else {
return 1
}
let ablPointer = UnsafeMutableAudioBufferListPointer(audioBufferList)
data.withUnsafeBytes { (intPointer: UnsafePointer<Int16>) in
for index in 0 ..< Int(frameCount) {
let value = intPointer[index]
// Set the same value on all channels (due to the inputFormat, there's only one channel though).
for buffer in ablPointer {
let buf: UnsafeMutableBufferPointer<Int16> = UnsafeMutableBufferPointer(buffer)
buf[index] = value
}
}
}
return noErr
}
engine.attach(sourceNode)
engine.connect(sourceNode, to: mixerNode, format: audioFormat)
do {
try AVAudioSession.sharedInstance().setCategory(.playback)
} catch {
print(error)
}
}
func playSound() {
tsf_note_on(tinySoundFont, 0, 60, 1)
}
func getSound(length: Int) -> Data? {
let array = [Int16]()
var storage = UnsafeMutablePointer<Int16>.allocate(capacity: length)
storage.initialize(from: array, count: length)
tsf_render_short(tinySoundFont, storage, Int32(length), 0)
let data = Data(bytes: storage, count: length)
storage.deallocate()
return data
}
}
Run Code Online (Sandbox Code Playgroud)
初始化AVAudioSourceNode器采用渲染块。在您使用的模式(实时播放)中,这是一个实时回调,因此您有一个非常紧迫的期限来用请求的数据填充块并返回它以便可以播放。您没有大量时间进行计算。您肯定没有时间访问文件系统。
在您的块中,您在每个渲染周期重新计算整个 WAV,然后将其写入磁盘,然后从磁盘读取它,然后填充所请求的块。您忽略请求的时间戳,并始终从样本零开始填充缓冲区。不匹配是引起嗡嗡声的原因。事实上,你的速度如此之慢可能就是导致音高下降的原因。
\n根据文件的大小,实现此目的的最简单方法是首先将所有内容解码到内存中,然后填充请求的时间戳和长度的缓冲区。看起来您的 C 代码已经生成了 PCM 数据,因此无需将其转换为 WAV 文件。它似乎已经是正确的格式。
\nApple 为信号发生器提供了一个很好的示例项目,您应该将其用作起点。下载它并确保它按预期工作。然后交换您的 SF2 代码。您可能还会发现有关此内容的视频很有帮助:What\xe2\x80\x99s New in AVAudioEngine。
\n这里使用的最简单的工具可能是 AVAudioPlayerNode。您的 SoundFontHelper 使事情变得更加复杂,因此我已将其删除并直接从 Swift 调用 TSF。为此,请创建一个名为tsf.c以下的文件:
#define TSF_IMPLEMENTATION\n#include "tsf.h"\nRun Code Online (Sandbox Code Playgroud)\n并将其添加到BridgingHeader.h:
#import "tsf.h"\nRun Code Online (Sandbox Code Playgroud)\n将 ContentView 简化为:
\nimport SwiftUI\n\nstruct ContentView: View {\n @ObservedObject var settings = Settings.shared\n\n init() {\n // You\'ll want error handling here.\n try! settings.prepare()\n }\n\n var body: some View {\n Button("Play Sound") {\n settings.play()\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n剩下的就是新版本的“设置”,这就是它的核心:
\nimport SwiftUI\nimport AVFoundation\n\nclass Settings: ObservableObject {\n static let shared = Settings()\n\n var engine = AVAudioEngine()\n let playerNode = AVAudioPlayerNode()\n var tsf: OpaquePointer\n var outputFormat = AVAudioFormat()\n\n init() {\n let soundFontPath = Bundle.main.path(forResource: "GMGSx", ofType: "sf2")\n tsf = tsf_load_filename(soundFontPath)\n\n engine.attach(playerNode)\n engine.connect(playerNode, to: engine.mainMixerNode, format: nil)\n\n updateOutputFormat()\n }\n\n // For simplicity, this object assumes the outputFormat does not change during its lifetime.\n // It\'s important to watch for route changes, and recreate this object if they occur. For details, see:\n // https://developer.apple.com/documentation/avfaudio/avaudiosession/responding_to_audio_session_route_changes\n func updateOutputFormat() {\n outputFormat = engine.mainMixerNode.outputFormat(forBus: 0)\n }\n\n func prepare() throws {\n // Start the engine\n try AVAudioSession.sharedInstance().setCategory(.playback)\n try engine.start()\n playerNode.play()\n\n updateOutputFormat()\n\n // Configure TSF. The only important thing here is the sample rate, which can be different on different hardware.\n // Core Audio has a defined format of "deinterleaved 32-bit floating point."\n tsf_set_output(tsf,\n TSF_STEREO_UNWEAVED, // mode\n Int32(outputFormat.sampleRate), // sampleRate\n 0) // gain\n }\n\n func play() {\n tsf_note_on(tsf,\n 0, // preset_index\n 60, // key (middle C)\n 1.0) // velocity\n\n // These tones have a long falloff, so you want a lot of source data. This is 10s.\n let frameCount = 10 * Int(outputFormat.sampleRate)\n\n // Create a buffer for the samples\n let buffer = AVAudioPCMBuffer(pcmFormat: outputFormat, frameCapacity: AVAudioFrameCount(frameCount))!\n buffer.frameLength = buffer.frameCapacity\n\n // Render the samples. Do not mix. This buffer has been extended to\n // the needed size by the assignment to `frameLength` above. The call to\n // `assumingMemoryBound` is known to be correct because the format is Float32.\n let ptr = buffer.audioBufferList.pointee.mBuffers.mData?.assumingMemoryBound(to: Float.self)\n tsf_render_float(tsf,\n ptr, // buffer\n Int32(frameCount), // samples\n 0) // mixing (do not mix)\n\n // All done. Play the buffer, interrupting whatever is currently playing\n playerNode.scheduleBuffer(buffer, at: nil, options: .interrupts)\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n你可以在我的 fork找到完整版本。您还可以看到第一次提交,这是维护 SoundFontHelper 并进行转换来处理它的另一种方法,但首先正确渲染音频要简单得多。
\n| 归档时间: |
|
| 查看次数: |
496 次 |
| 最近记录: |