将音频录制到音频缓冲区
捕获的音频数据最常见的用途是保存到声音缓冲区(sf::SoundBuffer)中,以便可以播放或保存到文件中。
可以使用sf::SoundBufferRecorder类的非常简单的接口来实现这一点:
// first check if an input audio device is available on the system
if (!sf::SoundBufferRecorder::isAvailable())
{
// error: audio capture is not available on this system
...
}
// create the recorder
sf::SoundBufferRecorder recorder;
// start the capture
recorder.start();
// wait...
// stop the capture
recorder.stop();
// retrieve the buffer that contains the captured audio data
const sf::SoundBuffer& buffer = recorder.getBuffer();
SoundBufferRecorder::isAvailable静态函数检查系统是否支持音频录制。如果返回false,则您将无法使用sf::SoundBufferRecorder类。
start和stop函数是不言自明的。捕获在其自己的线程中运行,这意味着您可以在start和stop之间进行任何操作。在捕获结束后,录制的音频数据可在用getBuffer函数获取的声音缓冲区中使用。
利用录制的数据,您可以执行以下操作:
- 保存到文件
buffer.saveToFile("my_record.ogg");
- 直接播放
sf::Sound sound(buffer);
sound.play();
- 访问原始音频数据并进行分析、转换等操作。
const sf::Int16* samples = buffer.getSamples();
std::size_t count = buffer.getSampleCount();
doSomething(samples, count);
如果你想在recorder被销毁或重新启动后使用捕获的音频数据,请不要忘记复制音频缓冲区。
选择输入设备
如果您的计算机连接了多个声音输入设备(例如麦克风、声音接口(外部声卡)或网络摄像头麦克风),则可以指定用于录制的设备。声音输入设备通过其名称进行标识。std::vector< std::string > 包含所有连接设备的名称,可以通过静态的SoundBufferRecorder::getAvailableDevices()函数获得。然后,通过将所选设备的名称传递给setDevice()方法,可以从列表中选择一个设备进行录制。甚至可以在录制过程中更改设备。
当前使用的设备名称可以通过调用getDevice()方法获得。如果您没有自己选择设备,则将使用默认设备。其名称可以通过静态SoundBufferRecorder::getDefaultDevice()函数获得。
以下是如何设置输入设备的简单示例:
// get the available sound input device names
std::vector<std::string> availableDevices = sf::SoundRecorder::getAvailableDevices();
// choose a device
std::string inputDevice = availableDevices[0];
// create the recorder
sf::SoundBufferRecorder recorder;
// set the device
if (!recorder.setDevice(inputDevice))
{
// error: device selection failed
...
}
// use recorder as usual
自定义录制
如果将捕获的数据存储在声音缓冲区中不是您想要的,您可以编写自己的录制器。这样做将允许您在捕获期间处理音频数据,(几乎)直接从录制设备。这样,您可以将捕获的音频流式传输到网络上,在其上执行实时分析等操作。
要编写自己的录制器,您必须从sf::SoundRecorder抽象基类继承。实际上,sf::SoundBufferRecorder只是这个类的内置特化。
您只需要在派生类中覆盖一个虚拟函数:onProcessSamples。每次捕获一个新的音频样本块时,它将被调用,因此这是您实现特定任务的地方。
默认情况下,音频样本每100毫秒提供给onProcessSamples方法一次。您可以使用setProcessingInterval方法更改间隔。如果您想实时处理录制的数据,则可以使用较小的间隔。请注意,这只是一个提示,实际周期可能会有所不同,因此不能依赖它来实现精确的定时。
还有两个额外的虚拟函数,您可以选择覆盖:onStart和onStop。它们分别在捕获开始/停止时调用。它们适用于初始化/清理任务。
以下是完整派生类的模板:
class MyRecorder : public sf::SoundRecorder
{
virtual bool onStart() // optional
{
// initialize whatever has to be done before the capture starts
...
// return true to start the capture, or false to cancel it
return true;
}
virtual bool onProcessSamples(const sf::Int16* samples, std::size_t sampleCount)
{
// do something useful with the new chunk of samples
...
// return true to continue the capture, or false to stop it
return true;
}
virtual void onStop() // optional
{
// clean up whatever has to be done after the capture is finished
...
}
}
isAvailable / start / stop函数定义在sf::SoundRecorder基类中,并因此继承在每个派生类中。这意味着您可以用与上文提到的sf::SoundBufferRecorder类完全相同的方式使用任何录音机类。
if (!MyRecorder::isAvailable())
{
// error...
}
MyRecorder recorder;
recorder.start();
...
recorder.stop();
线程问题
由于录音是在单独的线程中进行的,因此了解发生的事情以及发生的位置非常重要。
onStart将直接由start函数调用,因此在调用它的同一线程中执行。但是,onProcessSample和onStop将始终从SFML创建的内部录制线程中调用。
如果您的录音机使用可能在调用线程和录制线程中同时访问的数据,则必须保护它(例如使用互斥锁)以避免并发访问,这可能会导致未定义的行为–记录损坏的数据,崩溃等。
如果您对线程不太熟悉,可以参考相应的教程以获取更多信息。