【问题标题】:Audio - Build/generate and play a pure waveform音频 - 构建/生成和播放纯波形
【发布时间】:2023-03-12 00:03:01
【问题描述】:

按照 Ben 的回答进行编辑

我正在尝试做一些对使用信号处理的人来说应该很容易的东西,但这让我很头疼。我只是想生成一个可以播放任意秒数的波形声音,可以少于或超过一秒(0.1 秒、0.88 秒、1.2 秒……)。

为了产生波浪声,我使用的是众所周知的方法:

+ (NSData*) WAVSoundForFrequency:(float)frequency duration:(float)seconds sampleRate:(unsigned int)sampleRate gain:(float)gain
{
    int frames = seconds * sampleRate;
    float* rawSound = (float*)malloc(frames*sizeof(float));
    if (rawSound == NULL) return nil;

    for (int i = 0; i < frames; i++)
      rawSound[i] = gain * sinf(i*2*M_PI*frequency/sampleRate);

    // converting to raw sound and returning the whole thing
}

这基本上被称为:

AVAudioPlayer* player = [self.audioPlayerManager buildSoundFrequency:200 duration:0.18 sampleRate:44100 gain:1.0];
player.volume = 1.0;
player.numberOfLoops = -1;
[player play];

问题在于,使用这些参数时,波形最终似乎并不完整,因此它会产生在每个循环中都能听到的咔嗒声。但是,如果我使用 0.5 秒或 1.0 秒的持续时间和 200 赫兹(当然使用调整的持续时间),则不会单击。仍然出于测试目的,如果我使用 400 赫兹或 440 赫兹而不是 200 赫兹,我现在有 0.5 秒的点击次数。

请注意,这里的循环仅用于测试并查找是否有点击。最后,声音应该只在所需的持续时间播放。

我猜那是因为持续时间不是波浪周期的整数倍,所以我调整了这样的调用,将想要的持续时间调整为最接近的持续时间,这将是一个周期的倍数想要的频率:

float wantedDuration = 0.18;
float hertz = 200;
int wantedSampleRate = 44100;

// Adjusting wanted duration so the duration contains an entiere number of waves
float oneWaveDurationInSeconds = 1.0/hertz;
int nbWavesNeeded = roundf(wantedDuration/oneWaveDurationInSeconds);
float adjustedDuration = nbWavesNeeded * oneWaveDurationInSeconds;

// Adjusting sample rate so one wave takes an entiere number of samples
float oneSampleDuration = 1.0/wantedSampleRate;

int adjustedSamplerate = wantedSampleRate;
while (YES) {
    oneSampleDuration = 1.0/adjustedSamplerate;
    if (roundf(oneWaveDurationInSeconds/oneSampleDuration) == oneWaveDurationInSeconds/oneSampleDuration) break;
    adjustedSamplerate++;
    NSLog(@"%d", adjustedSamplerate);
}

// Debug
float nbSamplesForOneWave = oneWaveDurationInSeconds / (1.0/adjustedSamplerate);
NSLog(@"nbSamplesForOneWave : %f", nbSamplesForOneWave);

// Execute
MyAudioPlayer* player = [self.manager preloadSoundFrequency:hertz duration:adjustedDuration sampleRate:adjustedSamplerate gain:1.0 
                                                 identifier:@"ii" category:@"Radar"];
player.volume = 1.0;
player.numberOfLoops = -1;
[player play];

但是还是有点击。

有人告诉我,问题可能出在采样率上。但我真的不明白为什么。据我了解,采样率是在一秒钟内定义的样本数。所以对我来说,它不依赖于持续时间或频率。
还有……为什么我不应该有 44100 采样质量的 0.18s 的声音……

但是无论如何...我想象如果我在一秒钟内采样 44100 个点,要求 0.18 的持续时间应该会导致 44100*0.18 个样本。这是int frames 表示的数字。所以我尝试替换

      rawSound[i] = gain * sinf(i*2*M_PI*frequency/sampleRate);

      rawSound[i] = gain * sinf(i*2*M_PI*frequency/frames);

这不起作用,只会使声音更加尖锐。我仍然不明白为什么。我认为这将是一个质量较差的声音,因为 tehre 只是更少的样本。

有人可以帮我为任何想要的延迟、所需的质量和频率生成(可能是可循环的)波形声音吗?

我确信这听起来 (:-)) 很容易,但我看不出实现这一目标的方法。

我试图放一个 NSLog 来查看使用的值(没有 Paul 斜坡的日志):

    if (i<20 || i > frames-20) NSLog(@"%f", rawSound[i]);

对于 440Hz、44100 采样率、1.0 持续时间(无调整): 没有点击

