From fe2b0f2dc281ebebf2ef8c53b17a9a6e6aadf2b9 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 14 Oct 2019 11:38:29 +1100 Subject: [PATCH] implemented base audio playback and scrubbing --- app/audio/CMakeLists.txt | 4 ++ app/audio/audiohybriddevice.cpp | 93 +++++++++++++++++++++++++++++++++ app/audio/audiohybriddevice.h | 37 +++++++++++++ app/audio/audiomanager.cpp | 50 +++++++++++++----- app/audio/audiomanager.h | 13 ++++- app/widget/viewer/viewer.cpp | 33 +++++++++--- app/widget/viewer/viewer.h | 7 +++ 7 files changed, 215 insertions(+), 22 deletions(-) create mode 100644 app/audio/audiohybriddevice.cpp create mode 100644 app/audio/audiohybriddevice.h diff --git a/app/audio/CMakeLists.txt b/app/audio/CMakeLists.txt index 72ceaba7c..247a727fe 100644 --- a/app/audio/CMakeLists.txt +++ b/app/audio/CMakeLists.txt @@ -16,6 +16,10 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} + audio/audiohybriddevice.h + audio/audiohybriddevice.cpp + audio/audiolayouts.h + audio/audiolayouts.cpp audio/audiomanager.h audio/audiomanager.cpp PARENT_SCOPE diff --git a/app/audio/audiohybriddevice.cpp b/app/audio/audiohybriddevice.cpp new file mode 100644 index 000000000..71bcd8d90 --- /dev/null +++ b/app/audio/audiohybriddevice.cpp @@ -0,0 +1,93 @@ +#include "audiohybriddevice.h" + +#include +#include + +AudioHybridDevice::AudioHybridDevice(QObject *parent) : + QIODevice(parent), + device_(nullptr), + sample_index_(0) +{ + +} + +void AudioHybridDevice::Push(const QByteArray& samples) +{ + pushed_samples_ = samples; + sample_index_ = 0; + + if (!pushed_samples_.isEmpty()) { + emit HasSamples(); + } +} + +void AudioHybridDevice::Stop() +{ + // Whatever is happening, stop it + pushed_samples_.clear(); + device_ = nullptr; +} + +void AudioHybridDevice::ConnectDevice(QIODevice *device) +{ + // Clear any previous device or pushed sample + Stop(); + + device_ = device; + + if (device_ != nullptr) { + emit HasSamples(); + } +} + +bool AudioHybridDevice::IsIdle() +{ + return device_ == nullptr && pushed_samples_.isEmpty(); +} + +qint64 AudioHybridDevice::readData(char *data, qint64 maxSize) +{ + // If a device is connected, passthrough to it + if (device_ != nullptr) { + qint64 read_count = device_->read(data, maxSize); + + // Stop reading this device + if (device_->atEnd()) { + device_ = nullptr; + } + + return read_count; + } + + // If there are samples to push, push those + if (!pushed_samples_.isEmpty()) { + qint64 read_count = qMin(maxSize, pushed_samples_.size() - sample_index_); + + memcpy(data, pushed_samples_.data()+sample_index_, static_cast(read_count)); + + sample_index_ += read_count; + + if (sample_index_ == pushed_samples_.size()) { + pushed_samples_.clear(); + } + + if (read_count < maxSize) { + memset(data + read_count, 0, static_cast(maxSize - read_count)); + } + + return maxSize; + } + + memset(data, 0, static_cast(maxSize)); + return maxSize; +} + +qint64 AudioHybridDevice::writeData(const char *data, qint64 maxSize) +{ + Q_UNUSED(data) + Q_UNUSED(maxSize) + + // This device doesn't support writing + + return -1; +} diff --git a/app/audio/audiohybriddevice.h b/app/audio/audiohybriddevice.h new file mode 100644 index 000000000..20e3979f5 --- /dev/null +++ b/app/audio/audiohybriddevice.h @@ -0,0 +1,37 @@ +#ifndef AUDIOHYBRIDDEVICE_H +#define AUDIOHYBRIDDEVICE_H + +#include + +typedef float sample; + +class AudioHybridDevice : public QIODevice +{ + Q_OBJECT +public: + AudioHybridDevice(QObject* parent = nullptr); + + void Push(const QByteArray &samples); + + void Stop(); + + void ConnectDevice(QIODevice* device); + + bool IsIdle(); + +signals: + void HasSamples(); + +protected: + virtual qint64 readData(char *data, qint64 maxSize) override; + virtual qint64 writeData(const char *data, qint64 maxSize) override; + +private: + QIODevice* device_; + + QByteArray pushed_samples_; + qint64 sample_index_; + +}; + +#endif // AUDIOHYBRIDDEVICE_H diff --git a/app/audio/audiomanager.cpp b/app/audio/audiomanager.cpp index adb5f1dc9..c3755c6d9 100644 --- a/app/audio/audiomanager.cpp +++ b/app/audio/audiomanager.cpp @@ -61,32 +61,29 @@ bool AudioManager::IsRefreshing() return refreshing_devices_; } +void AudioManager::PushToOutput(const QByteArray &samples) +{ + output_manager_.Push(samples); +} + void AudioManager::StartOutput(QIODevice *device) { if (output_ == nullptr) { return; } - output_file_ = device; - - output_->start(output_file_); + output_manager_.ConnectDevice(device); } void AudioManager::StopOutput() { - if (output_ == nullptr || output_file_ == nullptr) { - return; - } - - output_->stop(); - - output_file_->close(); - delete output_file_; - output_file_ = nullptr; + output_manager_.Stop(); } void AudioManager::SetOutputDevice(const QAudioDeviceInfo &info) { + StopOutput(); + QAudioFormat format; format.setSampleRate(48000); format.setChannelCount(2); @@ -96,6 +93,8 @@ void AudioManager::SetOutputDevice(const QAudioDeviceInfo &info) format.setSampleType(QAudioFormat::Float); output_ = std::unique_ptr(new QAudioOutput(info, format, this)); + connect(output_.get(), SIGNAL(stateChanged(QAudio::State)), this, SLOT(OutputStateChanged(QAudio::State))); + connect(output_.get(), SIGNAL(notify()), this, SLOT(OutputNotified())); output_device_info_ = info; } @@ -117,14 +116,17 @@ const QList &AudioManager::ListOutputDevices() AudioManager::AudioManager() : output_(nullptr), - output_file_(nullptr), input_(nullptr), input_file_(nullptr), refreshing_devices_(false) { + connect(&refresh_thread_, SIGNAL(ListsReady()), this, SLOT(RefreshThreadDone())); + RefreshDevices(); - connect(&refresh_thread_, SIGNAL(ListsReady()), this, SLOT(RefreshThreadDone())); + connect(&output_manager_, SIGNAL(HasSamples()), this, SLOT(OutputManagerHasSamples())); + + output_manager_.open(AudioHybridDevice::ReadOnly); } void AudioManager::RefreshThreadDone() @@ -169,6 +171,26 @@ void AudioManager::RefreshThreadDone() emit DeviceListReady(); } +void AudioManager::OutputManagerHasSamples() +{ + if (output_ != nullptr && output_->state() != QAudio::ActiveState) { + output_->start(&output_manager_); + } +} + +void AudioManager::OutputStateChanged(QAudio::State state) +{ + qDebug() << state; +} + +void AudioManager::OutputNotified() +{ + qDebug() << "Notified"; + if (output_manager_.IsIdle()) { + static_cast(sender())->stop(); + } +} + AudioRefreshDevicesThread::AudioRefreshDevicesThread() { } diff --git a/app/audio/audiomanager.h b/app/audio/audiomanager.h index 7cbf6df3b..60f3b1cae 100644 --- a/app/audio/audiomanager.h +++ b/app/audio/audiomanager.h @@ -26,6 +26,8 @@ #include #include +#include "audiohybriddevice.h" + /** * @brief A thread for refreshing the total list of devices on the system * @@ -62,6 +64,8 @@ public: bool IsRefreshing(); + void PushToOutput(const QByteArray& samples); + /** * @brief Start playing audio from QIODevice * @@ -93,9 +97,10 @@ private: static AudioManager* audio_; + AudioHybridDevice output_manager_; + std::unique_ptr output_; QAudioDeviceInfo output_device_info_; - QIODevice* output_file_; std::unique_ptr input_; QAudioDeviceInfo input_device_info_; @@ -108,6 +113,12 @@ private: private slots: void RefreshThreadDone(); + void OutputManagerHasSamples(); + + void OutputStateChanged(QAudio::State state); + + void OutputNotified(); + }; #endif // AUDIOMANAGER_H diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 73650e9d2..97a77b977 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -32,8 +32,15 @@ ViewerWidget::ViewerWidget(QWidget *parent) : QWidget(parent), viewer_node_(nullptr), - playback_speed_(0) + playback_speed_(0), + // FIXME: Test code + test_file_("/home/matt/Desktop/11 - Santa Monica.raw") + // End test code { + // FIXME: Test code + test_file_.open(QFile::ReadOnly); + // End test code + // Set up main layout QVBoxLayout* layout = new QVBoxLayout(this); layout->setMargin(0); @@ -172,6 +179,8 @@ void ViewerWidget::UpdateTimeInternal(int64_t i) if (viewer_node_ != nullptr) { UpdateTextureFromNode(time_set); + + PushScrubbedAudio(); } emit TimeChanged(i); @@ -199,18 +208,28 @@ void ViewerWidget::PlayInternal(int speed) start_timestamp_ = ruler_->GetTime(); playback_speed_ = speed; - QFile* file = new QFile("/home/matt/Desktop/11 - Santa Monica.raw"); - if (file->open(QFile::ReadOnly)) { - file->seek(static_cast(qFloor(GetTime().toDouble() * 48000 * 2)) * static_cast(sizeof(float))); - - AudioManager::instance()->StartOutput(file); - } + // FIXME: Test code + test_file_.seek(static_cast(qFloor(GetTime().toDouble() * 48000 * 2)) * static_cast(sizeof(float))); + AudioManager::instance()->StartOutput(&test_file_); + // End test code playback_timer_.start(); controls_->ShowPauseButton(); } +void ViewerWidget::PushScrubbedAudio() +{ + // FIXME: Test code + if (!IsPlaying()) { + int size_of_sample = qFloor(2 * 48000 * time_base_dbl_) * static_cast(sizeof(float)); + test_file_.seek(static_cast(qFloor(GetTime().toDouble() * 48000 * 2)) * static_cast(sizeof(float))); + QByteArray frame_audio = test_file_.read(size_of_sample); + AudioManager::instance()->PushToOutput(frame_audio); + } + // End test code +} + void ViewerWidget::RulerTimeChange(int64_t i) { Pause(); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 362f973d2..5817a95c9 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -21,6 +21,7 @@ #ifndef VIEWER_WIDGET_H #define VIEWER_WIDGET_H +#include #include #include #include @@ -106,6 +107,8 @@ private: void PlayInternal(int speed); + void PushScrubbedAudio(); + ViewerSizer* sizer_; ViewerGLWidget* gl_widget_; @@ -129,6 +132,10 @@ private: int playback_speed_; + // FIXME: Test code + QFile test_file_; + // End test code + private slots: void RulerTimeChange(int64_t);