Move to portaudiocxx for recording audio

presentation
Ulrich Huber 7 years ago
parent b616af8742
commit d417619c26
  1. 8
      CMakeLists.txt
  2. 17
      src/control/AudioController.cpp
  3. 2
      src/control/AudioController.h
  4. 7
      src/util/XournalTypeList.h
  5. 62
      src/util/audio/AudioQueue.cpp
  6. 40
      src/util/audio/AudioQueue.h
  7. 52
      src/util/audio/AudioRecorder.cpp
  8. 41
      src/util/audio/AudioRecorder.h
  9. 17
      src/util/audio/DeviceInfo.cpp
  10. 32
      src/util/audio/DeviceInfo.h
  11. 123
      src/util/audio/PortAudioProducer.cpp
  12. 62
      src/util/audio/PortAudioProducer.h
  13. 94
      src/util/audio/SoxConsumer.cpp
  14. 46
      src/util/audio/SoxConsumer.h

@ -88,6 +88,14 @@ add_includes_ldflags ("${ZLIB_LIBRARIES}" "${ZLIB_INCLUDE_DIRS}")
find_package (Threads REQUIRED)
set (xournalpp_LDFLAGS ${xournalpp_LDFLAGS} ${CMAKE_THREAD_LIBS_INIT})
# portaudio
pkg_check_modules(PORTAUDIOCPP portaudiocpp)
add_includes_ldflags ("${PORTAUDIOCPP_LDFLAGS}" "${PORTAUDIOCPP_INCLUDE_DIRS}")
# SOX
pkg_check_modules(SOX sox)
add_includes_ldflags ("${SOX_LDFLAGS}" "${SOX_INCLUDE_DIRS}")
## Additional features ##
# CppUnit

