parent
b616af8742
commit
d417619c26
14 changed files with 595 additions and 8 deletions
@ -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…
Reference in new issue