在 c++ 中使用 alsa-lib 同时播放 '.wav' 个音频的简单示例?

Simple example of playing '.wav' audios simultaneously with alsa-lib in c++?

本文关键字:音频 简单 wav c++ alsa-lib 播放      更新时间:2024-04-29

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

我使用SFML/Audio.hpp和SDL2/SDL_mixer.h libs完美地做到了这一点,但下面代码的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,它似乎更快,但我设法一次发出一个声音,而且我不能同时发出音频。我试着使用线程,但只有在另一个声音结束后才会发出声音。

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

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

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

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

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

#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
}