vastly improved audio

This commit is contained in:
itsmattkc
2018-08-14 20:25:56 +10:00
parent d549586761
commit aa1096d86a
19 changed files with 237 additions and 146 deletions
+3 -4
View File
@@ -21,10 +21,9 @@ PanEffect::PanEffect(Clip* c) : Effect(c, EFFECT_TYPE_AUDIO, AUDIO_PAN_EFFECT) {
void PanEffect::refresh() {}
Effect* PanEffect::copy(Clip* c) {
/*PanEffect* p = new PanEffect(c);
p->pan_val->set_value(pan_val->value());
return p;*/
return NULL;
PanEffect* p = new PanEffect(c);
p->pan_val->set_double_value(pan_val->get_double_value());
return p;
}
void PanEffect::process_audio(quint8 *samples, int nb_bytes) {
+5 -6
View File
@@ -44,12 +44,11 @@ void ShakeEffect::refresh() {
}
Effect* ShakeEffect::copy(Clip* c) {
/*ShakeEffect* e = new ShakeEffect(c);
e->intensity_val->set_value(intensity_val->value());
e->rotation_val->set_value(rotation_val->value());
e->frequency_val->set_value(frequency_val->value());
return e;*/
return NULL;
ShakeEffect* e = new ShakeEffect(c);
e->intensity_val->set_double_value(intensity_val->get_double_value());
e->rotation_val->set_double_value(rotation_val->get_double_value());
e->frequency_val->set_double_value(frequency_val->get_double_value());
return e;
}
void ShakeEffect::process_gl(int*, int*) {
+1 -1
View File
@@ -3,7 +3,7 @@
#include <QString>
#define SAVE_VERSION "180727"
#define SAVE_VERSION "180814"
struct Config {
Config();
+4
View File
@@ -20,6 +20,8 @@
#include "dialogs/exportdialog.h"
#include "dialogs/preferencesdialog.h"
#include "playback/audio.h"
#include "ui_timeline.h"
#include <QDebug>
@@ -149,6 +151,8 @@ MainWindow::~MainWindow() {
config.save(config_dir);
}
stop_audio();
delete ui;
delete panel_project;
+4 -4
View File
@@ -1,6 +1,6 @@
#include "panels.h"
Project* panel_project;
EffectControls* panel_effect_controls;
Viewer* panel_viewer;
Timeline* panel_timeline;
Project* panel_project = 0;
EffectControls* panel_effect_controls = 0;
Viewer* panel_viewer = 0;
Timeline* panel_timeline = 0;
+1 -1
View File
@@ -1110,7 +1110,7 @@ void Project::save_folder(QXmlStreamWriter& stream, QTreeWidgetItem* parent, int
for (int k=0;k<c->effects.size();k++) {
stream.writeStartElement("effect"); // effect
Effect* e = c->effects.at(k);
stream.writeAttribute("id", QString::number(e->id));
stream.writeAttribute("id", QString::number(e->id));
e->save(&stream);
stream.writeEndElement(); // effect
}
+4 -3
View File
@@ -138,16 +138,16 @@ void Timeline::reset_all_audio() {
}
ui->audio_monitor->reset();
clear_audio_ibuffer();
audio_ibuffer_frame = playhead;
}
void Timeline::seek(long p) {
pause();
reset_all_audio();
audio_ibuffer_frame = p;
playhead = p;
reset_all_audio();
repaint_timeline();
}
@@ -170,6 +170,7 @@ void Timeline::play() {
void Timeline::pause() {
playing = false;
panel_viewer->set_playpause_icon(true);
playback_updater.stop();
}
void Timeline::go_to_end() {
+1 -3
View File
@@ -29,9 +29,7 @@ Viewer::Viewer(QWidget *parent) :
update_end_timecode();
}
Viewer::~Viewer()
{
init_audio();
Viewer::~Viewer() {
delete ui;
}
+92 -5
View File
@@ -2,6 +2,10 @@
#include "project/sequence.h"
#include "panels/panels.h"
#include "panels/timeline.h"
#include "ui_timeline.h"
#include <QAudioOutput>
#include <QtMath>
#include <QDebug>
@@ -18,12 +22,10 @@ qint8 audio_ibuffer[audio_ibuffer_size];
int audio_ibuffer_read = 0;
long audio_ibuffer_frame = 0;
AudioSenderThread* audio_thread;
void init_audio() {
if (audio_device_set) {
audio_output->stop();
delete audio_output;
audio_device_set = false;
}
stop_audio();
if (sequence != NULL) {
QAudioFormat audio_format;
@@ -45,11 +47,25 @@ void init_audio() {
audio_io_device = audio_output->start();
audio_device_set = true;
// start sender thread
audio_thread = new AudioSenderThread();
audio_thread->start();
clear_audio_ibuffer();
}
}
}
void stop_audio() {
if (audio_device_set) {
audio_thread->stop();
audio_output->stop();
delete audio_output;
audio_device_set = false;
}
}
void clear_audio_ibuffer() {
memset(audio_ibuffer, 0, audio_ibuffer_size);
audio_ibuffer_read = 0;
@@ -63,3 +79,74 @@ int get_buffer_offset_from_frame(long frame) {
return 0;
}
}
AudioSenderThread::AudioSenderThread() : close(false) {
connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
}
void AudioSenderThread::stop() {
close = true;
cond.wakeAll();
wait();
}
void AudioSenderThread::run() {
// start data loop
send_audio_to_output(0, audio_ibuffer_size);
lock.lock();
while (true) {
msleep(20);
if (close) {
break;
} else if (panel_timeline->playing) {
int written_bytes = 0;
int adjusted_read_index = audio_ibuffer_read%audio_ibuffer_size;
int max_write = audio_ibuffer_size - adjusted_read_index;
int actual_write = send_audio_to_output(adjusted_read_index, max_write);
written_bytes += actual_write;
if (actual_write == max_write) {
// got all the bytes, write again
written_bytes += send_audio_to_output(0, audio_ibuffer_size);
}
}
}
lock.unlock();
}
int AudioSenderThread::send_audio_to_output(int offset, int max) {
// send audio to device
int actual_write = audio_io_device->write((const char*) audio_ibuffer+offset, max);
audio_ibuffer_read += actual_write;
// send samples to audio monitor cache
if (panel_timeline->ui->audio_monitor->sample_cache_offset == -1) {
panel_timeline->ui->audio_monitor->sample_cache_offset = panel_timeline->playhead;
}
int channel_count = av_get_channel_layout_nb_channels(sequence->audio_layout);
long sample_cache_playhead = panel_timeline->ui->audio_monitor->sample_cache_offset + (panel_timeline->ui->audio_monitor->sample_cache.size()/channel_count);
int next_buffer_offset, buffer_offset_adjusted, i;
int buffer_offset = get_buffer_offset_from_frame(sample_cache_playhead);
if (samples.size() != channel_count) samples.resize(channel_count);
samples.fill(0);
// TODO: I don't like this, but i'm not sure if there's a smarter way to do it
while (buffer_offset < audio_ibuffer_read) {
sample_cache_playhead++;
next_buffer_offset = qMin(get_buffer_offset_from_frame(sample_cache_playhead), audio_ibuffer_read);
while (buffer_offset < next_buffer_offset) {
for (i=0;i<samples.size();i++) {
buffer_offset_adjusted = buffer_offset%audio_ibuffer_size;
samples[i] = qMax(qAbs((qint16) (((audio_ibuffer[buffer_offset_adjusted+1] & 0xFF) << 8) | (audio_ibuffer[buffer_offset_adjusted] & 0xFF))), samples[i]);
buffer_offset += 2;
}
}
panel_timeline->ui->audio_monitor->sample_cache.append(samples);
buffer_offset = next_buffer_offset;
}
memset(audio_ibuffer+offset, 0, actual_write);
return actual_write;
}
+19 -1
View File
@@ -2,12 +2,15 @@
#define AUDIO_H
#include <QVector>
#include <QThread>
#include <QWaitCondition>
#include <QMutex>
//#define INT16_MAX 0x7fff
//#define INT16_MIN (-INT16_MAX-1)
class QAudioOutput;
class QIODevice;
class QAudioOutput;
struct Sequence;
@@ -21,6 +24,21 @@ extern long audio_ibuffer_frame;
void clear_audio_ibuffer();
void init_audio();
void stop_audio();
int get_buffer_offset_from_frame(long frame);
class AudioSenderThread : public QThread {
Q_OBJECT
public:
AudioSenderThread();
void run();
void stop();
QWaitCondition cond;
bool close;
QMutex lock;
private:
QVector<qint16> samples;
int send_audio_to_output(int offset, int max);
};
#endif // AUDIO_H
+1 -1
View File
@@ -446,7 +446,7 @@ void Cacher::run() {
open_clip_worker(clip);
while (caching) {
clip->can_cache.wait(&clip->lock);
clip->can_cache.wait(&clip->lock);
if (!caching) {
break;
} else {
+90 -5
View File
@@ -48,9 +48,7 @@ EffectRow* Effect::row(int i) {
return rows.at(i);
}
void Effect::refresh() {
qDebug() << "[WARNING] Tried to init base Effect class - type:" << type << "- id:" << id;
}
void Effect::refresh() {}
void Effect::field_changed() {
panel_viewer->viewer_widget->update();
@@ -65,9 +63,80 @@ bool Effect::is_enabled() {
return container->enabled_check->isChecked();
}
void Effect::load(QXmlStreamReader* stream) {
for (int i=0;i<rows.size();i++) {
EffectRow* row = rows.at(i);
while (!stream->atEnd()) {
stream->readNext();
if (stream->name() == "row" && stream->isStartElement()) {
for (int j=0;j<row->fieldCount();j++) {
EffectField* field = row->field(j);
while (!stream->atEnd()) {
stream->readNext();
if (stream->name() == "field" && stream->isStartElement()) {
stream->readNext();
switch (field->type) {
case EFFECT_FIELD_DOUBLE:
field->set_double_value(stream->text().toDouble());
break;
case EFFECT_FIELD_COLOR:
field->set_color_value(QColor(stream->text().toString()));
break;
case EFFECT_FIELD_STRING:
field->set_string_value(stream->text().toString());
break;
case EFFECT_FIELD_BOOL:
field->set_bool_value(stream->text() == "1");
break;
case EFFECT_FIELD_COMBO:
field->set_combo_string(stream->text().toString());
break;
case EFFECT_FIELD_FONT:
field->set_combo_string(stream->text().toString());
break;
}
break;
}
}
}
break;
}
}
}
}
void Effect::save(QXmlStreamWriter* stream) {
for (int i=0;i<rows.size();i++) {
EffectRow* row = rows.at(i);
stream->writeStartElement("row");
for (int j=0;j<row->fieldCount();j++) {
EffectField* field = row->field(j);
switch (field->type) {
case EFFECT_FIELD_DOUBLE:
stream->writeTextElement("field", QString::number(field->get_double_value()));
break;
case EFFECT_FIELD_COLOR:
stream->writeTextElement("field", field->get_color_value().name());
break;
case EFFECT_FIELD_STRING:
stream->writeTextElement("field", field->get_string_value());
break;
case EFFECT_FIELD_BOOL:
stream->writeTextElement("field", QString::number(field->get_bool_value()));
break;
case EFFECT_FIELD_COMBO:
stream->writeTextElement("field", field->get_combo_string());
break;
case EFFECT_FIELD_FONT:
stream->writeTextElement("field", field->get_font_name());
break;
}
}
stream->writeEndElement(); // row
}
}
Effect* Effect::copy(Clip*) {return NULL;}
void Effect::load(QXmlStreamReader*) {}
void Effect::save(QXmlStreamWriter*) {}
void Effect::process_gl(int*, int*) {}
void Effect::post_gl() {}
void Effect::process_audio(uint8_t*, int) {}
@@ -92,6 +161,14 @@ EffectRow::~EffectRow() {
}
}
EffectField* EffectRow::field(int i) {
return fields.at(i);
}
int EffectRow::fieldCount() {
return fields.size();
}
/* Effect Field Definitions */
EffectField::EffectField(Effect *parent, int t) : type(t) {
@@ -191,6 +268,10 @@ void EffectField::set_combo_index(int index) {
static_cast<QComboBox*>(ui_element)->setCurrentIndex(index);
}
void EffectField::set_combo_string(const QString& s) {
static_cast<QComboBox*>(ui_element)->setCurrentText(s);
}
bool EffectField::get_bool_value() {
return static_cast<QCheckBox*>(ui_element)->isChecked();
}
@@ -214,3 +295,7 @@ const QString EffectField::get_font_name() {
QColor EffectField::get_color_value() {
return static_cast<ColorButton*>(ui_element)->get_color();
}
void EffectField::set_color_value(QColor color) {
static_cast<ColorButton*>(ui_element)->set_color(color);
}
+3
View File
@@ -44,6 +44,7 @@ public:
const QVariant get_combo_data();
const QString get_combo_string();
void set_combo_index(int index);
void set_combo_string(const QString& s);
bool get_bool_value();
void set_bool_value(bool b);
@@ -51,6 +52,7 @@ public:
const QString get_font_name();
QColor get_color_value();
void set_color_value(QColor color);
QWidget* get_ui_element();
void set_enabled(bool e);
@@ -64,6 +66,7 @@ public:
~EffectRow();
EffectField* add_field(int type);
EffectField* field(int i);
int fieldCount();
void set_keyframe(int field, long time);
void move_keyframe(int field, long from, long to);
void delete_keyframe(int field, long time);
+2 -1
View File
@@ -37,9 +37,10 @@ void AudioMonitor::resizeEvent(QResizeEvent *e) {
void AudioMonitor::paintEvent(QPaintEvent *) {
if (sequence != NULL) {
QPainter p(this);
int channel_x = AUDIO_MONITOR_GAP;
int channel_count = av_get_channel_layout_nb_channels(sequence->audio_layout);
int channel_count = av_get_channel_layout_nb_channels(sequence->audio_layout);
if (peaks.size() != channel_count) {
peaks.resize(channel_count);
peaks.fill(false);
+3 -2
View File
@@ -16,23 +16,24 @@ QColor ColorButton::get_color() {
void ColorButton::set_color(QColor c) {
color = c;
set_button_color();
}
void ColorButton::set_button_color() {
QPalette pal = palette();
pal.setColor(QPalette::Button, color);
setPalette(pal);
emit color_changed();
}
void ColorButton::open_dialog() {
QColor old_color = color;
QColor new_color = QColorDialog::getColor(color, NULL);
if (old_color != new_color) {
if (new_color.isValid() && old_color != new_color) {
ColorCommand* command = new ColorCommand(this, old_color, new_color);
undo_stack.push(command);
set_button_color();
emit color_changed();
}
}
+1 -16
View File
@@ -3,20 +3,5 @@
#include <QFontDatabase>
FontCombobox::FontCombobox(QWidget* parent) : ComboBoxEx(parent) {
addItem(QFont().family());
}
void FontCombobox::showPopup() {
QString current = currentText();
clear();
QStringList fonts = QFontDatabase().families();
bool found = false;
for (int i=0;i<fonts.size();i++) {
addItem(fonts.at(i));
if (!found && fonts.at(i) == current) {
setCurrentIndex(i);
found = true;
}
}
QComboBox::showPopup();
addItems(QFontDatabase().families());
}
-2
View File
@@ -6,8 +6,6 @@
class FontCombobox : public ComboBoxEx {
public:
FontCombobox(QWidget* parent = 0);
protected:
void showPopup();
};
#endif // FONTCOMBOBOX_H
+2 -76
View File
@@ -14,6 +14,7 @@
#include <QDebug>
#include <QPainter>
#include <QAudioOutput>
#include <QtMath>
extern "C" {
@@ -31,20 +32,11 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
format.setDepthBufferSize(24);
setFormat(format);
// start audio sending thread
audio_sender_thread.start();
// error handler - retries after 250ms if we couldn't get the entire image
retry_timer.setInterval(250);
connect(&retry_timer, SIGNAL(timeout()), this, SLOT(retry()));
}
ViewerWidget::~ViewerWidget() {
audio_sender_thread.close = true;
audio_sender_thread.cond.wakeAll();
audio_sender_thread.wait();
}
void ViewerWidget::deleteFunction() {
// destroy all textures as well
for (int i=0;i<sequence->clip_count();i++) {
@@ -229,7 +221,7 @@ void ViewerWidget::compose_sequence(QVector<Clip*>& nests, bool render_audio) {
void ViewerWidget::paintGL() {
bool loop = true;
while (loop) {
while (loop) {
loop = false;
texture_failed = false;
@@ -241,11 +233,6 @@ void ViewerWidget::paintGL() {
QVector<Clip*> nests;
compose_sequence(nests, (panel_timeline->playing || force_audio));
// send audio to IO device
if (panel_timeline->playing) {
audio_sender_thread.cond.wakeAll();
}
if (texture_failed) {
if (multithreaded) {
retry_timer.start();
@@ -256,64 +243,3 @@ void ViewerWidget::paintGL() {
}
}
}
AudioSenderThread::AudioSenderThread() : close(false) {
connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
}
void AudioSenderThread::run() {
lock.lock();
while (true) {
cond.wait(&lock);
if (close) {
break;
} else {
int written_bytes = 0;
int adjusted_read_index = audio_ibuffer_read%audio_ibuffer_size;
int max_write = audio_ibuffer_size - adjusted_read_index;
int actual_write = send_audio_to_output(adjusted_read_index, max_write);
written_bytes += actual_write;
if (actual_write == max_write) {
// got all the bytes, write again
written_bytes += send_audio_to_output(0, audio_ibuffer_size);
}
qDebug() << "WB:" << written_bytes;
}
}
lock.unlock();
}
int AudioSenderThread::send_audio_to_output(int offset, int max) {
// send audio to device
int actual_write = audio_io_device->write((const char*) audio_ibuffer+offset, max);
audio_ibuffer_read += actual_write;
// send samples to audio monitor cache
if (panel_timeline->ui->audio_monitor->sample_cache_offset == -1) {
panel_timeline->ui->audio_monitor->sample_cache_offset = panel_timeline->playhead;
}
long sample_cache_playhead = panel_timeline->ui->audio_monitor->sample_cache_offset + panel_timeline->ui->audio_monitor->sample_cache.size();
int next_buffer_offset, buffer_offset_adjusted, i;
int buffer_offset = get_buffer_offset_from_frame(sample_cache_playhead);
samples.resize(av_get_channel_layout_nb_channels(sequence->audio_layout));
samples.fill(0);
while (buffer_offset < audio_ibuffer_read) {
sample_cache_playhead++;
next_buffer_offset = qMin(get_buffer_offset_from_frame(sample_cache_playhead), audio_ibuffer_read);
while (buffer_offset < next_buffer_offset) {
for (i=0;i<samples.size();i++) {
buffer_offset_adjusted = buffer_offset%audio_ibuffer_size;
samples[i] = qMax(qAbs((qint16) (((audio_ibuffer[buffer_offset_adjusted+1] & 0xFF) << 8) | (audio_ibuffer[buffer_offset_adjusted] & 0xFF))), samples[i]);
buffer_offset += 2;
}
}
panel_timeline->ui->audio_monitor->sample_cache.append(samples);
buffer_offset = next_buffer_offset;
}
memset(audio_ibuffer+offset, 0, actual_write);
return actual_write;
}
+1 -15
View File
@@ -13,24 +13,11 @@
struct Clip;
struct Sequence;
class AudioSenderThread : public QThread {
public:
AudioSenderThread();
void run();
QWaitCondition cond;
bool close;
QMutex lock;
private:
QVector<qint16> samples;
int send_audio_to_output(int offset, int max);
};
class ViewerWidget : public QOpenGLWidget, public QOpenGLFunctions
{
Q_OBJECT
public:
ViewerWidget(QWidget *parent = 0);
~ViewerWidget();
ViewerWidget(QWidget *parent = 0);
bool multithreaded;
bool force_audio;
@@ -43,7 +30,6 @@ protected:
// void resizeGL(int w, int h);
private:
QTimer retry_timer;
AudioSenderThread audio_sender_thread;
private slots:
void retry();
void deleteFunction();