音频 - 构建/生成并播放纯波形

4

根据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
}

这基本上称为“:basicaly with”。
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赫兹(当然要使用adjustedDuration),则没有点击声。仍然为了测试目的,如果我使用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.18秒的声音...

但无论如何...我想象过,如果我在一秒钟内采样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);

那不行,它只会使声音变得更尖锐。而且我仍然不明白为什么会这样。我以为声音质量会降低,因为采样率减少了。

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

我确信这听起来很容易,但我没有找到实现这个目标的方法。

我曾尝试过放置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。如果您有解决方法,可以节省我几天时间。


向苹果提交一个错误报告。其他人都无法使其正常工作。 - hotpaw2
4个回答

4
您选择的200Hz频率在44.1kHz下不是一个整数采样周期。如果每秒有44100个样本/ 200个循环,则会得到220.5个样本/循环。因此,当 `nbWavesNeeded` 不为偶数时(以取消半个样本),您的 `adjustedDuration` 在被转换为 `frames` 时会产生一个小舍入误差,从而产生弹跳声。
(在将其编辑为440Hz后,问题变得更糟,因为44100/ 440具有更高的最大公因数)
引用块中提到,“波形频率是一秒钟内有多少上下波浪。持续时间是...持续时间,而采样率是一秒钟内有多少切割。”因此,当 `hertz = 440` 时,每秒钟有“440个上下波浪”,并且使用 `sampleRate = 44100`,您的一秒钟被分成44100份。一个“上下波浪”需要多少份?1 / 440秒,或者是您的44100份的1 / 440,即 `44100 / 440`,即 `100.2272727272...`。因此,如果 `frames == 100.22727272..`,则“上下波浪”的确切结束将对应于您的 `rawSound` 的确切结束。但是, `frames` 是整数,因此您会在 `frames = 100` 处停止,因此您将波形截短了。当音频播放器回到0时,它实际上希望循环到 `0.2272727...`,但当然它不能。您会听到弹跳声。

我有些难以理解你的意思。你能给我一些例子吗?我的意思是,在我的例子中,当1.0、0.5和0.25、0.1时,nbWavesNeeded都是偶数。所以,如果我理解你的解释,我就不应该出现噼啪声了。如何根据赫兹值调整采样率以防止噼啪声呢? - Oliver
就我所理解的原则而言,波频是指每秒钟上下波动的次数。持续时间是...持续的时间,采样率是每秒钟切割的次数。因此,如果我将波形分成1、10、50或1000部分,它始终是相同的波形,只是不够精确。因此,我不明白你所说的这两者之间的关系。 - Oliver
抱歉,我不太明白你的意思。我理解你所说的,但是...帧是持续时间乘以采样率。而持续时间被调整为匹配整数波的数量。因此,无论我将其分成10或44100部分,它都从0开始,结束于0。如果最终我将其切割得太短,以至于在结尾处缺少一个样本,循环时第一个值就是可能丢失的值和上一个循环的结尾。所以循环应该是完美的。对吗? - Oliver
非常感谢您的帮助。您可以在我最终接受的答案中看到我的最后一条评论。 - Oliver

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

问题实际上并不是关于连续的声音。这只是因为重复循环而显得明显,但问题仍然存在,即它没有被循环。我有一个不会结束的声音,在其结尾处会产生刮痕。 - Oliver
自从我上次测试以来,情况已经发生了变化(您可以查看我的最后一次编辑)。您知道有什么好的教程可以像使用AVAudioPlayer一样容易地播放声音,但使用另一个播放器吗?我听说过OpenHAL,但我不明白如何在项目中使用它。我需要一些基本功能,例如播放、停止、暂停、控制音量、自动循环以及在播放结束时的回调。 - Oliver
@Oliver - 抱歉,音频队列和RemoteIO音频单元API不像AVAudioPlayer API那样易于使用。您需要(重新)编写代码以允许API回调您的应用程序以获取所请求大小的采样缓冲区并计算正确的持续时间。我在这里有一个部分教程:http://www.musingpaw.com/2011/04/iphone-programming-how-to-play-tone-at.html - hotpaw2
1
为什么不使用AVAudioPlayer,使用更长的1秒缓冲区来避免点击声?可以使用NSTimer提前停止声音。 - hotpaw2
非常感谢您的帮助。您可以在我最终接受的答案中看到我的最后一条评论。 - Oliver
显示剩余2条评论

0

通常情况下,任何你想播放的合成声音都需要应用起始和结束斜坡(也称为攻击衰减),否则在声音的开头和结尾会出现瞬态,这可能会听到点击声。

一个简单的线性斜坡在几毫秒的时间内通常足以消除这种问题,虽然更平滑的形状,如指数或升余弦通常更受欢迎。

额外的好处是,你不需要确保你的波形从零开始和结束,因为起始和结束函数会处理这个问题。

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.18秒发出滴答声,如果使用0.5秒或1秒仍然没有滴答声。如果声音循环播放,那么这真的可以听到。 - Oliver
为了确保我们不是在追踪错误的问题,尝试播放一个静音缓冲区(所有值为0.0f),持续时间为“问题”时长,例如0.25秒,看看是否仍然会出现点击声。 - Paul R
我确认。使用“问题”时长/频率/采样率的全零缓冲区,绝对没有声音或弹出窗口。 - Oliver
自从我上次测试以来,情况已经发生了变化(您可以查看我的最后一次编辑)。您知道有没有一个好的教程,可以像使用AVAudioPlayer一样轻松地播放声音,但使用另一个播放器?我听说过OpenHAL,但我不明白如何将其用于我的项目。我需要一些基本功能,如播放、停止、暂停、控制音量、自动循环以及在播放完毕后回调。 - Oliver
出于某种原因,我仍然不明白为什么在完成所有测试后,在声音(在2个波上)中放一个非常短的淡入淡出就解决了播放声音时的点击声。这仍然不能解释为什么当使用AVAudioPlayer循环播放时会出现噼啪声,但是使用OpenAL则不会出现,但这不是最初的问题,所以我接受这个答案。 - Oliver

0

在看到您的编辑和示例数据后,我相信您已经避免了我在另一个答案中描述的陷阱。

让我建议一种替代方案:AVAudioPlayer采用交错立体声样本(因为numberOfChannels为2),当您呈现偶数个样本时,您会听到两个音调(一个非常轻微地与另一个不同相位)以两倍于预期频率的速度。当您呈现奇数个样本(如您最后的示例)时,一个通道缺少一个样本,导致出现爆炸声。

这只是一个猜测,因为我不是iOS开发人员,也无法理解为什么numberOfChannels是只读的而不是可读写的。


我认为这是错误的方法。因为在1.0秒内,我有44100个样本。对于0.1秒,我有4410个样本。两者都是偶数,但第一个不弹出,而第二个则会。我已经添加了EDIT,调整采样率,使得一个波形占据整数个切片。现在一个波形占据整数个切片,持续时间占据整数个波形。但实际上这并没有改变任何事情... - Oliver
我回到你身边,接着我的最后一次测试。你可以看到我的最后一次编辑。你知道有没有一种像AVAudioPlayer那样容易播放声音的方法吗?我计划替换我的自定义类的播放器,但我发现很难理解,也很难找到其他播放声音的文档。有人告诉我openHAL,但我不知道如何在我的项目中使用它。你知道有什么好的教程吗? - Oliver

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接