skip to Main Content

In my iOS application, using swift

i am getting audio from digital health device (BLE). In the form of audio data, to hear heart beats.

  1. We have ADPCM decoded file. That we have decoded using ADPCM decoder.

ADPCM Decoded file

We want to remove the background noise from it.

2.
For that, we apply low pass filter and high pass filter using apple’s DSP code. We are getting float samples, after after filtering.

So, first question is Conversion of Data to float is correct ?

Apple’s DSP Code

      let samples = convertUInt8ToFloats(sampleData: audioData)
      let samples1 =  musicProvider.loadAudioSamples(samples: samples)    


func convertUInt8ToFloats(sampleData: Data) -> [Float] {
        
        let factor = Float(UInt8.max)
        let samples = sampleData.withUnsafeBytes {
            UnsafeBufferPointer<UInt8>(start: $0, count: sampleData.count / MemoryLayout<UInt8>.size)
        }
        var floats: [Float] = Array(repeating: 0.0, count: samples.count)
        for i in 0..<samples.count {
            floats[i] = Float(samples[i]) / factor
        }
        return floats
    }

In Apple’s sample code they are loading assets like this, But i can not load assets like this, as i have raw audio file, is above convertUInt8ToFloats conversion same as below ?

    let asset = AVAsset(url: path.absoluteURL)
    
    let reader = try AVAssetReader(asset: asset)
    
    guard let track = try await asset.load(.tracks).first else {
        return nil
    }
    
    let outputSettings: [String: Int] = [
        AVFormatIDKey: Int(kAudioFormatLinearPCM),
        AVNumberOfChannelsKey: 1,
        AVLinearPCMIsBigEndianKey: 0,
        AVLinearPCMIsFloatKey: 1,
        AVLinearPCMBitDepthKey: 32,
        AVLinearPCMIsNonInterleaved: 1
    ]
    
    let output = AVAssetReaderTrackOutput(track: track,
                                          outputSettings: outputSettings)
    
    reader.add(output)
    reader.startReading()
    
    var samplesData = [Float]()
    
    while reader.status == .reading {
        if
            let sampleBuffer = output.copyNextSampleBuffer(),
            let dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer) {
            
            let bufferLength = CMBlockBufferGetDataLength(dataBuffer)
            let count = bufferLength / 4
            
            let data = [Float](unsafeUninitializedCapacity: count) {
                buffer, initializedCount in
                
                CMBlockBufferCopyDataBytes(dataBuffer,
                                           atOffset: 0,
                                           dataLength: bufferLength,
                                           destination: buffer.baseAddress!)
                
                initializedCount = count
            }
            
            samplesData.append(contentsOf: data)
        }
    }

2

Answers


  1.     func applyFilterToData(decoded: Data, filterEnabled: Bool = true) -> Data {
        var lastData = Data()
        for decodedData in decoded {
            let unsignedData = UInt8(decodedData)
            let scaledInput = Double(unsignedData) / 128.0 - 1.0  // Assuming 0-255 range
    
            var filteredOutput: Double
            if filterEnabled {
                filteredOutput = iirFilterNew.filter(scaledInput)
            } else {
                filteredOutput = scaledInput
            }
    
            // Scaling output (adjust if needed)
            let scaledOutput = filteredOutput * 128.0 + 128.0
            let outputUInt8 = UInt8(clamping: Int(round(scaledOutput)))
    
            lastData.append(outputUInt8)
        }
        return lastData
    }
    
    // Improved conversion with clamping
    func convertDoubleToUInt8(value: Double) -> UInt8 {
        return UInt8(clamping: Int(round(value)))
    }
    
    Login or Signup to reply.
  2. Actually i tried to play with ffplay -f s16le -ar 16000 -ac 1 -i input2 this format, and i got good sound of heart beat. the format should be s16le.

    This is all the information needed. You need to convert Data to SInt16 (i.e. s16le), and then SInt16 to Float.

    Using the Apple sample code, this would be:

    static func getAudioSamples(forResource: String,
                                withExtension: String) async throws -> (naturalTimeScale: CMTimeScale,
                                                                        data: [Float])? {
        
        guard let path = Bundle.main.url(forResource: forResource,
                                         withExtension: withExtension) else {
            return nil
        }
    
        // Load raw data
        let rawData = try Data(contentsOf: path)
    
        // Convert raw data into [S16Int] as little-endian
        let sIntLEData = rawData.withUnsafeBytes {
            Array($0.bindMemory(to: Int16.self)).map(Int16.init(littleEndian:))
        }
    
        // Scale [SInt16] to [Float] in the range [-1, 1]
        let samplesData = sIntLEData.map { Float($0) / Float(Int16.max) }
    
        // Set the sample-rate
        let naturalTimeScale = CMTimeScale(16_000)
    
        // Return as expected by caller.
        return (naturalTimeScale: naturalTimeScale,
                data: samplesData)
    }
    

    You can then apply filters as in Apple’s sample code.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search