Effectuer des captures audio

Effectuer une capture dans un buffer

Le destin le plus probable pour des données audio capturées, est d'être sauvegardées dans un buffer (sf::SoundBuffer) de sorte qu'elles puissent être jouées ou bien sauvegardées dans un fichier.

Ce type de capture peut être effectué via l'interface très simple de la classe sf::SoundBufferRecorder :

// tout d'abord on vérifie si la capture est supportée par le système
if (!sf::SoundBufferRecorder::isAvailable())
{
    // erreur : la capture audio n'est pas supportée
    ...
}

// création de l'enregistreur
sf::SoundBufferRecorder recorder;

// démarrage de l'enregistrement
recorder.start();

// on attend...

// fin de l'enregistrement
recorder.stop();

// récupération du buffer qui contient les données audio enregistrées
const sf::SoundBuffer& buffer = recorder.getBuffer();

La fonction statique SoundBufferRecorder::isAvailable vérifie si la capture audio est supportée par le système. Si elle renvoie false, vous ne pourrez pas utiliser la classe sf::SoundBufferRecorder.

Les fonctions start et stop sont assez explicites. La capture tourne dans son propre thread, ce qui signifie que vous pouvez faire tout ce qui vous chante entre le début et la fin de l'enregistrement. Après la fin de la capture, les données audio sont disponibles dans un buffer que vous pouvez récupérer avec la fonction getBuffer.

Avec les données enregistrées, vous pouvez ensuite :

Si vous comptez utiliser les données capturées après que l'enregistreur a été détruit ou redémarré, n'oubliez pas de faire une copie du buffer.

Sélectionner le périphérique d'entrée

Si vous avez plusieurs périphériques d'entrées audio connectés à votre ordinateur (par exemple un microphone, une carte son externe ou encore une webcam dotée d'un micro), vous pouvez spécifier le périphérique utilisé pour l'enregistrement. Un périphérique d'entrée audio est identifié par son nom. std::vector<std::string> contenant le nom de tous les périphériques connectés est disponible via à la fonction statique SoundBufferRecorder::getAvailableDevices(). Vous pouvez sélectionner un périphérique de la liste pour l'enregistrement en passant le nom du périphérique désiré à la fonction membre setDevice(). Il est même possible de changer de périphérique à la volée (i.e. pendant l'enregistrement).

Le nom du périphérique actuellement utilisé peut être obtenu en appelant getDevice(). Si vous ne choisissez pas de périphérique explicitement vous-même, celui par défaut sera utilisé. Son nom peut être obtenu grace à la fonction statique SoundBufferRecorder::getDefaultDevice().

Voici un petit exemple illustrant comment sélectionner un périphérique d'entrée audio :

// récupération des noms des périphériques d'enregistrement
std::vector<std::string> availableDevices = sf::SoundRecorder::getAvailableDevices();

// choix d'un périphérique
std::string inputDevice = availableDevices[0];

// création d'un enregistreur
sf::SoundBufferRecorder recorder;

// sélection du périphérique
if (!recorder.setDevice(inputDevice))
{
    // erreur : échec de sélection du périphérique
    ...
}

// utilisation habituelle de l'enregistreur

Enregistrement personnalisé

Si stocker les données enregistrées dans un buffer audio n'est pas ce que vous voulez, vous pouvez tout aussi bien écrire votre propre enregistreur. Cela vous permettra de traiter les données audio pendant qu'elles sont en train d'être capturées, (presque) directement dès qu'elles arrivent du périphérique d'enregistrement. De cette façon vous pouvez, par exemple, streamer les données capturées sur le réseau, en effectuer une analyse temps-réel, etc.

Pour écrire votre propre enregistreur, vous devez hériter de la classe abstraite sf::SoundRecorder. En fait, sf::SoundBufferRecorder est juste une spécialisation de cette classe, directement intégrée à SFML.

Vous n'avez qu'une fonction virtuelle à redéfinir dans votre classe dérivée : onProcessSamples. Elle est appelée à chaque fois qu'un nouveau lot d'échantillons audio ont été capturés, c'est donc là que vous devez implémenter votre traitement perso.

Par défaut, les échantillons audio sont fournis à la fonction membre onProcessSamples toutes les 100 ms. Vous pouvez changer l'intervalle en utilisant la fonction membre setProcessingInterval. Vous souhaiterez probablement utiliser un intervalle plus petit si vous voulez manipuler les données enregistrées en temps réel, par exemple. Notez que ceci n'est qu'un indice et qu'il se peut que l'intervalle utilisé en pratique varie, donc ne vous y fiez pas pour implémenter un timing précis.

Il existe deux autres fonctions virtuelles que vous pouvez redéfinir si vous le souhaitez : onStart et onStop. Elles sont appelées respectivement lorsque la capture démarre/est arrêtée. Elles peuvent être utiles pour les tâches d'initialisation et de nettoyage.

Voici le squelette d'une classe dérivée complète :

class MyRecorder : public sf::SoundRecorder
{
    virtual bool onStart() // optionnelle
    {
        // initialisez ce qui doit l'être avant que la capture démarre
        ...

        // renvoyez true pour démarrer la capture, ou false pour l'annuler
        return true;
    }

    virtual bool onProcessSamples(const sf::Int16* samples, std::size_t sampleCount)
    {
        // faites ce que vous voulez des échantillons audio
        ...

        // renvoyez true pour continuer la capture, ou false pour la stopper
        return true;
    }

    virtual void onStop() // optionnelle
    {
        // nettoyez ce qui doit l'être après la fin de la capture
        ...
    }
}

Les fonctions isAvailable/start/stop sont définies dans la classe de base, sf::SoundRecorder, et donc par conséquent dans toutes les classes dérivées. Cela signifie que vous pouvez utiliser n'importe quelle enregistreur exactement de la même manière que la classe sf::SoundBufferRecorder ci-dessus.

if (!MyRecorder::isAvailable())
{
    // erreur...
}

MyRecorder recorder;
recorder.start();
...
recorder.stop();

Attention aux threads

Comme la capture est effectuée dans un thread, il est important de savoir ce qui se passe exactement, et où.

onStart sera appelée directement par la fonction start, donc elle sera exécutée dans le thread qui l'a appelée. Par contre, onProcessSample et onStop seront toujours appelées depuis le thread de capture interne que SFML crée.

Donc, si votre enregistreur utilise des données qui peuvent être accédées de manière concurrente (c'est-à-dire en même temps) à la fois par le thread appelant et par le thread d'enregistrement, il faudra les protéger (avec un mutex ou autre) afin d'éviter les accès concurrents, qui pourraient entraîner des comportements indéterminés -- données audio corrompues, crashs, etc.

Si vous n'êtes pas suffisamment à l'aise avec les problèmes liés aux threads, vous pouvez faire un saut par le tutoriel correspondant.