Improvements to Audio Recording

This commit is contained in:
justburner 2021-11-29 09:57:58 +00:00 committed by manongjohn
parent 6f8de8cbf3
commit 34075514d2
2 changed files with 387 additions and 126 deletions

View file

@ -66,29 +66,47 @@ AudioRecordingPopup::AudioRecordingPopup()
m_saveButton = new QPushButton(tr("Save and Insert")); m_saveButton = new QPushButton(tr("Save and Insert"));
m_pauseRecordingButton = new QPushButton(this); m_pauseRecordingButton = new QPushButton(this);
m_pausePlaybackButton = new QPushButton(this); m_pausePlaybackButton = new QPushButton(this);
// m_refreshDevicesButton = new QPushButton(tr("Refresh")); m_refreshDevicesButton = new QPushButton(this);
m_duration = new QLabel("00:00"); m_duration = new QLabel("00:00.000");
m_playDuration = new QLabel("00:00"); m_playDuration = new QLabel("00:00.000");
m_deviceListCB = new QComboBox(); m_deviceListCB = new QComboBox();
m_audioLevelsDisplay = new AudioLevelsDisplay(this); m_audioLevelsDisplay = new AudioLevelsDisplay(this);
m_playXSheetCB = new QCheckBox(tr("Sync with Scene"), this); m_playXSheetCB = new QCheckBox(tr("Sync with XSheet/Timeline"), this);
m_timer = new QElapsedTimer(); m_timer = new QElapsedTimer();
m_recordedLevels = QMap<qint64, double>(); m_recordedLevels = QMap<qint64, double>();
m_oldElapsed = 0; m_oldElapsed = 0;
m_probe = new QAudioProbe;
m_player = new QMediaPlayer(this); m_player = new QMediaPlayer(this);
m_console = FlipConsole::getCurrent(); m_console = FlipConsole::getCurrent();
m_audioRecorder = new QAudioRecorder;
m_recordButton->setMaximumWidth(25); m_labelDevice = new QLabel(tr("Device: "));
m_playButton->setMaximumWidth(25); m_labelSamplerate = new QLabel(tr("Sample rate: "));
m_pauseRecordingButton->setMaximumWidth(25); m_labelSamplefmt = new QLabel(tr("Sample format: "));
m_pausePlaybackButton->setMaximumWidth(25); m_comboSamplerate = new QComboBox();
m_comboSamplefmt = new QComboBox();
m_comboSamplerate->addItem(tr("8000 Hz"), QVariant::fromValue(8000));
m_comboSamplerate->addItem(tr("11025 Hz"), QVariant::fromValue(11025));
m_comboSamplerate->addItem(tr("22050 Hz"), QVariant::fromValue(22050));
m_comboSamplerate->addItem(tr("44100 Hz"), QVariant::fromValue(44100));
m_comboSamplerate->addItem(tr("48000 Hz"), QVariant::fromValue(48000));
m_comboSamplerate->addItem(tr("96000 Hz"), QVariant::fromValue(96000));
m_comboSamplerate->setCurrentIndex(3); // 44.1KHz
m_comboSamplefmt->addItem(tr("Mono 8-Bits"), QVariant::fromValue(9));
m_comboSamplefmt->addItem(tr("Stereo 8-Bits"), QVariant::fromValue(10));
m_comboSamplefmt->addItem(tr("Mono 16-Bits"), QVariant::fromValue(17));
m_comboSamplefmt->addItem(tr("Stereo 16-Bits"), QVariant::fromValue(18));
m_comboSamplefmt->setCurrentIndex(2); // Mono 16-Bits
m_recordButton->setMaximumWidth(32);
m_playButton->setMaximumWidth(32);
m_pauseRecordingButton->setMaximumWidth(32);
m_pausePlaybackButton->setMaximumWidth(32);
m_refreshDevicesButton->setMaximumWidth(25);
QString playDisabled = QString(":Resources/play_disabled.svg"); QString playDisabled = QString(":Resources/play_disabled.svg");
QString pauseDisabled = QString(":Resources/pause_disabled.svg"); QString pauseDisabled = QString(":Resources/pause_disabled.svg");
QString stopDisabled = QString(":Resources/stop_disabled.svg"); QString stopDisabled = QString(":Resources/stop_disabled.svg");
QString recordDisabled = QString(":Resources/record_disabled.svg"); QString recordDisabled = QString(":Resources/record_disabled.svg");
QString refreshDisabled = QString(":Resources/repeat_icon.svg");
m_pauseIcon = createQIcon("pause"); m_pauseIcon = createQIcon("pause");
m_pauseIcon.addFile(pauseDisabled, QSize(), QIcon::Disabled); m_pauseIcon.addFile(pauseDisabled, QSize(), QIcon::Disabled);
@ -98,6 +116,9 @@ AudioRecordingPopup::AudioRecordingPopup()
m_recordIcon.addFile(recordDisabled, QSize(), QIcon::Disabled); m_recordIcon.addFile(recordDisabled, QSize(), QIcon::Disabled);
m_stopIcon = createQIcon("stop"); m_stopIcon = createQIcon("stop");
m_stopIcon.addFile(stopDisabled, QSize(), QIcon::Disabled); m_stopIcon.addFile(stopDisabled, QSize(), QIcon::Disabled);
m_refreshIcon = createQIcon("repeat");
m_refreshIcon.addFile(refreshDisabled, QSize(), QIcon::Disabled);
m_pauseRecordingButton->setIcon(m_pauseIcon); m_pauseRecordingButton->setIcon(m_pauseIcon);
m_pauseRecordingButton->setIconSize(QSize(17, 17)); m_pauseRecordingButton->setIconSize(QSize(17, 17));
m_playButton->setIcon(m_playIcon); m_playButton->setIcon(m_playIcon);
@ -106,12 +127,32 @@ AudioRecordingPopup::AudioRecordingPopup()
m_recordButton->setIconSize(QSize(17, 17)); m_recordButton->setIconSize(QSize(17, 17));
m_pausePlaybackButton->setIcon(m_pauseIcon); m_pausePlaybackButton->setIcon(m_pauseIcon);
m_pausePlaybackButton->setIconSize(QSize(17, 17)); m_pausePlaybackButton->setIconSize(QSize(17, 17));
m_refreshDevicesButton->setIcon(m_refreshIcon);
m_refreshDevicesButton->setIconSize(QSize(17, 17));
QStringList inputs = m_audioRecorder->audioInputs(); // Enumerate devices and initialize default device
m_deviceListCB->addItems(inputs); enumerateAudioDevices("");
QString selectedInput = m_audioRecorder->defaultAudioInput(); QAudioDeviceInfo m_audioDeviceInfo = QAudioDeviceInfo::defaultInputDevice();
m_deviceListCB->setCurrentText(selectedInput); QAudioFormat format;
m_audioRecorder->setAudioInput(selectedInput); format.setSampleRate(44100);
format.setChannelCount(1);
format.setSampleSize(16);
format.setSampleType(QAudioFormat::SignedInt);
format.setByteOrder(QAudioFormat::LittleEndian);
format.setCodec("audio/pcm");
if (!m_audioDeviceInfo.isFormatSupported(format)) {
format = m_audioDeviceInfo.nearestFormat(format);
}
m_audioInput = new QAudioInput(m_audioDeviceInfo, format);
m_audioWriterWAV = new AudioWriterWAV(format);
// Tool tips to provide additional info to the user
m_deviceListCB->setToolTip(tr("Audio input device to record"));
m_comboSamplerate->setToolTip(tr("Number of samples per second, 44.1KHz = CD Quality"));
m_comboSamplefmt->setToolTip(tr("Number of channels and bits per sample, 16-bits recommended"));
m_playXSheetCB->setToolTip(tr("Play animation from current frame while recording/playback"));
m_saveButton->setToolTip(tr("Save recording and insert into new column"));
m_refreshDevicesButton->setToolTip(tr("Refresh list of connected audio input devices"));
m_topLayout->setMargin(5); m_topLayout->setMargin(5);
m_topLayout->setSpacing(8); m_topLayout->setSpacing(8);
@ -124,11 +165,19 @@ AudioRecordingPopup::AudioRecordingPopup()
recordGridLay->setHorizontalSpacing(2); recordGridLay->setHorizontalSpacing(2);
recordGridLay->setVerticalSpacing(3); recordGridLay->setVerticalSpacing(3);
{ {
recordGridLay->addWidget(m_deviceListCB, 0, 0, 1, 4, Qt::AlignCenter); recordGridLay->addWidget(m_labelDevice, 0, 0, 1, 2, Qt::AlignRight);
// recordGridLay->addWidget(m_refreshDevicesButton, 0, 3, Qt::AlignLeft); recordGridLay->addWidget(m_deviceListCB, 0, 2, 1, 2, Qt::AlignLeft);
recordGridLay->addWidget(new QLabel(tr(" ")), 1, 0, Qt::AlignCenter);
recordGridLay->addWidget(m_audioLevelsDisplay, 2, 0, 1, 4, recordGridLay->addWidget(m_labelSamplerate, 1, 0, 1, 2, Qt::AlignRight);
recordGridLay->addWidget(m_comboSamplerate, 1, 2, 1, 1, Qt::AlignLeft);
recordGridLay->addWidget(m_refreshDevicesButton, 1, 3, Qt::AlignRight);
recordGridLay->addWidget(m_labelSamplefmt, 2, 0, 1, 2, Qt::AlignRight);
recordGridLay->addWidget(m_comboSamplefmt, 2, 2, 1, 2, Qt::AlignLeft);
recordGridLay->addWidget(m_audioLevelsDisplay, 3, 0, 1, 4,
Qt::AlignCenter); Qt::AlignCenter);
recordGridLay->addWidget(m_playXSheetCB, 4, 0, 1, 5, Qt::AlignCenter);
QHBoxLayout *recordLay = new QHBoxLayout(); QHBoxLayout *recordLay = new QHBoxLayout();
recordLay->setSpacing(4); recordLay->setSpacing(4);
recordLay->setContentsMargins(0, 0, 0, 0); recordLay->setContentsMargins(0, 0, 0, 0);
@ -139,7 +188,7 @@ AudioRecordingPopup::AudioRecordingPopup()
recordLay->addWidget(m_duration); recordLay->addWidget(m_duration);
recordLay->addStretch(); recordLay->addStretch();
} }
recordGridLay->addLayout(recordLay, 3, 0, 1, 4, Qt::AlignCenter); recordGridLay->addLayout(recordLay, 5, 0, 1, 4, Qt::AlignCenter);
QHBoxLayout *playLay = new QHBoxLayout(); QHBoxLayout *playLay = new QHBoxLayout();
playLay->setSpacing(4); playLay->setSpacing(4);
playLay->setContentsMargins(0, 0, 0, 0); playLay->setContentsMargins(0, 0, 0, 0);
@ -150,11 +199,9 @@ AudioRecordingPopup::AudioRecordingPopup()
playLay->addWidget(m_playDuration); playLay->addWidget(m_playDuration);
playLay->addStretch(); playLay->addStretch();
} }
recordGridLay->addLayout(playLay, 4, 0, 1, 4, Qt::AlignCenter); recordGridLay->addLayout(playLay, 6, 0, 1, 4, Qt::AlignCenter);
recordGridLay->addWidget(new QLabel(tr(" ")), 5, 0, Qt::AlignCenter); recordGridLay->addWidget(new QLabel(tr(" ")), 7, 0, Qt::AlignCenter);
recordGridLay->addWidget(m_saveButton, 6, 0, 1, 4, recordGridLay->addWidget(m_saveButton, 8, 0, 1, 4,
Qt::AlignCenter | Qt::AlignVCenter);
recordGridLay->addWidget(m_playXSheetCB, 7, 0, 1, 4,
Qt::AlignCenter | Qt::AlignVCenter); Qt::AlignCenter | Qt::AlignVCenter);
} }
recordGridLay->setColumnStretch(0, 0); recordGridLay->setColumnStretch(0, 0);
@ -172,23 +219,6 @@ AudioRecordingPopup::AudioRecordingPopup()
m_playXSheetCB->setChecked(true); m_playXSheetCB->setChecked(true);
m_probe->setSource(m_audioRecorder);
QAudioEncoderSettings audioSettings;
audioSettings.setCodec("audio/PCM");
// setting the sample rate to some value (like 44100)
// may cause divide-by-zero crash in QAudioDeviceInfo::nearestFormat()
// so here we set the value to -1, as the documentation says;
// "A value of -1 indicates the encoder should make an optimal choice"
audioSettings.setSampleRate(-1);
audioSettings.setChannelCount(1);
audioSettings.setBitRate(16);
audioSettings.setEncodingMode(QMultimedia::ConstantBitRateEncoding);
audioSettings.setQuality(QMultimedia::HighQuality);
m_audioRecorder->setContainerFormat("wav");
m_audioRecorder->setEncodingSettings(audioSettings);
connect(m_probe, SIGNAL(audioBufferProbed(QAudioBuffer)), this,
SLOT(processBuffer(QAudioBuffer)));
connect(m_playXSheetCB, SIGNAL(stateChanged(int)), this, connect(m_playXSheetCB, SIGNAL(stateChanged(int)), this,
SLOT(onPlayXSheetCBChanged(int))); SLOT(onPlayXSheetCBChanged(int)));
connect(m_saveButton, SIGNAL(clicked()), this, SLOT(onSaveButtonPressed())); connect(m_saveButton, SIGNAL(clicked()), this, SLOT(onSaveButtonPressed()));
@ -199,16 +229,18 @@ AudioRecordingPopup::AudioRecordingPopup()
SLOT(onPauseRecordingButtonPressed())); SLOT(onPauseRecordingButtonPressed()));
connect(m_pausePlaybackButton, SIGNAL(clicked()), this, connect(m_pausePlaybackButton, SIGNAL(clicked()), this,
SLOT(onPausePlaybackButtonPressed())); SLOT(onPausePlaybackButtonPressed()));
connect(m_audioRecorder, SIGNAL(durationChanged(qint64)), this, connect(m_audioWriterWAV, SIGNAL(update(qint64)), this,
SLOT(updateRecordDuration(qint64))); SLOT(updateRecordDuration(qint64)));
connect(m_console, SIGNAL(playStateChanged(bool)), this, if (m_console) connect(m_console, SIGNAL(playStateChanged(bool)), this,
SLOT(onPlayStateChanged(bool))); SLOT(onPlayStateChanged(bool)));
connect(m_deviceListCB, SIGNAL(currentTextChanged(const QString)), this, connect(m_deviceListCB, SIGNAL(currentTextChanged(const QString)), this,
SLOT(onInputDeviceChanged())); SLOT(onInputDeviceChanged()));
// connect(m_refreshDevicesButton, SIGNAL(clicked()), this, connect(m_refreshDevicesButton, SIGNAL(clicked()), this,
// SLOT(onRefreshButtonPressed())); SLOT(onRefreshButtonPressed()));
// connect(m_audioRecorder, SIGNAL(availableAudioInputsChanged()), this, connect(m_comboSamplerate, SIGNAL(currentTextChanged(const QString)), this,
// SLOT(onRefreshButtonPressed())); SLOT(onAudioSettingChanged()));
connect(m_comboSamplefmt, SIGNAL(currentTextChanged(const QString)), this,
SLOT(onAudioSettingChanged()));
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -218,11 +250,24 @@ AudioRecordingPopup::~AudioRecordingPopup() {}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void AudioRecordingPopup::onRecordButtonPressed() { void AudioRecordingPopup::onRecordButtonPressed() {
if (m_audioRecorder->state() == QAudioRecorder::StoppedState) { #if QT_VERSION >= 0x051000
if (m_audioRecorder->status() == QMediaRecorder::UnavailableStatus) { if (m_audioInput->state() == QAudio::InterruptedState) {
DVGui::warning(
tr("The microphone is not available: "
"\nPlease select a different device or check the microphone."));
return;
} else if (m_audioInput->state() == QAudio::StoppedState) {
if (!m_console) {
DVGui::warning(
tr("Record failed: "
"\nMake sure there's XSheet or Timeline in the room."));
#else
if (m_audioInput->state() == QAudio::StoppedState) {
if (!m_console) {
DVGui::warning( DVGui::warning(
tr("The microphone is not available: " tr("The microphone is not available: "
"\nPlease select a different device or check the microphone.")); "\nPlease select a different device or check the microphone."));
#endif
return; return;
} }
// clear the player in case the file is open there // clear the player in case the file is open there
@ -236,8 +281,6 @@ void AudioRecordingPopup::onRecordButtonPressed() {
// (rarely) // (rarely)
// could cause a crash. I think OT tried to import the level before the // could cause a crash. I think OT tried to import the level before the
// final file was fully copied to the new location // final file was fully copied to the new location
m_audioRecorder->setOutputLocation(
QUrl::fromLocalFile(m_filePath.getQString()));
if (TSystem::doesExistFileOrLevel(m_filePath)) { if (TSystem::doesExistFileOrLevel(m_filePath)) {
TSystem::removeFileOrLevel(m_filePath); TSystem::removeFileOrLevel(m_filePath);
} }
@ -246,15 +289,21 @@ void AudioRecordingPopup::onRecordButtonPressed() {
m_playButton->setDisabled(true); m_playButton->setDisabled(true);
m_pausePlaybackButton->setDisabled(true); m_pausePlaybackButton->setDisabled(true);
m_pauseRecordingButton->setEnabled(true); m_pauseRecordingButton->setEnabled(true);
m_deviceListCB->setDisabled(true);
m_refreshDevicesButton->setDisabled(true);
m_comboSamplerate->setDisabled(true);
m_comboSamplefmt->setDisabled(true);
m_recordedLevels.clear(); m_recordedLevels.clear();
m_oldElapsed = 0; m_oldElapsed = 0;
m_pausedTime = 0; m_pausedTime = 0;
m_startPause = 0; m_startPause = 0;
m_endPause = 0; m_endPause = 0;
m_stoppedAtEnd = false; m_stoppedAtEnd = false;
m_playDuration->setText("00:00"); m_playDuration->setText("00:00.000");
m_timer->restart(); m_timer->restart();
m_audioRecorder->record(); m_audioWriterWAV->restart(m_audioInput->format());
m_audioWriterWAV->start();
m_audioInput->start(m_audioWriterWAV);
// this sometimes sets to one frame off, so + 1. // this sometimes sets to one frame off, so + 1.
m_currentFrame = TApp::instance()->getCurrentFrame()->getFrame() + 1; m_currentFrame = TApp::instance()->getCurrentFrame()->getFrame() + 1;
if (m_syncPlayback && !m_isPlaying) { if (m_syncPlayback && !m_isPlaying) {
@ -264,13 +313,20 @@ void AudioRecordingPopup::onRecordButtonPressed() {
} }
} else { } else {
m_audioRecorder->stop(); m_audioInput->stop();
m_audioLevelsDisplay->setLevel(0); m_audioWriterWAV->stop();
if (m_audioWriterWAV->save(m_filePath.getQString())) {
m_saveButton->setEnabled(true);
m_playButton->setEnabled(true);
}
m_audioLevelsDisplay->setLevel(-1);
m_recordButton->setIcon(m_recordIcon); m_recordButton->setIcon(m_recordIcon);
m_saveButton->setEnabled(true);
m_playButton->setEnabled(true);
m_pauseRecordingButton->setDisabled(true); m_pauseRecordingButton->setDisabled(true);
m_pauseRecordingButton->setIcon(m_pauseIcon); m_pauseRecordingButton->setIcon(m_pauseIcon);
m_deviceListCB->setEnabled(true);
m_refreshDevicesButton->setEnabled(true);
m_comboSamplerate->setEnabled(true);
m_comboSamplefmt->setEnabled(true);
if (m_syncPlayback) { if (m_syncPlayback) {
if (m_isPlaying) { if (m_isPlaying) {
m_console->pressButton(FlipConsole::ePause); m_console->pressButton(FlipConsole::ePause);
@ -285,15 +341,18 @@ void AudioRecordingPopup::onRecordButtonPressed() {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void AudioRecordingPopup::updateRecordDuration(qint64 duration) { void AudioRecordingPopup::updateRecordDuration(qint64 duration) {
// this is only called every second or so - sometimes duration ~= 950
// this gives some padding so it doesn't take two seconds to show one second
// has passed
if (duration % 1000 > 850) duration += 150;
int minutes = duration / 60000; int minutes = duration / 60000;
int seconds = (duration / 1000) % 60; int seconds = (duration / 1000) % 60;
int milis = duration % 1000;
QString strMinutes = QString::number(minutes).rightJustified(2, '0'); QString strMinutes = QString::number(minutes).rightJustified(2, '0');
QString strSeconds = QString::number(seconds).rightJustified(2, '0'); QString strSeconds = QString::number(seconds).rightJustified(2, '0');
m_duration->setText(strMinutes + ":" + strSeconds); QString strMilis = QString::number(milis).rightJustified(3, '0');
m_duration->setText(strMinutes + ":" + strSeconds + "." + strMilis);
// Show and record amplitude
qreal level = m_audioWriterWAV->level();
m_audioLevelsDisplay->setLevel(level);
m_recordedLevels[duration / 20] = level;
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -301,9 +360,11 @@ void AudioRecordingPopup::updateRecordDuration(qint64 duration) {
void AudioRecordingPopup::updatePlaybackDuration(qint64 duration) { void AudioRecordingPopup::updatePlaybackDuration(qint64 duration) {
int minutes = duration / 60000; int minutes = duration / 60000;
int seconds = (duration / 1000) % 60; int seconds = (duration / 1000) % 60;
int milis = duration % 1000;
QString strMinutes = QString::number(minutes).rightJustified(2, '0'); QString strMinutes = QString::number(minutes).rightJustified(2, '0');
QString strSeconds = QString::number(seconds).rightJustified(2, '0'); QString strSeconds = QString::number(seconds).rightJustified(2, '0');
m_playDuration->setText(strMinutes + ":" + strSeconds); QString strMilis = QString::number(milis).rightJustified(3, '0');
m_playDuration->setText(strMinutes + ":" + strSeconds + "." + strMilis);
// the qmediaplayer probe doesn't work on all platforms, so we fake it by // the qmediaplayer probe doesn't work on all platforms, so we fake it by
// using // using
@ -328,6 +389,10 @@ void AudioRecordingPopup::onPlayButtonPressed() {
m_recordButton->setDisabled(true); m_recordButton->setDisabled(true);
m_saveButton->setDisabled(true); m_saveButton->setDisabled(true);
m_pausePlaybackButton->setEnabled(true); m_pausePlaybackButton->setEnabled(true);
m_deviceListCB->setDisabled(true);
m_refreshDevicesButton->setDisabled(true);
m_comboSamplerate->setDisabled(true);
m_comboSamplefmt->setDisabled(true);
m_stoppedAtEnd = false; m_stoppedAtEnd = false;
m_player->play(); m_player->play();
// this sometimes sets to one frame off, so + 1. // this sometimes sets to one frame off, so + 1.
@ -343,25 +408,29 @@ void AudioRecordingPopup::onPlayButtonPressed() {
m_playButton->setIcon(m_playIcon); m_playButton->setIcon(m_playIcon);
m_pausePlaybackButton->setDisabled(true); m_pausePlaybackButton->setDisabled(true);
m_pausePlaybackButton->setIcon(m_pauseIcon); m_pausePlaybackButton->setIcon(m_pauseIcon);
m_deviceListCB->setEnabled(true);
m_refreshDevicesButton->setEnabled(true);
m_comboSamplerate->setEnabled(true);
m_comboSamplefmt->setEnabled(true);
} }
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void AudioRecordingPopup::onPauseRecordingButtonPressed() { void AudioRecordingPopup::onPauseRecordingButtonPressed() {
if (m_audioRecorder->state() == QAudioRecorder::StoppedState) { if (m_audioInput->state() == QAudio::StoppedState) {
return; return;
} else if (m_audioRecorder->state() == QAudioRecorder::PausedState) { } else if (m_audioInput->state() == QAudio::SuspendedState) {
m_endPause = m_timer->elapsed(); m_endPause = m_timer->elapsed();
m_pausedTime += m_endPause - m_startPause; m_pausedTime += m_endPause - m_startPause;
m_audioRecorder->record(); m_audioInput->resume();
m_pauseRecordingButton->setIcon(m_pauseIcon); m_pauseRecordingButton->setIcon(m_pauseIcon);
if (m_syncPlayback && !m_isPlaying && !m_stoppedAtEnd) { if (m_syncPlayback && !m_isPlaying && !m_stoppedAtEnd) {
m_console->pressButton(FlipConsole::ePlay); m_console->pressButton(FlipConsole::ePlay);
m_isPlaying = true; m_isPlaying = true;
} }
} else { } else {
m_audioRecorder->pause(); m_audioInput->suspend();
m_pauseRecordingButton->setIcon(m_recordIcon); m_pauseRecordingButton->setIcon(m_recordIcon);
m_startPause = m_timer->elapsed(); m_startPause = m_timer->elapsed();
if (m_syncPlayback && m_isPlaying) { if (m_syncPlayback && m_isPlaying) {
@ -398,7 +467,7 @@ void AudioRecordingPopup::onPausePlaybackButtonPressed() {
void AudioRecordingPopup::onMediaStateChanged(QMediaPlayer::State state) { void AudioRecordingPopup::onMediaStateChanged(QMediaPlayer::State state) {
// stopping can happen through the stop button or the file ending // stopping can happen through the stop button or the file ending
if (state == QMediaPlayer::StoppedState) { if (state == QMediaPlayer::StoppedState) {
m_audioLevelsDisplay->setLevel(0); m_audioLevelsDisplay->setLevel(-1);
if (m_syncPlayback) { if (m_syncPlayback) {
if (m_isPlaying) { if (m_isPlaying) {
m_console->pressButton(FlipConsole::ePause); m_console->pressButton(FlipConsole::ePause);
@ -410,6 +479,10 @@ void AudioRecordingPopup::onMediaStateChanged(QMediaPlayer::State state) {
m_pausePlaybackButton->setIcon(m_pauseIcon); m_pausePlaybackButton->setIcon(m_pauseIcon);
m_pausePlaybackButton->setDisabled(true); m_pausePlaybackButton->setDisabled(true);
m_recordButton->setEnabled(true); m_recordButton->setEnabled(true);
m_deviceListCB->setEnabled(true);
m_refreshDevicesButton->setEnabled(true);
m_comboSamplerate->setEnabled(true);
m_comboSamplefmt->setEnabled(true);
m_saveButton->setEnabled(true); m_saveButton->setEnabled(true);
m_isPlaying = false; m_isPlaying = false;
} }
@ -426,36 +499,36 @@ void AudioRecordingPopup::onPlayXSheetCBChanged(int status) {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Refresh isn't working right now, but I'm leaving the code in case a future void AudioRecordingPopup::onRefreshButtonPressed() {
// change QAudioDeviceInfo m_audioDeviceInfo =
// makes it work m_deviceListCB->itemData(m_deviceListCB->currentIndex())
.value<QAudioDeviceInfo>();
// void AudioRecordingPopup::onRefreshButtonPressed() { enumerateAudioDevices(m_audioDeviceInfo.deviceName());
// m_deviceListCB->clear(); }
// QStringList inputs = m_audioRecorder->audioInputs();
// int count = inputs.count();
// m_deviceListCB->addItems(inputs);
// QString selectedInput = m_audioRecorder->defaultAudioInput();
// m_deviceListCB->setCurrentText(selectedInput);
//
//}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void AudioRecordingPopup::onInputDeviceChanged() { void AudioRecordingPopup::onInputDeviceChanged() {
m_audioRecorder->setAudioInput(m_deviceListCB->currentText()); reinitAudioInput();
}
//-----------------------------------------------------------------------------
void AudioRecordingPopup::onAudioSettingChanged() {
reinitAudioInput();
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void AudioRecordingPopup::onSaveButtonPressed() { void AudioRecordingPopup::onSaveButtonPressed() {
if (m_audioRecorder->state() != QAudioRecorder::StoppedState) { if (m_audioInput->state() != QAudio::StoppedState) {
m_audioRecorder->stop(); m_audioInput->stop();
m_audioLevelsDisplay->setLevel(0); m_audioLevelsDisplay->setLevel(-1);
} }
if (m_player->state() != QMediaPlayer::StoppedState) { if (m_player->state() != QMediaPlayer::StoppedState) {
m_player->stop(); m_player->stop();
m_audioLevelsDisplay->setLevel(0); m_audioLevelsDisplay->setLevel(-1);
} }
if (!TSystem::doesExistFileOrLevel(m_filePath)) return; if (!TSystem::doesExistFileOrLevel(m_filePath)) return;
@ -499,31 +572,6 @@ void AudioRecordingPopup::makePaths() {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void AudioRecordingPopup::processBuffer(const QAudioBuffer &buffer) {
// keep from processing too many times
// get 50 signals per second
if (m_timer->elapsed() < m_oldElapsed + 20) return;
m_oldElapsed = m_timer->elapsed() - m_pausedTime;
qint16 value = 0;
if (!buffer.format().isValid() ||
buffer.format().byteOrder() != QAudioFormat::LittleEndian)
return;
if (buffer.format().codec() != "audio/pcm") return;
const qint16 *data = buffer.constData<qint16>();
qreal maxValue = 0;
qreal tempValue = 0;
for (int i = 0; i < buffer.frameCount(); ++i) {
tempValue = qAbs(qreal(data[i]));
if (tempValue > maxValue) maxValue = tempValue;
}
maxValue /= SHRT_MAX;
m_audioLevelsDisplay->setLevel(maxValue);
m_recordedLevels[m_oldElapsed / 20] = maxValue;
}
void AudioRecordingPopup::onPlayStateChanged(bool playing) { void AudioRecordingPopup::onPlayStateChanged(bool playing) {
// m_isPlaying = playing; // m_isPlaying = playing;
if (!playing && m_isPlaying) m_stoppedAtEnd = true; if (!playing && m_isPlaying) m_stoppedAtEnd = true;
@ -545,22 +593,33 @@ void AudioRecordingPopup::resetEverything() {
m_pauseRecordingButton->setIcon(m_pauseIcon); m_pauseRecordingButton->setIcon(m_pauseIcon);
m_pauseRecordingButton->setDisabled(true); m_pauseRecordingButton->setDisabled(true);
m_pausePlaybackButton->setDisabled(true); m_pausePlaybackButton->setDisabled(true);
m_deviceListCB->setEnabled(true);
m_refreshDevicesButton->setEnabled(true);
m_comboSamplerate->setEnabled(true);
m_comboSamplefmt->setEnabled(true);
m_recordedLevels.clear(); m_recordedLevels.clear();
m_duration->setText("00:00"); m_duration->setText("00:00.000");
m_playDuration->setText("00:00"); m_playDuration->setText("00:00.000");
m_audioLevelsDisplay->setLevel(0); m_audioLevelsDisplay->setLevel(-1);
if (!m_console) {
m_console = FlipConsole::getCurrent();
if (m_console)
connect(m_console, SIGNAL(playStateChanged(bool)), this,
SLOT(onPlayStateChanged(bool)));
}
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void AudioRecordingPopup::hideEvent(QHideEvent *event) { void AudioRecordingPopup::hideEvent(QHideEvent *event) {
if (m_audioRecorder->state() != QAudioRecorder::StoppedState) { if (m_audioInput->state() != QAudio::StoppedState) {
m_audioRecorder->stop(); m_audioInput->stop();
} }
if (m_player->state() != QMediaPlayer::StoppedState) { if (m_player->state() != QMediaPlayer::StoppedState) {
m_player->stop(); m_player->stop();
} }
// make sure the file is freed before deleting // make sure the file is freed before deleting
delete m_player;
m_player = new QMediaPlayer(this); m_player = new QMediaPlayer(this);
// this should only remove files that haven't been used in the scene // this should only remove files that haven't been used in the scene
// make paths checks to only create path names that don't exist yet. // make paths checks to only create path names that don't exist yet.
@ -569,6 +628,172 @@ void AudioRecordingPopup::hideEvent(QHideEvent *event) {
} }
} }
//-----------------------------------------------------------------------------
void AudioRecordingPopup::enumerateAudioDevices(const QString &selectedDeviceName) {
const QAudioDeviceInfo &defaultDeviceInfo =
QAudioDeviceInfo::defaultInputDevice();
m_blockAudioSettings = true;
m_deviceListCB->clear();
m_deviceListCB->addItem(defaultDeviceInfo.deviceName(),
QVariant::fromValue(defaultDeviceInfo));
for (auto &deviceInfo :
QAudioDeviceInfo::availableDevices(QAudio::AudioInput)) {
if (deviceInfo != defaultDeviceInfo &&
m_deviceListCB->findText(deviceInfo.deviceName()) == -1) {
m_deviceListCB->addItem(deviceInfo.deviceName(),
QVariant::fromValue(deviceInfo));
}
}
int deviceIndex = m_deviceListCB->findText(selectedDeviceName);
if (deviceIndex != -1) m_deviceListCB->setCurrentIndex(deviceIndex);
m_blockAudioSettings = false;
}
//-----------------------------------------------------------------------------
void AudioRecordingPopup::reinitAudioInput() {
if (m_blockAudioSettings) return;
QAudioDeviceInfo m_audioDeviceInfo =
m_deviceListCB->itemData(m_deviceListCB->currentIndex())
.value<QAudioDeviceInfo>();
int samplerate =
m_comboSamplerate->itemData(m_comboSamplerate->currentIndex())
.value<int>();
int sampletype =
m_comboSamplefmt->itemData(m_comboSamplefmt->currentIndex()).value<int>();
int bitdepth = sampletype & 56;
int channels = sampletype & 7;
QAudioFormat format;
format.setSampleRate(samplerate);
format.setChannelCount(channels);
format.setSampleSize(bitdepth);
format.setSampleType(bitdepth == 8 ? QAudioFormat::UnSignedInt
: QAudioFormat::SignedInt);
format.setByteOrder(QAudioFormat::LittleEndian);
format.setCodec("audio/pcm");
if (!m_audioDeviceInfo.isFormatSupported(format)) {
DVGui::warning(tr(
"Audio format unsupported:\nNearest format will be internally used."));
format = m_audioDeviceInfo.nearestFormat(format);
}
// Recreate input
delete m_audioInput;
m_audioInput = new QAudioInput(m_audioDeviceInfo, format);
m_audioWriterWAV->restart(format);
}
//-----------------------------------------------------------------------------
// AudioWriterWAV Class
//-----------------------------------------------------------------------------
// IODevice to write standard WAV files, performs peak level calc
//
// 8-bits audio must be unsigned
// 16-bits audio must be signed
// 32-bits isn't supported
AudioWriterWAV::AudioWriterWAV(const QAudioFormat &format)
: m_level(0.0), m_maxAmp(0.0) {
restart(format);
}
bool AudioWriterWAV::restart(const QAudioFormat &format) {
m_format = format;
if (m_format.sampleSize() == 8) {
m_rbytesms = 1000.0 / (m_format.sampleRate() * m_format.channelCount());
m_maxAmp = 127.0;
} else if (m_format.sampleSize() == 16) {
m_rbytesms = 500.0 / (m_format.sampleRate() * m_format.channelCount());
m_maxAmp = 32767.0;
} else {
// 32-bits isn't supported
m_rbytesms = 250.0 / (m_format.sampleRate() * m_format.channelCount());
m_maxAmp = 1.0;
}
m_barray.clear();
return this->reset();
}
void AudioWriterWAV::start() {
open(QIODevice::WriteOnly);
}
void AudioWriterWAV::stop() {
close();
}
qint64 AudioWriterWAV::readData(char *data, qint64 maxlen) {
Q_UNUSED(data)
Q_UNUSED(maxlen)
return 0;
}
qint64 AudioWriterWAV::writeData(const char *data, qint64 len) {
qreal tmp, peak = 0.0;
// Measure peak
if (m_format.sampleSize() == 8) {
const quint8 *sdata = (const quint8 *)data;
int slen = len;
for (int i = 0; i < slen; ++i) {
tmp = qAbs(qreal(sdata[i]) - 128.0);
if (tmp > peak) peak = tmp;
}
} else if (m_format.sampleSize() == 16) {
const qint16 *sdata = (const qint16 *)data;
int slen = len / 2;
for (int i = 0; i < slen; ++i) {
tmp = qAbs(qreal(sdata[i]));
if (tmp > peak) peak = tmp;
}
} else {
// 32-bits isn't supported
peak = -1.0;
}
m_level = peak / m_maxAmp;
// Write to buffer
m_barray.append(data, len);
// Emit an update
emit update(m_barray.size() * m_rbytesms);
return len;
}
bool AudioWriterWAV::save(const QString &filename)
{
QFile file;
quint16 channels = m_format.channelCount();
quint32 samplerate = m_format.sampleRate();
quint16 bitrate = m_format.sampleSize();
file.setFileName(filename);
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
DVGui::warning(tr(
"Failed to save WAV file:\nMake sure you have folder permissions."));
return false;
}
QDataStream out(&file);
out.setByteOrder(QDataStream::LittleEndian);
out.writeRawData("RIFF", 4);
out << (quint32)(m_barray.size() + 44);
out.writeRawData("WAVEfmt ", 8);
out << (quint32)16 << (quint16)1;
out << channels << samplerate;
out << quint32(samplerate * channels * bitrate / 8);
out << quint16(channels * bitrate / 8);
out << bitrate;
out.writeRawData("data", 4);
out << (quint32)m_barray.size();
out.writeRawData(m_barray.constData(), m_barray.size());
return true;
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// AudioLevelsDisplay Class // AudioLevelsDisplay Class
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -591,11 +816,11 @@ void AudioLevelsDisplay::paintEvent(QPaintEvent *event) {
QPainter painter(this); QPainter painter(this);
QColor color; QColor color;
if (m_level < 0.5) { if (m_level < 0.0) {
return; // draw nothing...
} else if (m_level < 0.5) {
color = Qt::green; color = Qt::green;
} } else if (m_level < 0.75) {
else if (m_level < 0.75) {
color = QColor(204, 205, 0); // yellow color = QColor(204, 205, 0); // yellow
} else if (m_level < 0.95) { } else if (m_level < 0.95) {
color = QColor(255, 115, 0); // orange color = QColor(255, 115, 0); // orange

View file

@ -15,7 +15,6 @@
class QComboBox; class QComboBox;
class QCheckBox; class QCheckBox;
class QPushButton; class QPushButton;
class QAudioRecorder;
class QLabel; class QLabel;
class AudioLevelsDisplay; class AudioLevelsDisplay;
class FlipConsole; class FlipConsole;
@ -23,6 +22,7 @@ class QAudioProbe;
class QAudioBuffer; class QAudioBuffer;
class QMediaPlayer; class QMediaPlayer;
class QElapsedTimer; class QElapsedTimer;
class AudioWriterWAV;
//============================================================================= //=============================================================================
// AudioRecordingPopup // AudioRecordingPopup
@ -31,13 +31,13 @@ class QElapsedTimer;
class AudioRecordingPopup : public DVGui::Dialog { class AudioRecordingPopup : public DVGui::Dialog {
Q_OBJECT Q_OBJECT
QString m_deviceName;
QPushButton QPushButton
*m_recordButton, // *m_refreshDevicesButton, -refresh not working for now *m_recordButton, *m_refreshDevicesButton,
*m_playButton, *m_playButton,
*m_pauseRecordingButton, *m_pausePlaybackButton, *m_saveButton; *m_pauseRecordingButton, *m_pausePlaybackButton, *m_saveButton;
QComboBox *m_deviceListCB; QComboBox *m_deviceListCB;
QAudioRecorder *m_audioRecorder; QAudioInput *m_audioInput;
AudioWriterWAV *m_audioWriterWAV;
QLabel *m_duration, *m_playDuration; QLabel *m_duration, *m_playDuration;
QCheckBox *m_playXSheetCB; QCheckBox *m_playXSheetCB;
int m_currentFrame; int m_currentFrame;
@ -56,7 +56,11 @@ class AudioRecordingPopup : public DVGui::Dialog {
QIcon m_pauseIcon; QIcon m_pauseIcon;
QIcon m_recordIcon; QIcon m_recordIcon;
QIcon m_stopIcon; QIcon m_stopIcon;
QIcon m_refreshIcon;
bool m_isPlaying, m_syncPlayback, m_stoppedAtEnd; bool m_isPlaying, m_syncPlayback, m_stoppedAtEnd;
QLabel *m_labelDevice, *m_labelSamplerate, *m_labelSamplefmt;
QComboBox *m_comboSamplerate, *m_comboSamplefmt;
bool m_blockAudioSettings;
public: public:
AudioRecordingPopup(); AudioRecordingPopup();
@ -67,6 +71,8 @@ protected:
void hideEvent(QHideEvent *event); void hideEvent(QHideEvent *event);
void makePaths(); void makePaths();
void resetEverything(); void resetEverything();
void enumerateAudioDevices(const QString &deviceName);
void reinitAudioInput();
private slots: private slots:
void onRecordButtonPressed(); void onRecordButtonPressed();
@ -76,12 +82,42 @@ private slots:
void onSaveButtonPressed(); void onSaveButtonPressed();
void onPauseRecordingButtonPressed(); void onPauseRecordingButtonPressed();
void onPausePlaybackButtonPressed(); void onPausePlaybackButtonPressed();
void processBuffer(const QAudioBuffer &buffer);
void onPlayStateChanged(bool playing); void onPlayStateChanged(bool playing);
void onPlayXSheetCBChanged(int status); void onPlayXSheetCBChanged(int status);
void onMediaStateChanged(QMediaPlayer::State state); void onMediaStateChanged(QMediaPlayer::State state);
void onInputDeviceChanged(); void onInputDeviceChanged();
// void onRefreshButtonPressed(); void onRefreshButtonPressed();
void onAudioSettingChanged();
};
//=============================================================================
// AudioWriterWAV
//-----------------------------------------------------------------------------
class AudioWriterWAV : public QIODevice {
Q_OBJECT
public:
AudioWriterWAV(const QAudioFormat &format);
bool restart(const QAudioFormat &format);
void start();
void stop();
bool save(const QString &filename);
qint64 readData(char *data, qint64 maxlen) override;
qint64 writeData(const char *data, qint64 len) override;
qreal level() const { return m_level; }
private:
QByteArray m_barray;
QAudioFormat m_format;
qreal m_rbytesms;
qreal m_maxAmp;
qreal m_level;
signals:
void update(qint64 duration);
}; };
//============================================================================= //=============================================================================