首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >AVAudio和脱机手动渲染模式-无法将频率较高的缓冲区写入输出文件

AVAudio和脱机手动渲染模式-无法将频率较高的缓冲区写入输出文件
EN

Stack Overflow用户
提问于 2020-11-21 06:52:20
回答 1查看 202关注 0票数 0

我正在读取一个输入文件,在脱机手动渲染模式下,我想执行振幅调制,并将结果写入输出文件。

为了便于测试,我产生了纯正弦波-这对于低于6.000 Hz的频率很好。对于更高的频率(我的目标是使用大约20.000赫兹),信号(因此收听输出文件)失真,频谱在8.000赫兹结束-不再是纯频谱,在0到8.000赫兹之间有多个峰值。

下面是我的代码片段:

代码语言:javascript
复制
    let outputFile: AVAudioFile

    do {
        let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        let outputURL = documentsURL.appendingPathComponent("output.caf")
        outputFile = try AVAudioFile(forWriting: outputURL, settings: sourceFile.fileFormat.settings)
    } catch {
        fatalError("Unable to open output audio file: \(error).")
    }

    var sampleTime: Float32 = 0

    while engine.manualRenderingSampleTime < sourceFile.length {
        do {
            let frameCount = sourceFile.length - engine.manualRenderingSampleTime
            let framesToRender = min(AVAudioFrameCount(frameCount), buffer.frameCapacity)
            
            let status = try engine.renderOffline(framesToRender, to: buffer)
            
            switch status {
            
            case .success:
                // The data rendered successfully. Write it to the output file.
                let sampleRate:Float = Float((mixer.outputFormat(forBus: 0).sampleRate))

                let modulationFrequency: Float = 20000.0
                
                for i in stride(from:0, to: Int(buffer.frameLength), by: 1) {
                    let val = sinf(2.0 * .pi * modulationFrequency * Float(sampleTime) / Float(sampleRate))
                    // TODO: perform modulation later
                    buffer.floatChannelData?.pointee[Int(i)] = val
                    sampleTime = sampleTime + 1.0
                }

                try outputFile.write(from: buffer)
                
            case .insufficientDataFromInputNode:
                // Applicable only when using the input node as one of the sources.
                break
                
            case .cannotDoInCurrentContext:
                // The engine couldn't render in the current render call.
                // Retry in the next iteration.
                break
                
            case .error:
                // An error occurred while rendering the audio.
                fatalError("The manual rendering failed.")
            @unknown default:
                fatalError("unknown error")
            }
        } catch {
            fatalError("The manual rendering failed: \(error).")
        }
    }

或者有谁知道如何产生具有更高频率的正弦波的输出文件?

我认为手动渲染模式不足以处理更高的频率。

更新:在此期间,我使用Audacity分析了输出文件。上面是1.000 Hz的波形,下面是20.000 Hz的波形:

当我放大时,我看到以下内容:

比较两个输出文件的频谱,我得到以下结果:

奇怪的是,频率越高,振幅就越接近于零。此外,我在第二个频谱中看到更多的频率。

与结果相关的一个新问题是以下算法的正确性:

代码语言:javascript
复制
// Process the audio in `renderBuffer` here
for i in 0..<Int(renderBuffer.frameLength) {
    let val = sinf(1000.0*Float(index) *2 * .pi / Float(sampleRate))
    renderBuffer.floatChannelData?.pointee[i] = val
    index += 1
}

我确实检查了采样率,它是48000 -我知道当采样频率大于被采样信号的最大频率的两倍时,原始信号可以被忠实地重建。

更新2:

代码语言:javascript
复制
    settings[AVFormatIDKey] = kAudioFormatAppleLossless
    settings[AVAudioFileTypeKey] = kAudioFileCAFType
    settings[AVSampleRateKey] = readBuffer.format.sampleRate
    settings[AVNumberOfChannelsKey] = 1
    settings[AVLinearPCMIsFloatKey] = (readBuffer.format.commonFormat == .pcmFormatInt32)
    settings[AVSampleRateConverterAudioQualityKey] = AVAudioQuality.max
    settings[AVLinearPCMBitDepthKey] = 32
    settings[AVEncoderAudioQualityKey] = AVAudioQuality.max

现在输出信号的质量更好了,但并不完美。我得到了更高的振幅,但在频谱分析仪中总是不止一个频率。也许解决方法可以包括应用高通滤波器?

在此期间,我确实使用了一种SignalGenerator,将处理过的缓冲区(使用正弦波)直接传输到扬声器-在这种情况下,输出是完美的。我认为将信号路由到文件会导致这样的问题。

