-
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;
}
-
You could use a private std::promise (http://en.cppreference.com/w/cpp/thread/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 (http://en.cppreference.com/w/cpp/thread/shared_future)
Another option would be std::condition_variable (http://en.cppreference.com/w/cpp/thread/condition_variable)
-
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;
}
-
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 (https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/7.2.0/threads-posix/seh/) builds.
-
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 (https://github.com/SFML/SFML/blob/master/src/SFML/Audio/SoundRecorder.cpp) 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.