2011-10-31 01:02:34.110 testAudio[9602:207] 0.000000
2011-10-31 01:02:34.112 testAudio[9602:207] 0.062648
2011-10-31 01:02:34.113 testAudio[9602:207] 0.125051
2011-10-31 01:02:34.114 testAudio[9602:207] 0.186961
2011-10-31 01:02:34.115 testAudio[9602:207] 0.248138
2011-10-31 01:02:34.116 testAudio[9602:207] 0.308339
2011-10-31 01:02:34.116 testAudio[9602:207] 0.367330
2011-10-31 01:02:34.117 testAudio[9602:207] 0.424877
2011-10-31 01:02:34.117 testAudio[9602:207] 0.480755
2011-10-31 01:02:34.118 testAudio[9602:207] 0.534744
2011-10-31 01:02:34.119 testAudio[9602:207] 0.586632
2011-10-31 01:02:34.121 testAudio[9602:207] 0.636216
2011-10-31 01:02:34.121 testAudio[9602:207] 0.683300
2011-10-31 01:02:34.122 testAudio[9602:207] 0.727699
2011-10-31 01:02:34.123 testAudio[9602:207] 0.769240
2011-10-31 01:02:34.123 testAudio[9602:207] 0.807759
2011-10-31 01:02:34.124 testAudio[9602:207] 0.843104
2011-10-31 01:02:34.125 testAudio[9602:207] 0.875137
2011-10-31 01:02:34.126 testAudio[9602:207] 0.903732
2011-10-31 01:02:34.127 testAudio[9602:207] 0.928777
2011-10-31 01:02:34.130 testAudio[9602:207] -0.928790
2011-10-31 01:02:34.130 testAudio[9602:207] -0.903724
2011-10-31 01:02:34.131 testAudio[9602:207] -0.875102
2011-10-31 01:02:34.132 testAudio[9602:207] -0.843167
2011-10-31 01:02:34.132 testAudio[9602:207] -0.807795
2011-10-31 01:02:34.133 testAudio[9602:207] -0.769245
2011-10-31 01:02:34.134 testAudio[9602:207] -0.727667
2011-10-31 01:02:34.135 testAudio[9602:207] -0.683225
2011-10-31 01:02:34.135 testAudio[9602:207] -0.636283
2011-10-31 01:02:34.136 testAudio[9602:207] -0.586658
2011-10-31 01:02:34.137 testAudio[9602:207] -0.534724
2011-10-31 01:02:34.138 testAudio[9602:207] -0.480687
2011-10-31 01:02:34.138 testAudio[9602:207] -0.424978
2011-10-31 01:02:34.139 testAudio[9602:207] -0.367383
2011-10-31 01:02:34.140 testAudio[9602:207] -0.308342
2011-10-31 01:02:34.140 testAudio[9602:207] -0.248087
2011-10-31 01:02:34.141 testAudio[9602:207] -0.186856
2011-10-31 01:02:34.142 testAudio[9602:207] -0.125132
2011-10-31 01:02:34.142 testAudio[9602:207] -0.062676

对于 440Hz,44100 采样率,0.5 持续时间(无调整): 没有点击