@ -9,12 +9,16 @@ AudioController::AudioController(Settings* settings, Control* control)
XOJ_INIT_TYPE(AudioController);
this->settings = settings;
this->control = control;
this->audioRecorder = new AudioRecorder(settings);
}
AudioController::~AudioController()
{
XOJ_CHECK_TYPE(AudioController);
delete this->audioRecorder;
this->audioRecorder = nullptr;
XOJ_RELEASE_TYPE(AudioController);
}
@ -29,8 +33,6 @@ void AudioController::recStartStop(bool rec)
{
XOJ_CHECK_TYPE(AudioController);
string command;
if (rec)
{
if (getAudioFolder().isEmpty())
@ -42,7 +44,7 @@ void AudioController::recStartStop(bool rec)
sttime = (g_get_monotonic_time() / 1000000);
char buffer[50];
time_t secs = time(0);
time_t secs = time(nullptr);
tm *t = localtime(&secs);
// This prints the date and time in ISO format.
sprintf(buffer, "%04d-%02d-%02d_%02d-%02d-%02d", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour,
@ -53,16 +55,19 @@ void AudioController::recStartStop(bool rec)
audioFilename = data;
g_message("Start recording");
command = "xopp-recording.sh start " + getAudioFolder().str() + "/" + data;
this->audioRecorder->start(getAudioFolder().str() + "/" + data);
}
else if (this->recording)
{
this->recording = false;
audioFilename = "";
sttime = 0;
command = "xopp-recording.sh stop";
g_message("Stop recording");
this->audioRecorder->stop();
}
system(command.c_str());
}
void AudioController::recToggle()

@ -4,6 +4,7 @@
#include "settings/Settings.h"
#include "Control.h"
#include <Path.h>
#include <util/audio/AudioRecorder.h>
class AudioController
{
@ -25,6 +26,7 @@ protected:
gint sttime = 0;
Settings* settings;
Control* control;
AudioRecorder* audioRecorder;
private:
XOJ_TYPE_ATTRIB;

@ -268,5 +268,8 @@ XOJ_DECLARE_TYPE(ToolBase, 258);
XOJ_DECLARE_TYPE(ScrollHandling, 259);
XOJ_DECLARE_TYPE(ScrollHandlingGtk, 260);
XOJ_DECLARE_TYPE(ScrollHandlingXournalpp, 261);
XOJ_DECLARE_TYPE(PortAudioProducer, 262);
XOJ_DECLARE_TYPE(SoxConsumer, 263);
XOJ_DECLARE_TYPE(AudioRecorder, 264);
XOJ_DECLARE_TYPE(AudioQueue, 265);
XOJ_DECLARE_TYPE(DeviceInfo, 266);

@ -0,0 +1,62 @@
#include "AudioQueue.h"
AudioQueue::AudioQueue()
{
XOJ_INIT_TYPE(AudioQueue);
}
AudioQueue::~AudioQueue()
{
XOJ_CHECK_TYPE(AudioQueue);
XOJ_RELEASE_TYPE(AudioQueue);
}
void AudioQueue::reset()
{
XOJ_CHECK_TYPE(AudioQueue);
this->notified = false;
this->streamEnd = false;
this->clear();
}
bool AudioQueue::empty()
{
XOJ_CHECK_TYPE(AudioQueue);
return deque<int>::empty();
}
unsigned long AudioQueue::size()
{
XOJ_CHECK_TYPE(AudioQueue);
return deque<int>::size();
}
void AudioQueue::push(int *samples, unsigned long nSamples)
{
XOJ_CHECK_TYPE(AudioQueue);
unsigned long requiredSize = this->size() + nSamples;
if (this->max_size() < requiredSize)
this->resize(requiredSize);
for (long i = nSamples - 1; i >= 0; i--)
this->push_front(samples[i]);
}
std::vector<int> AudioQueue::pop(unsigned long nSamples)
{
XOJ_CHECK_TYPE(AudioQueue);
std::vector<int> buffer(nSamples);
for (long i = nSamples - 1; i >= 0; i--)
{
buffer[i] = this->back();
this->pop_back();
}
return buffer;
}

@ -0,0 +1,40 @@
/*
* Xournal++
*
* Queue to connect an audio producer and an audio consumer
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <XournalType.h>
#include <vector>
#include <deque>
#include <mutex>
#include <condition_variable>
class AudioQueue : protected std::deque<int>
{
public:
AudioQueue();
~AudioQueue();
void reset();
bool empty();
unsigned long size();
void push(int *samples, unsigned long nSamples);
std::vector<int> pop(unsigned long nSamples);
std::mutex queueLock;
std::condition_variable lockCondition;
bool streamEnd = false;
bool notified = false;
private:
XOJ_TYPE_ATTRIB;
};

@ -0,0 +1,52 @@
#include "AudioRecorder.h"
AudioRecorder::AudioRecorder(Settings *settings) : settings(settings)
{
XOJ_INIT_TYPE(AudioRecorder);
this->audioQueue = new AudioQueue();
this->portAudioProducer = new PortAudioProducer(settings, this->audioQueue);
this->soxConsumer = new SoxConsumer(this->audioQueue);
}
AudioRecorder::~AudioRecorder()
{
XOJ_CHECK_TYPE(AudioRecorder);
delete this->portAudioProducer;
this->portAudioProducer = nullptr;
delete this->soxConsumer;
this->soxConsumer = nullptr;
delete this->audioQueue;
this->audioQueue = nullptr;
XOJ_RELEASE_TYPE(AudioRecorder);
}
void AudioRecorder::start(std::string filename)
{
XOJ_CHECK_TYPE(AudioRecorder);
// Start the consumer for writing the data
// TODO get sample rate from settings
this->soxConsumer->start(filename, 44100.0, this->portAudioProducer->getSelectedInputDevice());
// Start recording
this->portAudioProducer->startRecording();
}
void AudioRecorder::stop()
{
XOJ_CHECK_TYPE(AudioRecorder);
// Stop recording audio
this->portAudioProducer->stopRecording();
// Wait for libsox to write all the data
this->soxConsumer->join();
// Reset the queue for the next recording
this->audioQueue->reset();
}

@ -0,0 +1,41 @@
/*
* Xournal++
*
* Class to record audio and store it as MP3-file
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <XournalType.h>
#include "AudioQueue.h"
#include "PortAudioProducer.h"
#include "SoxConsumer.h"
#include <control/settings/Settings.h>
class AudioRecorder
{
public:
explicit AudioRecorder(Settings *settings);
~AudioRecorder();
void start(std::string filename);
void stop();
protected:
Settings* settings;
AudioQueue *audioQueue;
PortAudioProducer *portAudioProducer;
SoxConsumer *soxConsumer;
private:
XOJ_TYPE_ATTRIB;
};

@ -0,0 +1,17 @@
#include "DeviceInfo.h"
DeviceInfo::DeviceInfo(portaudio::Device *device, bool selected) : deviceName(device->name()),
index(device->index()),
selected(selected),
inputChannels((device->isFullDuplexDevice() || device->isInputOnlyDevice()) ? device->maxInputChannels() : 0),
outputChannels((device->isFullDuplexDevice() || device->isOutputOnlyDevice()) ? device->maxOutputChannels() : 0)
{
XOJ_INIT_TYPE(DeviceInfo);
}
DeviceInfo::~DeviceInfo()
{
XOJ_CHECK_TYPE(DeviceInfo);
XOJ_RELEASE_TYPE(DeviceInfo);
}

@ -0,0 +1,32 @@
/*
* Xournal++
*
* Class storing information about an audio device
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/#pragma once
#include <XournalType.h>
#include <portaudiocpp/PortAudioCpp.hxx>
#include <string>
class DeviceInfo
{
public:
DeviceInfo(portaudio::Device *device, bool selected);
~DeviceInfo();
const std::string deviceName;
const PaDeviceIndex index;
const bool selected;
const int inputChannels;
const int outputChannels;
private:
XOJ_TYPE_ATTRIB;
};

@ -0,0 +1,123 @@
#include "PortAudioProducer.h"
PortAudioProducer::PortAudioProducer(Settings *settings, AudioQueue *audioQueue) : sys(portaudio::System::instance()), settings(settings), audioQueue(audioQueue)
{
XOJ_INIT_TYPE(PortAudioProducer);
DeviceInfo inputInfo(&sys.defaultInputDevice(), true);
this->setInputDevice(inputInfo);
}
PortAudioProducer::~PortAudioProducer()
{
XOJ_CHECK_TYPE(PortAudioProducer);
portaudio::System::terminate();
XOJ_RELEASE_TYPE(PortAudioProducer);
}
std::list<DeviceInfo> PortAudioProducer::getInputDevices()
{
XOJ_CHECK_TYPE(PortAudioProducer);
std::list<DeviceInfo> deviceList;
for (portaudio::System::DeviceIterator i = this->sys.devicesBegin(); i != sys.devicesEnd(); ++i)
{
if (i->isFullDuplexDevice() || i->isInputOnlyDevice())
{
DeviceInfo deviceInfo(&(*i), this->selectedInputDevice == i->index());
deviceList.push_back(deviceInfo);
}
}
return deviceList;
}
const DeviceInfo PortAudioProducer::getSelectedInputDevice()
{
return DeviceInfo(&sys.deviceByIndex(this->selectedInputDevice), true);
}
void PortAudioProducer::setInputDevice(DeviceInfo deviceInfo)
{
XOJ_CHECK_TYPE(PortAudioProducer);
this->selectedInputDevice = deviceInfo.index;
portaudio::Device *device = &sys.deviceByIndex(this->selectedInputDevice);
this->inputChannels = static_cast<unsigned int>(device->maxInputChannels());
}
bool PortAudioProducer::isRecording()
{
XOJ_CHECK_TYPE(PortAudioProducer);
return this->inputStream != nullptr && this->inputStream->isActive();
}
void PortAudioProducer::startRecording()
{
XOJ_CHECK_TYPE(PortAudioProducer);
// Check if there already is a recording
if (this->inputStream != nullptr)
return;
// Get the device information of our input device
portaudio::Device *device = &sys.deviceByIndex(this->selectedInputDevice);
portaudio::DirectionSpecificStreamParameters inParams(*device, device->maxInputChannels(), portaudio::INT32, true, device->defaultLowInputLatency(), nullptr);
portaudio::StreamParameters params(inParams, portaudio::DirectionSpecificStreamParameters::null(), this->sampleRate, this->framesPerBuffer, paNoFlag);
// Specify the callback used for buffering the recorded data
this->inputStream = new portaudio::MemFunCallbackStream<PortAudioProducer>(params, *this, &PortAudioProducer::recordCallback);
// Start the recording
this->inputStream->start();
}
int PortAudioProducer::recordCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags)
{
XOJ_CHECK_TYPE(PortAudioProducer);
if (statusFlags)
{
g_message(("PortAudioProducer: statusFlag: " + std::to_string(statusFlags)).c_str());
}
if (inputBuffer != nullptr)
{
std::unique_lock<std::mutex> lock(this->audioQueue->queueLock);
unsigned long providedFrames = framesPerBuffer * this->inputChannels;
this->audioQueue->push(((int *) inputBuffer), providedFrames);
this->audioQueue->notified = true;
this->audioQueue->lockCondition.notify_one();
}
return paContinue;
}
void PortAudioProducer::stopRecording()
{
XOJ_CHECK_TYPE(PortAudioProducer);
// Stop the recording
if (this->inputStream != nullptr)
{
this->inputStream->stop();
this->inputStream->close();
}
// Notify the consumer at the other side that ther will be no more data
this->audioQueue->streamEnd = true;
this->audioQueue->notified = true;
this->audioQueue->lockCondition.notify_one();
// Allow new recording by removing the old one
delete this->inputStream;
this->inputStream = nullptr;
}

@ -0,0 +1,62 @@
/*
* Xournal++
*
* Class to record audio using libportaudio
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <XournalType.h>
#include "DeviceInfo.h"
#include "AudioQueue.h"
#include <control/settings/Settings.h>
#include <list>
#include <portaudiocpp/PortAudioCpp.hxx>
class PortAudioProducer
{
public:
explicit PortAudioProducer(Settings *settings, AudioQueue *audioQueue);
~PortAudioProducer();
std::list<DeviceInfo> getInputDevices();
const DeviceInfo getSelectedInputDevice();
void setInputDevice(DeviceInfo deviceInfo);
bool isRecording();
void startRecording();
int recordCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo * timeInfo, PaStreamCallbackFlags statusFlags);
void stopRecording();
protected:
double sampleRate = 44100.0;
const unsigned long framesPerBuffer = 64;
portaudio::AutoSystem autoSys;
portaudio::System &sys;
Settings *settings;
AudioQueue *audioQueue;
PaDeviceIndex selectedInputDevice;
unsigned int inputChannels;
portaudio::MemFunCallbackStream<PortAudioProducer> *inputStream = nullptr;
private:
XOJ_TYPE_ATTRIB;
};

@ -0,0 +1,94 @@
#include "SoxConsumer.h"
SoxConsumer::SoxConsumer(AudioQueue *audioQueue) : audioQueue(audioQueue)
{
XOJ_INIT_TYPE(SoxConsumer);
sox_init();
sox_format_init();
}
SoxConsumer::~SoxConsumer()
{
XOJ_CHECK_TYPE(SoxConsumer);
sox_format_quit();
sox_quit();
XOJ_RELEASE_TYPE(SoxConsumer);
}
void SoxConsumer::start(std::string filename, double sampleRate, const DeviceInfo &inputDevice)
{
XOJ_CHECK_TYPE(SoxConsumer);
this->inputSignal = new sox_signalinfo_t;
this->inputSignal->rate = sampleRate;
this->inputSignal->length = SOX_UNSPEC;
this->inputSignal->channels = (unsigned int) inputDevice.inputChannels;
this->inputSignal->mult = nullptr;
this->inputSignal->precision = 32;
this->outputFile = sox_open_write(filename.c_str(), this->inputSignal, nullptr, nullptr, nullptr, nullptr);
if (this->outputFile == nullptr)
{
g_message("SoxConsumer: output file could not be opened");
return;
}
this->consumerThread = new std::thread([&]
{
std::unique_lock<std::mutex> lock(audioQueue->queueLock);
unsigned long availableFrames;
while (!(this->stopConsumer || (audioQueue->streamEnd && audioQueue->empty())))
{
while (!audioQueue->notified)
{
audioQueue->lockCondition.wait(lock);
}
while (!audioQueue->empty())
{
unsigned long queueSize = audioQueue->size();
availableFrames = std::min(queueSize - queueSize % this->inputSignal->channels, (unsigned long) (64 * this->inputSignal->channels));
if (availableFrames > 0)
{
std::vector<int> tmpBuffer = audioQueue->pop(availableFrames);
sox_write(this->outputFile, tmpBuffer.data(), availableFrames);
}
}
audioQueue->notified = false;
}
sox_close(this->outputFile);
});
}
void SoxConsumer::join()
{
XOJ_CHECK_TYPE(SoxConsumer);
// Join the consumer thread to wait for completion
if (this->consumerThread->joinable())
this->consumerThread->join();
}
void SoxConsumer::stop()
{
XOJ_CHECK_TYPE(SoxConsumer);
// Stop consumer
this->stopConsumer = true;
this->audioQueue->notified = true;
this->audioQueue->lockCondition.notify_one();
// Wait for consumer to finish
if (this->consumerThread->joinable())
this->consumerThread->join();
}

@ -0,0 +1,46 @@
/*
* Xournal++
*
* Class to save audio data in an mp3 file
*
* @author Xournal++ Team
* https://github.com/xournalpp/xournalpp
*
* @license GNU GPLv2 or later
*/
#pragma once
#include <XournalType.h>
#include "AudioQueue.h"
#include "DeviceInfo.h"
#include <sox.h>
#include <thread>
#include <utility>
class SoxConsumer
{
public:
explicit SoxConsumer(AudioQueue *audioQueue);
~SoxConsumer();
void start(std::string filename, double sampleRate, const DeviceInfo &inputDevice);
void join();
void stop();
protected:protected:
sox_signalinfo_t *inputSignal = nullptr;
sox_format_t *outputFile = nullptr;
bool stopConsumer = false;
AudioQueue *audioQueue;
std::thread *consumerThread;
private:
XOJ_TYPE_ATTRIB;
};
Loading…
Cancel
Save