EN

回答 1

Stack Overflow用户

发布于 2020-11-22 00:36:52

手动渲染模式的速度不是问题,因为手动渲染上下文中的速度有点无关紧要。

下面是从源文件到输出文件的手动渲染框架代码:

代码语言:javascript
复制
// Open the input file
let file = try! AVAudioFile(forReading: URL(fileURLWithPath: "/tmp/test.wav"))

let engine = AVAudioEngine()
let player = AVAudioPlayerNode()

engine.attach(player)

engine.connect(player, to:engine.mainMixerNode, format: nil)

// Run the engine in manual rendering mode using chunks of 512 frames
let renderSize: AVAudioFrameCount = 512

// Use the file's processing format as the rendering format
let renderFormat = AVAudioFormat(commonFormat: file.processingFormat.commonFormat, sampleRate: file.processingFormat.sampleRate, channels: file.processingFormat.channelCount, interleaved: true)!
let renderBuffer = AVAudioPCMBuffer(pcmFormat: renderFormat, frameCapacity: renderSize)!

try! engine.enableManualRenderingMode(.offline, format: renderFormat, maximumFrameCount: renderBuffer.frameCapacity)

try! engine.start()
player.play()

// The render format is also the output format
let output = try! AVAudioFile(forWriting: URL(fileURLWithPath: "/tmp/foo.wav"), settings: renderFormat.settings, commonFormat: renderFormat.commonFormat, interleaved: renderFormat.isInterleaved)

// Read using a buffer sized to produce `renderSize` frames of output
let readBuffer = AVAudioPCMBuffer(pcmFormat: file.processingFormat, frameCapacity: renderSize)!

// Process the file
while true {
    do {
        // Processing is finished if all frames have been read
        if file.framePosition == file.length {
            break
        }

        try file.read(into: readBuffer)
        player.scheduleBuffer(readBuffer, completionHandler: nil)

        let result = try engine.renderOffline(readBuffer.frameLength, to: renderBuffer)

        // Process the audio in `renderBuffer` here

        // Write the audio
        try output.write(from: renderBuffer)
        if result != .success {
            break
        }
    }
    catch {
        break
    }
}

player.stop()
engine.stop()

下面的代码片段显示了如何在整个引擎中设置相同的采样率:

代码语言:javascript
复制
// Replace:
//engine.connect(player, to:engine.mainMixerNode, format: nil)

// With:
let busFormat = AVAudioFormat(standardFormatWithSampleRate: file.fileFormat.sampleRate, channels: file.fileFormat.channelCount)

engine.disconnectNodeInput(engine.outputNode, bus: 0)
engine.connect(engine.mainMixerNode, to: engine.outputNode, format: busFormat)

engine.connect(player, to:engine.mainMixerNode, format: busFormat)

使用以下命令验证采样率是否始终相同:

代码语言:javascript
复制
NSLog("%@", engine)
代码语言:javascript
复制
________ GraphDescription ________
AVAudioEngineGraph 0x7f8194905af0: initialized = 0, running = 0, number of nodes = 3

     ******** output chain ********

     node 0x600001db9500 {'auou' 'ahal' 'appl'}, 'U'
         inputs = 1
             (bus0, en1) <- (bus0) 0x600001d80b80, {'aumx' 'mcmx' 'appl'}, [ 2 ch,  48000 Hz, 'lpcm' (0x00000029) 32-bit little-endian float, deinterleaved]

     node 0x600001d80b80 {'aumx' 'mcmx' 'appl'}, 'U'
         inputs = 1
             (bus0, en1) <- (bus0) 0x600000fa0200, {'augn' 'sspl' 'appl'}, [ 2 ch,  48000 Hz, 'lpcm' (0x00000029) 32-bit little-endian float, deinterleaved]
         outputs = 1
             (bus0, en1) -> (bus0) 0x600001db9500, {'auou' 'ahal' 'appl'}, [ 2 ch,  48000 Hz, 'lpcm' (0x00000029) 32-bit little-endian float, deinterleaved]

     node 0x600000fa0200 {'augn' 'sspl' 'appl'}, 'U'
         outputs = 1
             (bus0, en1) -> (bus0) 0x600001d80b80, {'aumx' 'mcmx' 'appl'}, [ 2 ch,  48000 Hz, 'lpcm' (0x00000029) 32-bit little-endian float, deinterleaved]
______________________________________
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64937978

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档