2011-10-31 01:04:51.043 testAudio[9714:207] 0.000000
2011-10-31 01:04:51.045 testAudio[9714:207] 0.062648
2011-10-31 01:04:51.047 testAudio[9714:207] 0.125051
2011-10-31 01:04:51.049 testAudio[9714:207] 0.186961
2011-10-31 01:04:51.049 testAudio[9714:207] 0.248138
2011-10-31 01:04:51.050 testAudio[9714:207] 0.308339
2011-10-31 01:04:51.051 testAudio[9714:207] 0.367330
2011-10-31 01:04:51.052 testAudio[9714:207] 0.424877
2011-10-31 01:04:51.053 testAudio[9714:207] 0.480755
2011-10-31 01:04:51.054 testAudio[9714:207] 0.534744
2011-10-31 01:04:51.055 testAudio[9714:207] 0.586632
2011-10-31 01:04:51.055 testAudio[9714:207] 0.636216
2011-10-31 01:04:51.056 testAudio[9714:207] 0.683300
2011-10-31 01:04:51.057 testAudio[9714:207] 0.727699
2011-10-31 01:04:51.059 testAudio[9714:207] 0.769240
2011-10-31 01:04:51.060 testAudio[9714:207] 0.807759
2011-10-31 01:04:51.060 testAudio[9714:207] 0.843104
2011-10-31 01:04:51.061 testAudio[9714:207] 0.875137
2011-10-31 01:04:51.062 testAudio[9714:207] 0.903732
2011-10-31 01:04:51.062 testAudio[9714:207] 0.928777
2011-10-31 01:04:51.064 testAudio[9714:207] -0.928795
2011-10-31 01:04:51.065 testAudio[9714:207] -0.903730
2011-10-31 01:04:51.065 testAudio[9714:207] -0.875109
2011-10-31 01:04:51.066 testAudio[9714:207] -0.843109
2011-10-31 01:04:51.067 testAudio[9714:207] -0.807731
2011-10-31 01:04:51.067 testAudio[9714:207] -0.769253
2011-10-31 01:04:51.068 testAudio[9714:207] -0.727676
2011-10-31 01:04:51.069 testAudio[9714:207] -0.683324
2011-10-31 01:04:51.070 testAudio[9714:207] -0.636199
2011-10-31 01:04:51.070 testAudio[9714:207] -0.586669
2011-10-31 01:04:51.071 testAudio[9714:207] -0.534736
2011-10-31 01:04:51.072 testAudio[9714:207] -0.480806
2011-10-31 01:04:51.072 testAudio[9714:207] -0.424880
2011-10-31 01:04:51.073 testAudio[9714:207] -0.367282
2011-10-31 01:04:51.074 testAudio[9714:207] -0.308355
2011-10-31 01:04:51.074 testAudio[9714:207] -0.248100
2011-10-31 01:04:51.075 testAudio[9714:207] -0.186989
2011-10-31 01:04:51.076 testAudio[9714:207] -0.125025
2011-10-31 01:04:51.077 testAudio[9714:207] -0.062689

对于 440Hz、44100 采样率、0.25 持续时间(无调整): 硬点击

2011-10-31 01:05:25.245 testAudio[9759:207] 0.000000
2011-10-31 01:05:25.247 testAudio[9759:207] 0.062648
2011-10-31 01:05:25.249 testAudio[9759:207] 0.125051
2011-10-31 01:05:25.250 testAudio[9759:207] 0.186961
2011-10-31 01:05:25.251 testAudio[9759:207] 0.248138
2011-10-31 01:05:25.252 testAudio[9759:207] 0.308339
2011-10-31 01:05:25.252 testAudio[9759:207] 0.367330
2011-10-31 01:05:25.253 testAudio[9759:207] 0.424877
2011-10-31 01:05:25.254 testAudio[9759:207] 0.480755
2011-10-31 01:05:25.254 testAudio[9759:207] 0.534744
2011-10-31 01:05:25.255 testAudio[9759:207] 0.586632
2011-10-31 01:05:25.256 testAudio[9759:207] 0.636216
2011-10-31 01:05:25.257 testAudio[9759:207] 0.683300
2011-10-31 01:05:25.257 testAudio[9759:207] 0.727699
2011-10-31 01:05:25.258 testAudio[9759:207] 0.769240
2011-10-31 01:05:25.259 testAudio[9759:207] 0.807759
2011-10-31 01:05:25.260 testAudio[9759:207] 0.843104
2011-10-31 01:05:25.261 testAudio[9759:207] 0.875137
2011-10-31 01:05:25.261 testAudio[9759:207] 0.903732
2011-10-31 01:05:25.262 testAudio[9759:207] 0.928777
2011-10-31 01:05:25.263 testAudio[9759:207] -0.928781
2011-10-31 01:05:25.264 testAudio[9759:207] -0.903727
2011-10-31 01:05:25.264 testAudio[9759:207] -0.875135
2011-10-31 01:05:25.265 testAudio[9759:207] -0.843105
2011-10-31 01:05:25.266 testAudio[9759:207] -0.807763
2011-10-31 01:05:25.267 testAudio[9759:207] -0.769249
2011-10-31 01:05:25.267 testAudio[9759:207] -0.727692
2011-10-31 01:05:25.268 testAudio[9759:207] -0.683296
2011-10-31 01:05:25.269 testAudio[9759:207] -0.636217
2011-10-31 01:05:25.269 testAudio[9759:207] -0.586638
2011-10-31 01:05:25.270 testAudio[9759:207] -0.534756
2011-10-31 01:05:25.271 testAudio[9759:207] -0.480746
2011-10-31 01:05:25.271 testAudio[9759:207] -0.424873
2011-10-31 01:05:25.272 testAudio[9759:207] -0.367332
2011-10-31 01:05:25.273 testAudio[9759:207] -0.308348
2011-10-31 01:05:25.273 testAudio[9759:207] -0.248152
2011-10-31 01:05:25.274 testAudio[9759:207] -0.186952
2011-10-31 01:05:25.275 testAudio[9759:207] -0.125047
2011-10-31 01:05:25.276 testAudio[9759:207] -0.062652

