0

我正在用电脑键盘制作钢琴,我需要同时播放“.wav”音频。

我设法使用 SFML/Audio.hpp 和 SDL2/SDL_mixer.h 库完美地做到了这一点,但是下面代码的 PlayAudio :: play () 函数需要几毫秒来播放音频,但是延迟几乎无法察觉,当钢琴高速弹奏时,我注意到存在的小延迟。

例子:

#include <SFML / Audio.hpp>
#include <SDL2 / SDL_mixer.h>
#include "PlayAudio.h"

sf :: SoundBuffer buffer [10];
sf :: Sound pad [10];

void PlayAudio :: loadBank () {
  buffer [0] .loadFromFile ("src / audiosExa / a1.wav");
  pad [0] .setBuffer (buffer [0]);

  buffer [1] .loadFromFile ("src / audiosExa / a2.wav");
  pad [1] .setBuffer (buffer [1]);

  buffer [2] .loadFromFile ("src / audiosExa / a3.wav");
  pad [2] .setBuffer (buffer [2]);

   buffer [3] .loadFromFile ("src / audiosExa / a4.wav");
   pad [3] .setBuffer (buffer [3]);
};

void PlayAudio :: play (int i) {
  pad [i] .play ();
};

所以我考虑做同样的过程,但使用似乎更快的 alsa-lib,但我设法一次发出一个声音,我不能同时发出音频。我尝试使用线程,但只有在另一个声音结束后才会发出声音。

4

2 回答 2

2

几个月后,有可能以一种非常简单的方式解决这个大问题......在上面提到的代码中,我只使用了 SFML LIB,不幸的是它有一个非常高的延迟,正如我已经评论过的...... lib SDL 它根本没有被使用,它只是被导入。我重新编写了代码,这次只使用了 SDL 库,除了获得出色的延迟之外,我还设法让音频同时播放。按照下面的教程:https ://soundprogramming.net/programming/tutorial-using-sdl2-and-sdl_mixer-to-play-samples/

我对代码做了一些小改动,只在 Linux 上使用。

ALSA lib的使用相当复杂,因为级别很低。如果您不需要非常快速的音频,请将上面的代码与 lib SFML 一起使用,因为它实现起来非常简单快捷,否则使用 SDL 就可以成功。

于 2020-07-04T20:17:04.547 回答
0

您可以采用几种不同的方法在您的应用程序中获得复音。要么你自己混音,要么让 ALSA 帮你混音。

可能最简单的方法(也是最耗费资源的方法)是让 ALSA 为您进行混音。在这种情况下,您为每个打击垫打开一次 ALSA dmix 插件。您可以为每个焊盘创建一个类并将它们穿线,以便它们独立运行。每个类将音频数据加载到缓冲区,并在触发时将其播放回 ALSA dmix 插件。这是一个示例(使用提供 C++ libsox 音频文件处理、线程和 ALSA 处理的 gtkIOStream)

#include <Sox.H>
#include <ALSA.H>
#include <Thread.H>
using namespace ALSA;

class Pad : public PlayBack, WaitingThread {
  Eigen::Array<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> audio; // the Eigen array which holds the audio buffer

public:

  /// Constructor opens the audio plawyback device and reads in the audio file data
  Pad(char* devName, string audioFile) : PlayBack(devName) {
    Sox<int> sox; // instantiate libsox
    sox.openRead(audioFile); // open the audio file to read
    sox.read(audio); // read in the audio data
  }

  /// Configure the audio playback ([see here for example][2])
  configurePlayback(){
    // set channels, format, etc.
  }

  /// Playback the entire audio sound
  play(){
    *this<<audio; // stream the audio out through the PlayBack class
  }

  // In our threaded method, we wait to be signalled then we do playback, then wait again, [see here for examples on thread signalling][3]
  void *threadMain(void){
    while (continue) {
      cond.lock();
      cond.wait(); // wait till signalled
      play(); // playback the audio
      cond.unLock(); // unlock to be signalled again
    }
    return NULL;
  }
};

Pad pad1("dmix", "src/audiosExa/a1.wav"); // instantiate a pad
Pad pad2("dmix", "src/audiosExa/a2.wav"); // instantiate a pad
// in your UI thread, you need to run each of the Pad thread
pad1.run();
pad2.run();
// in your UI thread, you need to signal the waiting pad threads 
// Looks something like this for each pad
pad1.cond.lock(); pad1.cond.signal(); pad1.cond.unlock();

如果您想进行自己的混音,那么您将跟踪正在播放的音频垫以及您在垫的缓冲区中的位置,以便每次需要播放音频缓冲区时,将它们加在一起。在代码中(使用gtkIOStream),它看起来类似于取自ALSAPlaybackTest.C的这段代码:

const string deviceName="hw:0,0"; // could also be "default"
Playback playBack(deviceName.c_str()); // open the playback device
// configure the parameters of the playback device
Sox<short int> sox1, sox2; // instantiate two audio file readers (libsox)
int ret=sox1.openRead("src/audiosExa/a1.wav"); // load in their audio to an Eigen Array data buffer
Eigen::Array<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> pad1, pad2;
sox1.read(pad1);
ret=sox2.openRead("src/audiosExa/a2.wav");
sox2.read(pad2);
// now loop to constantly output, using outBuf to sum all pads which require output
Eigen::Array<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> outBuf;
while (continue){
  // construct your output buffer by adding portions of your pads here
  // something like : outBuf=pad1.block(...)+pad2.block(...)
  playBack<<outBuf; // play the audio data
}
于 2020-07-26T22:57:08.567 回答