Merge pull request #470 from Foaly/MultiSoundInput

Added support for selecting the audio capture device (fixes #220)
This commit is contained in:
Laurent Gomila 2013-10-11 07:39:07 -07:00
commit db77b76d91
2 changed files with 161 additions and 15 deletions

View File

@ -32,6 +32,7 @@
#include <SFML/System/Thread.hpp> #include <SFML/System/Thread.hpp>
#include <SFML/System/Time.hpp> #include <SFML/System/Time.hpp>
#include <vector> #include <vector>
#include <string>
namespace sf namespace sf
@ -59,13 +60,20 @@ public :
/// This function uses its own thread so that it doesn't block /// This function uses its own thread so that it doesn't block
/// the rest of the program while the capture runs. /// the rest of the program while the capture runs.
/// Please note that only one capture can happen at the same time. /// Please note that only one capture can happen at the same time.
/// You can select which capture device will be used, by passing
/// the name to the setDevice() method. If none was selected
/// before, the default capture device will be used. You can get a
/// list of the names of all available capture devices by calling
/// getAvailableDevices().
/// ///
/// \param sampleRate Desired capture rate, in number of samples per second /// \param sampleRate Desired capture rate, in number of samples per second
/// ///
/// \see stop /// \return True, if start of capture was successful
///
/// \see stop, getAvailableDevices
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void start(unsigned int sampleRate = 44100); bool start(unsigned int sampleRate = 44100);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Stop the capture /// \brief Stop the capture
@ -87,6 +95,54 @@ public :
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
unsigned int getSampleRate() const; unsigned int getSampleRate() const;
////////////////////////////////////////////////////////////
/// \brief Get a list of the names of all availabe audio capture devices
///
/// This function returns a vector of strings, containing
/// the names of all availabe audio capture devices.
///
/// \return A vector of strings containing the names
///
////////////////////////////////////////////////////////////
static std::vector<std::string> getAvailableDevices();
////////////////////////////////////////////////////////////
/// \brief Get the name of the default audio capture device
///
/// This function returns the name of the default audio
/// capture device. If none is available, an empty string
/// is returned.
///
/// \return The name of the default audio capture device
///
////////////////////////////////////////////////////////////
static std::string getDefaultDevice();
////////////////////////////////////////////////////////////
/// \brief Set the audio capture device
///
/// This function sets the audio capture device to the device
/// with the given \a name. It can be called on the fly (i.e:
/// while recording). If you do so while recording and
/// opening the device fails, it stops the recording.
///
/// \param The name of the audio capture device
///
/// \return True, if it was able to set the requested device
///
/// \see getAvailableDevices, getDefaultDevice
///
////////////////////////////////////////////////////////////
bool setDevice(const std::string& name);
////////////////////////////////////////////////////////////
/// \brief Get the name of the current audio capture device
///
/// \return The name of the current audio capture device
///
////////////////////////////////////////////////////////////
const std::string& getDevice() const;
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Check if the system supports audio capture /// \brief Check if the system supports audio capture
/// ///
@ -203,8 +259,9 @@ private :
Thread m_thread; ///< Thread running the background recording task Thread m_thread; ///< Thread running the background recording task
std::vector<Int16> m_samples; ///< Buffer to store captured samples std::vector<Int16> m_samples; ///< Buffer to store captured samples
unsigned int m_sampleRate; ///< Sample rate unsigned int m_sampleRate; ///< Sample rate
sf::Time m_processingInterval; ///< Time period between calls to onProcessSamples sf::Time m_processingInterval; ///< Time period between calls to onProcessSamples
bool m_isCapturing; ///< Capturing state bool m_isCapturing; ///< Capturing state
std::string m_deviceName; ///< Name of the audio capture device
}; };
} // namespace sf } // namespace sf
@ -244,10 +301,17 @@ private :
/// availability with the isAvailable() function. If it returns /// availability with the isAvailable() function. If it returns
/// false, then any attempt to use an audio recorder will fail. /// false, then any attempt to use an audio recorder will fail.
/// ///
/// If you have multiple sound input devices connected to your
/// computer (for example: microphone, external soundcard, webcam mic, ...)
/// you can get a list of all available devices throught the
/// getAvailableDevices() function. You can then select a device
/// by calling setDevice() with the appropiate device. Otherwise
/// the default capturing device will be used.
///
/// It is important to note that the audio capture happens in a /// It is important to note that the audio capture happens in a
/// separate thread, so that it doesn't block the rest of the /// separate thread, so that it doesn't block the rest of the
/// program. In particular, the onProcessSamples and onStop /// program. In particular, the onProcessSamples virtual function
/// virtual functions (but not onStart) will be called /// (but not onStart and not onStop) will be called
/// from this separate thread. It is important to keep this in /// from this separate thread. It is important to keep this in
/// mind, because you may have to take care of synchronization /// mind, because you may have to take care of synchronization
/// issues if you share data between threads. /// issues if you share data between threads.
@ -285,7 +349,10 @@ private :
/// if (CustomRecorder::isAvailable()) /// if (CustomRecorder::isAvailable())
/// { /// {
/// CustomRecorder recorder; /// CustomRecorder recorder;
/// recorder.start(); ///
/// if (!recorder.start())
/// return -1;
///
/// ... /// ...
/// recorder.stop(); /// recorder.stop();
/// } /// }