编辑

我已将生成的声音样本(440Hz,444100 采样率,0.1 秒)写入文件,并使用声音编辑器打开它。多次剪切和粘贴声音以发出更长的声音:它播放时没有咔嗒声。通过 AVAudioPlayer 播放的相同声音样本会在每个样本结束时产生咔嗒声。所以问题似乎出在 AVAudioPlayer 中,原因我不明白,因为只有一些特定的值会产生这些点击。

编辑

我使用了 wav 生成的文件,并让它与带有循环的 AVAudioPlayer 一起播放:点击
我使用了相同的文件,并使用自定义库使其与 OpenAL 循环播放:不再单击。问题是 OpenAL 真的很难理解,并且会导致我的声音部分完全重写,只是为了那个糟糕的声音。

问题显然是使用了 AVAudioPlayer。如果你有解决方案让它发挥作用,它会节省我几天的时间。

【问题讨论】:

  • 向 Apple 提交错误报告。没有其他人可以使它工作。

标签: iphone cocoa-touch math audio signal-processing


【解决方案1】:

您选择的 200Hz 频率不是 44.1kHz 处的整数个样本。如果有 44100 个样本/秒/200 个周期/秒,您将获得 220.5 个样本/周期。所以任何时候nbWavesNeeded 都不是(取消一半样本)你的adjustedDuration当翻译成frames 有一个小的舍入误差会产生弹出。

(编辑为 440Hz 后问题更严重,因为 44100/440 具有更高的最大公因数)

据我了解原理,波频就是一秒有多少个上下波。持续时间是......持续时间,而 sampleRate 是一秒钟内有多少次削减。因此,如果我将波切割成 1、10、50 或 1000 个部分,它总是相同的波,只是不太精确。

这基本上是正确的。所以在hertz = 440 有“一秒钟内有 440 个上下波”,而在sampleRate = 44100 你的第二个被分为 44100 个切片。一个“上下波”需要多少片? 1/440 秒,或 44100 切片的 1/440,或 44100 / 440,即 100.2272727272... 所以如果 frames == 100.22727272.. 那么“上下波”的确切结束将对应于你的 @ 的确切结束987654329@。但是frames 是一个整数,所以你停在frames = 100,这样你就缩短了你的波浪。当声音播放器循环回 0 时,它确实想循环到 0.2272727... 但当然不能。你听到的就像流行音乐一样。

【讨论】:

  • 跟随你有些困难。你能给我一些例子吗?我的意思是在我的示例中,在 1.0、0.5 与 0.25 和 0.1 时,nbWavesNeeded 是偶数。所以如果我理解你的解释,我不应该有流行音乐。如何根据赫兹值调整采样率以防止弹出?
  • 据我了解的原理,波频就是一秒有多少个上下波。持续时间是......持续时间,而 sampleRate 是一秒钟内有多少次削减。因此,如果我将波切割成 1、10、50 或 1000 个部分,它总是相同的波,只是不太精确。所以我不明白你所说的两者之间的关系。
  • 呃,抱歉,我没关注你。我明白你的意思,但是......帧是持续时间* sampleRate。并且持续时间被调整以匹配整个波数。因此,无论我切入 10 或 44100 个部分,它都从 0 开始,并以 0 结束。如果最终我将它剪得太短,以至于最后一个样本,当循环时,第一个值是可能的缺失值,并且上一个循环的结束。所以循环应该是完美的。没有?
  • 非常感谢您的帮助。你可以看到我对我最终接受的答案的最后评论。
【解决方案2】:

在iOS上生成纯连续音的方法是不使用AVAudioPlayer,依靠它来正确拼接音频片段,而是使用Audio Queue API或者RemoteIO Audio Unit,控制音频的连续性自己进入回调缓冲区。

【讨论】:

  • 问题实际上不在于连续的声音。由于重复循环,这里很明显,但问题仍然存在,它没有循环。我有一个不是 endend 的声音,并且在它的末端产生了划痕。
  • 自我上次测试以来情况发生了变化(您可以查看我的上次编辑)。你知道一个关于如何比使用 AVAudioPlayer 但使用另一个播放器更容易播放声音的好教程吗?有人告诉我 OpenHAL,但我不明白如何在我的项目中使用它。我需要一些基本功能,如播放、停止、暂停、控制音量、自动循环和播放声音结束时的回调。
  • @Oliver - 抱歉,Audio Queue 和 RemoteIO Audio Unit API 不如 AVAudioPlayer API 好用。您必须(重新)编写代码以允许 API 回调您的应用程序以获取请求大小的示例缓冲区并计算适当的持续时间。我这里有部分教程:musingpaw.com/2011/04/…
  • 哎呀...我已经编辑了这个问题。问题似乎来自 AVAudioPlayer。但是 OpenAL、AudioQueue ……只是一场噩梦。您没有任何技巧可以帮助我节省几天的工作时间,只需使用 AVAudioPlayer 进行一些控制即可播放一点声音?
  • 为什么不将 AVAudioPlayer 与较长的 1 秒不点击缓冲区一起使用?使用 NSTimer 提前停止声音。
