添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

The following code is an iPad App for iOS 16.6 with Xcode 15.0 on macOS Sonoma 14.1.1.

In the CoreAudioPlayer.swift file in the method playAudioFile() i try to put the play position of the file back to frame 0 . But that fails with the error:

Init CoreAudioPlayer with `kick`
2023-11-29 19:58:42.477729+0100 HexaDeca[3249:1400596] Metal API Validation Enabled
Current Frame before playing: 0
Playing Optional(file:///private/var/containers/Bundle/Application/19E65197-60D7-415A-91AD-E91F82FF5BCC/HexaDeca.app/kick.wav)
Current Frame before playing: 49863
2023-11-29 19:58:57.538153+0100 HexaDeca[3249:1400575] [default]          ExtAudioFile.cpp:1116  about to throw -66568: seek to frame in audio file
Can't seek. Status: -66568

I tried various other frames to seek to but it seems the method ExtAudioFileSeek() always fails. The currentFrame delivers the correct frame. Before i play the file the first time it prints 0 on a second time it prints 49863. Which shows that after playing the file the first time it's frame position is at the end of the file. I would like to set the position back to frame 0 in order to be able to play the file again. Also made sure the file is "seekable", a 24 bit stereo wav file. Also it is used only once and ExtAudioFileOpenURL() should open it in read only mode.

//  ContentView.swift
import SwiftUI
struct ContentView: View {
    var body: some View {
        CircleButton(sampleName: "kick")
extension Color {
    // Define a Color initializer from a hex value
    init(hex: Int, alpha: Double = 1.0) {
        self.init(
            .sRGB,
            red: Double((hex >> 16) & 0xFF) / 255.0,
            green: Double((hex >> 8) & 0xFF) / 255.0,
            blue: Double((hex) & 0xFF) / 255.0,
            opacity: alpha
#Preview {
    ContentView()
//  CircleButton.swift
import SwiftUI
struct CircleButton: View {
    @State private var buttonSize: CGFloat = 56.0
    @StateObject private var coreAudioPlayer: CoreAudioPlayer
    init(sampleName: String) {
        self.sampleName = sampleName
        _coreAudioPlayer = StateObject(wrappedValue: CoreAudioPlayer(filePath: sampleName))
    var body: some View {
        Circle()
            .fill(Color(hex: 0x111111))
            .frame(width: buttonSize - 10, height: buttonSize - 10)
            .opacity(0.8)
            .onTapGesture {
                coreAudioPlayer.playAudioFile()
//  CoreAudioPlayer.swift
import AudioToolbox
import AVFoundation
// TODO: Fix File Resource Usage
//          After certain amount of played samples the App stops playing sounds with the error
//             2023-11-29 00:54:11.224507+0100 HexaDeca[2671:1058009] [default]
//             ExtAudioFile.cpp:210   about to throw -42: open audio file
//             Error opening audio file: snare2 -42
class CoreAudioPlayer: NSObject, ObservableObject {
    @Published var isPlaying = false
    private var audioFile: ExtAudioFileRef?
    private var audioGraph: AUGraph?
    private var playerNode: AUNode = 0
    private var audioUnit: AudioUnit?
    private var fileURL: URL?
    init(filePath: String) {
        print("Init CoreAudioPlayer with `\(filePath)`")
        super.init()
        var status = noErr
        guard let url = Bundle.main.url(forResource: filePath, withExtension: "wav") else {
            print("File not found")
            return
        self.fileURL = url
        // Safely unwrap fileURL before using it
        guard let fileCFURL = self.fileURL else {
            print("Invalid file URL")
            return
        status = ExtAudioFileOpenURL(fileCFURL as CFURL, &audioFile)
        guard status == noErr else {
            print("Error opening audio file: \(filePath) \(status)")
            return
        setupAudioGraph()
    func setupAudioGraph() {
        var status = noErr
        status = NewAUGraph(&audioGraph)
        guard status == noErr, let graph = audioGraph else {
            print("Error creating AUGraph: \(status)")
            return
        var componentDescription = AudioComponentDescription(
            componentType: kAudioUnitType_Output,
            componentSubType: kAudioUnitSubType_RemoteIO,
            componentManufacturer: kAudioUnitManufacturer_Apple,
            componentFlags: 0,
            componentFlagsMask: 0
        status = AUGraphAddNode(graph, &componentDescription, &playerNode)
        guard status == noErr else {
            print("Error adding node to AUGraph: \(status)")
            return
        status = AUGraphOpen(graph)
        guard status == noErr else {
            print("Error opening AUGraph: \(status)")
            return
        status = AUGraphNodeInfo(graph, playerNode, nil, &audioUnit)
        guard status == noErr else {
            print("Error getting node info: \(status)")
            return
        status = AUGraphInitialize(graph)
        guard status == noErr else {
            print("Error initializing AUGraph: \(status)")
            return
    func playAudioFile() {
        var status = noErr
        // Ensure audioFile is not nil before using it
        guard let audioFile = self.audioFile else {
            print("Audio file is not initialized")
            return
        var audioFormat = AudioStreamBasicDescription()
        var size = UInt32(MemoryLayout.size(ofValue: audioFormat))
        status = ExtAudioFileGetProperty(audioFile, kExtAudioFileProperty_FileDataFormat, &size, &audioFormat)
        guard status == noErr else {
            print("Error getting file data format: \(status)")
            return
        // Check the current position of the audio file
        var currentFrame: Int64 = 0
        status = ExtAudioFileTell(audioFile, &currentFrame)
        guard status == noErr else {
            print("Error getting current frame: \(status)")
            return
        print("Current Frame before playing: \(currentFrame)")
        // Only seek if the current frame is not 0
        if currentFrame != 0 {
            // Reset the read position to the beginning of the file
            status = ExtAudioFileSeek(audioFile, 0)
            guard status == noErr else {
                print("Can't seek. Status: \(status)")
                return
        guard let audioUnit = audioUnit else {
            print("AudioUnit is not initialized 1")
            return
        status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &audioFormat, size)
        guard status == noErr else {
            print("Error setting stream format: \(status)")
            return
        var renderCallback = AURenderCallbackStruct(inputProc: CoreAudioPlayer.renderingCallback, inputProcRefCon: Unmanaged.passUnretained(self).toOpaque())
        status = AudioUnitSetProperty(audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, &renderCallback, UInt32(MemoryLayout<AURenderCallbackStruct>.size))
        guard status == noErr else {
            print("Error setting render callback: \(status)")
            return
        status = AUGraphStart(audioGraph!)
        guard status == noErr else {
            print("Error starting AUGraph: \(status)")
            return
        print("Playing \(String(describing: self.fileURL))")
    private static let renderingCallback: AURenderCallback = { (inRefCon, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData) -> OSStatus in
        var total: Float = 0
        // Retrieve your audio player instance
        let audioPlayer = Unmanaged<CoreAudioPlayer>.fromOpaque(inRefCon).takeUnretainedValue()
        guard let ioData = ioData else { return noErr }
        // Clear the buffer before using it
        for bufferIndex in 0..<Int(ioData.pointee.mNumberBuffers) {
            let bufferPointer = UnsafeMutableAudioBufferListPointer(ioData)
            if let data = bufferPointer[bufferIndex].mData {
                memset(data, 0, Int(bufferPointer[bufferIndex].mDataByteSize))
        guard let audioFile = audioPlayer.audioFile else {
            return -1
        var bufferList = AudioBufferList()
        bufferList.mNumberBuffers = 1
        bufferList.mBuffers.mNumberChannels = 2 // Assuming stereo audio
        bufferList.mBuffers.mDataByteSize = inNumberFrames * 2 * UInt32(MemoryLayout<Float32>.size)
        bufferList.mBuffers.mData = malloc(Int(bufferList.mBuffers.mDataByteSize))
        var framesToRead = inNumberFrames
        var status = ExtAudioFileRead(audioFile, &framesToRead, &bufferList)
        guard status == noErr else {
            print("Error reading from file: \(status)")
            return status
        if framesToRead > 0 {
            if let sourceBuffer = bufferList.mBuffers.mData, let destinationBuffer = ioData.pointee.mBuffers.mData {
                memcpy(destinationBuffer, sourceBuffer, Int(bufferList.mBuffers.mDataByteSize))
            } else {
                // Handle the error, perhaps by filling the destination buffer with silence
        defer {
            if let buffer = bufferList.mBuffers.mData {
                free(buffer)
        return noErr
    deinit {
        if let graph = audioGraph {
            AUGraphStop(graph)
            AUGraphUninitialize(graph)
            AUGraphClose(graph)
        if let file = audioFile {
            ExtAudioFileDispose(file)