Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: How to make main thread wait for SoundRecorder to finish  (Read 2900 times)

0 Members and 1 Guest are viewing this topic.

Thrusty

  • Newbie
  • *
  • Posts: 16
    • View Profile
How to make main thread wait for SoundRecorder to finish
« on: January 21, 2018, 12:53:21 pm »
What would be a good way to make the main thread wait for the audio thread to finish?

In the following code I use sf::sleep to wait to make sure that the audio thread has actually finished its work, but I'm not really happy with this solution because it precludes me from instantiating a temporary CRecorder in a function.

#include <atomic>
#include <iostream>
#include <SFML/Audio.hpp>

struct CRecorder : sf::SoundRecorder
{
    CRecorder() { }

    ~CRecorder() { stop(); }

    bool onProcessSamples(const int16_t* samples, const size_t sampleCount)
    {
        std::cout << "CustomRecorder::onProcessSamples() [samples:" << sampleCount << ", done: ";
        std::cout << finished.load(std::memory_order_relaxed) << "]" << std::endl;
 
        if (finished.load(std::memory_order_relaxed) == 1) { return false; }

        // collect samples
        count += sampleCount;

        // return false when 22050 or more have been collected, otherwise return true
        if (count >= 22050)
        {
            // tell the main thread to stop waiting
            finished.store(1, std::memory_order_relaxed);

            // note that onProcessSamples may be called again (sometimes) even though we return false
            return false;
        }

        return true;
    }

    bool onStart()
    {
        std::cout << "CustomRecorder::onStart();" << std::endl;
        finished.store(0, std::memory_order_relaxed);
        setProcessingInterval(sf::milliseconds(25));
        return true;
    }

    void onStop()
    {
        std::cout << "CustomRecorder::onStop();" << std::endl;
    }

    size_t count = 0;
    std::atomic<int32_t> finished;
};

int main()
{
    CRecorder r;
    std::cout << "starting audio capture" << std::endl;
    r.start();

    while (r.finished.load(std::memory_order_relaxed) == 0)
    {
        sf::sleep(sf::milliseconds(25));
    }

    // guard against the "pure virtual method called" exception (might not be totally foolproof)
    //sf::sleep(sf::milliseconds(1000));

    return 0;
}

dabbertorres

  • Hero Member
  • *****
  • Posts: 506
    • View Profile
    • website/blog
Re: How to make main thread wait for SoundRecorder to finish
« Reply #1 on: January 21, 2018, 08:19:21 pm »
You could use a private std::promise that is initialized in onStart(), and calls it's set_value() method in onStop().
Then, provide a function that returns the std::future from the std::promise's get_future() method. Then all your working function (ie: main()) has to do is call the std::future's wait() (or one of the timeout versions) method.

Reusing and modifying your code:
#include <future>
#include <iostream>
#include <SFML/Audio.hpp>
 
struct CRecorder : sf::SoundRecorder
{
    CRecorder() { }
 
    ~CRecorder() { stop(); }
 
    bool onProcessSamples(const int16_t* samples, const size_t sampleCount)
    {
        std::cout << "CustomRecorder::onProcessSamples() [samples:" << sampleCount << ", done: ";
 
        // collect samples
        count += sampleCount;
 
        // return false when 22050 or more have been collected, otherwise return true
        // note that onProcessSamples may be called again (sometimes) even though we return false
        return count < 22050;
    }
 
    bool onStart()
    {
        std::cout << "CustomRecorder::onStart();" << std::endl;
        // reset the promise so it can be reused
        complete = std::promise<void>{};
        setProcessingInterval(sf::milliseconds(25));
        return true;
    }
 
    void onStop()
    {
        std::cout << "CustomRecorder::onStop();" << std::endl;
        // tell any listeners we're all done
        complete.set_value();
    }

    std::future<void> getCompleteSignal()
    {
        return complete.get_future();
    }
 
    size_t count = 0;

private:
    std::promise<void> complete;
};
 