【解决方案3】:

在一般情况下,您想要播放的任何合成声音都需要应用起始和偏移斜坡(又名 attackdecay),否则您会在声音的开头和结尾,可以听到咔哒声。

几毫秒内的简单线性斜坡通常足以消除这种情况,但通常更喜欢指数或升余弦等更平滑的形状。

另一个好处是您不需要确保波形从零开始和结束,因为开始和偏移函数会处理这一点。

const int kAttack = (int)(0.005f * sampleRate); // 5 ms attack period (samples)
const int kDecay = (int)(0.010f * sampleRate);  // 10 ms decay period (samples)

for (int i = 0; i < frames; i++)
{
    float a = gain * sinf((float)i * 2.0f * M_PI * frequency / sampleRate);
    if (i < kAttack)                // if in attack (onset) period
    {
        a *= (float)i / kAttack;    // apply linear onset ramp
    }
    else if (i > frames - kDecay)  // if in decay (offset) period
    {
        a *= 1.0f - (float)(i - (frames - kDecay)) / kDecay;   // apply linear offset ramp
    }           

    rawSound[i] = a;
}

【讨论】:

  • 谢谢,我已将它包含在我的代码中,但这并不能解决问题。使用 0.18s 仍然有一个滴答声,如果我使用 0.5s 或 1s 仍然没有滴答声。如果声音被循环播放,那真的可以听到。
  • 为了确保我们没有追错问题,请尝试播放具有“问题”持续时间的静音缓冲区(所有值 0.0f),例如0.25s,看看是否还有点击。
  • 我确认。绝对没有声音,也没有爆音,带有“问题”持续时间/频率/采样率的零填充缓冲区。
  • 自我上次测试以来情况发生了变化(您可以查看我的上次编辑)。你知道一个关于如何播放声音比使用 AVAudioPlayer 但使用另一个播放器更容易的好教程吗?有人告诉我 OpenHAL,但我对如何在我的项目中使用它一无所知。我需要一些基本功能,如播放、停止、暂停、控制音量、自动循环和播放声音结束时的回调。
  • 出于某种原因,我仍然不明白看到所有已完成的测试,在声音上(在 2 个波上)放置一个非常短的淡入淡出解决了单独播放声音时的咔嗒声。这仍然不能解释为什么当用 AVAudioPlayer 循环时,声音会点击但不点击 OpenAL,但这不是最初的问题,所以我接受这个答案。
【解决方案4】:

在看到您的编辑和示例数据后,我有理由相信您正在避免我在其他答案中描述的使用您选择的特定值的陷阱。

让我提出一个替代方案:AVAudioPlayer 采用交错立体声采样(因为numberOfChannels 是 2),当您呈现偶数个采样时,您会听到两次音调(一个与另一个非常轻微异相)预期的频率。当您呈现奇数时(如上一个示例中所示),一个通道缺少一个样本,这会导致弹出。

这是一个疯狂的猜测,因为我不是 iOS 开发人员,我不明白为什么 numberOfChannels 是只读的而不是读写的。

【讨论】:

  • 我认为这是错误的方式。因为 1.0 秒,我有 44100 个样本。对于 0.1 秒,我有 4410 个样本。两者都是偶数,但第一个不弹出,第二个弹出。我已经添加并编辑,以调整采样率,以便一个波采用整个数量的切片。现在,一个波需要一个完整的切片数,而持续时间需要一个完整的波数。但这实际上并没有改变任何事情......
  • 我在最后一次测试旁边回到你身边。你可以看到我最后的编辑。你知道一种比 AVAudioPlayer 更容易播放声音的方法吗?我计划更换我的自定义类的播放器,但我发现很难理解,如查找文档,以获取其他播放声音的方式。有人告诉我 openHAL,但我不知道如何在我的项目中使用它。你知道一个好的教程吗?
猜你喜欢
  • 2017-07-08
  • 1970-01-01
  • 1970-01-01
  • 2012-11-16
  • 1970-01-01
  • 2011-06-28
  • 1970-01-01
  • 2013-01-06
  • 1970-01-01
相关资源
最近更新 更多