View File

@ -30,6 +30,7 @@
#include <SFML/Audio/ALCheck.hpp> #include <SFML/Audio/ALCheck.hpp>
#include <SFML/System/Sleep.hpp> #include <SFML/System/Sleep.hpp>
#include <SFML/System/Err.hpp> #include <SFML/System/Err.hpp>
#include <cstring>
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma warning(disable : 4355) // 'this' used in base member initializer list #pragma warning(disable : 4355) // 'this' used in base member initializer list
@ -51,6 +52,9 @@ m_processingInterval(milliseconds(100)),
m_isCapturing (false) m_isCapturing (false)
{ {
priv::ensureALInit(); priv::ensureALInit();
// Set the device name to the default device
m_deviceName = getDefaultDevice();
} }
@ -62,28 +66,28 @@ SoundRecorder::~SoundRecorder()
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void SoundRecorder::start(unsigned int sampleRate) bool SoundRecorder::start(unsigned int sampleRate)
{ {
// Check if the device can do audio capture // Check if the device can do audio capture
if (!isAvailable()) if (!isAvailable())
{ {
err() << "Failed to start capture : your system cannot capture audio data (call SoundRecorder::isAvailable to check it)" << std::endl; err() << "Failed to start capture : your system cannot capture audio data (call SoundRecorder::isAvailable to check it)" << std::endl;
return; return false;
} }
// Check that another capture is not already running // Check that another capture is not already running
if (captureDevice) if (captureDevice)
{ {
err() << "Trying to start audio capture, but another capture is already running" << std::endl; err() << "Trying to start audio capture, but another capture is already running" << std::endl;
return; return false;
} }
// Open the capture device for capturing 16 bits mono samples // Open the capture device for capturing 16 bits mono samples
captureDevice = alcCaptureOpenDevice(NULL, sampleRate, AL_FORMAT_MONO16, sampleRate); captureDevice = alcCaptureOpenDevice(m_deviceName.c_str(), sampleRate, AL_FORMAT_MONO16, sampleRate);
if (!captureDevice) if (!captureDevice)
{ {
err() << "Failed to open the audio capture device" << std::endl; err() << "Failed to open the audio capture device with the name: " << m_deviceName << std::endl;
return; return false;
} }
// Clear the array of samples // Clear the array of samples
@ -101,7 +105,11 @@ void SoundRecorder::start(unsigned int sampleRate)
// Start the capture in a new thread, to avoid blocking the main thread // Start the capture in a new thread, to avoid blocking the main thread
m_isCapturing = true; m_isCapturing = true;
m_thread.launch(); m_thread.launch();
return true;
} }
return false;
} }
@ -111,6 +119,9 @@ void SoundRecorder::stop()
// Stop the capturing thread // Stop the capturing thread
m_isCapturing = false; m_isCapturing = false;
m_thread.wait(); m_thread.wait();
// Notify derived class
onStop();
} }
@ -121,6 +132,77 @@ unsigned int SoundRecorder::getSampleRate() const
} }
////////////////////////////////////////////////////////////
std::vector<std::string> SoundRecorder::getAvailableDevices()
{
std::vector<std::string> deviceNameList;
const ALchar *deviceList = alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);
if (deviceList)
{
while (*deviceList)
{
deviceNameList.push_back(deviceList);
deviceList += std::strlen(deviceList) + 1;
}
}
return deviceNameList;
}
////////////////////////////////////////////////////////////
std::string SoundRecorder::getDefaultDevice()
{
return alcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);
}
////////////////////////////////////////////////////////////
bool SoundRecorder::setDevice(const std::string& name)
{
// Store the device name
if (name.empty())
m_deviceName = getDefaultDevice();
else
m_deviceName = name;
if (m_isCapturing)
{
// Stop the capturing thread
m_isCapturing = false;
m_thread.wait();
// Open the requested capture device for capturing 16 bits mono samples
captureDevice = alcCaptureOpenDevice(name.c_str(), m_sampleRate, AL_FORMAT_MONO16, m_sampleRate);
if (!captureDevice)
{
// Notify derived class
onStop();
err() << "Failed to open the audio capture device with the name: " << m_deviceName << std::endl;
return false;
}
// Start the capture
alcCaptureStart(captureDevice);
// Start the capture in a new thread, to avoid blocking the main thread
m_isCapturing = true;
m_thread.launch();
}
return true;
}
////////////////////////////////////////////////////////////
const std::string& SoundRecorder::getDevice() const
{
return m_deviceName;
}
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool SoundRecorder::isAvailable() bool SoundRecorder::isAvailable()
{ {
@ -165,9 +247,6 @@ void SoundRecorder::record()
// Capture is finished : clean up everything // Capture is finished : clean up everything
cleanup(); cleanup();
// Notify derived class
onStop();
} }