int main()
{
    CRecorder r;
    std::cout << "starting audio capture" << std::endl;
    r.start();

    std::future<void> complete = r.getCompleteSignal();
   
    // do some other work if desired
    // ...
 
    // if a timeout is desired, wait_for() or wait_until() can be used instead
    complete.wait();
    std::cout << "done waiting" << std::endl;

    return 0;
}

Benefit of this method, is the thread calling std::future<T>::wait() can sleep, or busy wait, depending on the std library implementation.

If you'd want to be able to notify multiple threads, see std::shared_future

Another option would be std::condition_variable
« Last Edit: January 21, 2018, 08:22:54 pm by dabbertorres »

Thrusty

  • Newbie
  • *
  • Posts: 16
    • View Profile
Re: How to make main thread wait for SoundRecorder to finish
« Reply #2 on: January 22, 2018, 04:18:45 pm »
My current version of MinGW doesn't support futures or promises, so I am using atomic temporarily. I tried a new way of waiting for the audio thread to finish, and I found that returning false from onProccessSamples() somehow causes onStop() not to be called. Am I missing something? By always returning true from onProccessSamples(), I can manually call stop() from the main thread and have onStop() get called.

#include <atomic>
#include <iostream>
#include <SFML/Audio.hpp>

struct CustomRecorder : sf::SoundRecorder
{
    CustomRecorder() { }

    ~CustomRecorder() { stop(); }

    void waitJoin() const
    {
        while (signalJoin() == false) wait();
    }

    void waitStop() const
    {
        while (signalStop() == false) wait();
    }

private:
    bool onProcessSamples(const int16_t* samples,
                          const size_t sampleCount)
    {
        if (signalStop() == false)
        {
            std::cout << "CustomRecorder::onProcessSamples() [samples:" << sampleCount << "]" << std::endl;
            count += sampleCount;

            if (count >= 22050) atomicStop.store(1, std::memory_order_relaxed);
        }
        else
        {
            std::cout << "CustomRecorder::onProcessSamples() [in flight before stop()]" << std::endl;
        }

        return true;
    }

    bool onStart()
    {
        std::cout << "CustomRecorder::onStart();" << std::endl;
        atomicJoin.store(0, std::memory_order_relaxed);
        atomicStop.store(0, std::memory_order_relaxed);
        return true;
    }

    void onStop()
    {
        std::cout << "CustomRecorder::onStop();" << std::endl;
        atomicJoin.store(1, std::memory_order_relaxed);
    }

    bool signalJoin() const
    {
        return atomicJoin.load(std::memory_order_relaxed);
    }

    bool signalStop() const
    {
        return atomicStop.load(std::memory_order_relaxed);
    }

    void wait() const
    {
        sf::sleep(sf::milliseconds(15));
    }

    size_t               count    = 0;
    std::atomic<int32_t> atomicJoin;
    std::atomic<int32_t> atomicStop;
};

int main()
{
    std::cout << "starting audio capture" << std::endl;
    CustomRecorder r;
    r.start();
    r.waitStop();
    r.stop();
    r.waitJoin();
    return 0;
}

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10820
    • View Profile
    • development blog
    • Email
Re: How to make main thread wait for SoundRecorder to finish
« Reply #3 on: January 22, 2018, 05:17:37 pm »
My current version of MinGW doesn't support futures or promises
You may want to get a MinGW-w64 version then that is including a fully working C++11 and C++14 feature set.
As always, I recommend MinGW Builds builds.
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

dabbertorres

  • Hero Member
  • *****
  • Posts: 506
    • View Profile
    • website/blog
Re: How to make main thread wait for SoundRecorder to finish
« Reply #4 on: January 22, 2018, 09:51:53 pm »
I tried a new way of waiting for the audio thread to finish, and I found that returning false from onProccessSamples() somehow causes onStop() not to be called. Am I missing something? By always returning true from onProccessSamples(), I can manually call stop() from the main thread and have onStop() get called.

I took a quick look at the source and onStop() is only called when stop() is called. Which (to me) seems odd. I would expect onStop() to be called once recording is stopped for any reason, not just explicitly stopped (ie: at the end of sf::SoundRecorder::record() or sf::SoundRecorder::cleanup()).

Maybe we should start a discussion about remedying that.

 

anything