Revision control
Copy as Markdown
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "audiorecorder.h"
#include "ui_audiorecorder.h"
#include <QAudioBuffer>
#include <QAudioDevice>
#include <QAudioInput>
#include <QDir>
#include <QFileDialog>
#include <QImageCapture>
#include <QMediaDevices>
#include <QMediaFormat>
#include <QMediaRecorder>
#include <QMimeType>
#include <QStandardPaths>
#if QT_CONFIG(permissions)
#include <QPermission>
#endif
AudioRecorder::AudioRecorder() : ui(new Ui::AudioRecorder)
{
ui->setupUi(this);
// channels
ui->channelsBox->addItem(tr("Default"), QVariant(-1));
ui->channelsBox->addItem(QStringLiteral("1"), QVariant(1));
ui->channelsBox->addItem(QStringLiteral("2"), QVariant(2));
ui->channelsBox->addItem(QStringLiteral("4"), QVariant(4));
// quality
ui->qualitySlider->setRange(0, int(QImageCapture::VeryHighQuality));
ui->qualitySlider->setValue(int(QImageCapture::NormalQuality));
// bit rates:
ui->bitrateBox->addItem(tr("Default"), QVariant(0));
ui->bitrateBox->addItem(QStringLiteral("32000"), QVariant(32000));
ui->bitrateBox->addItem(QStringLiteral("64000"), QVariant(64000));
ui->bitrateBox->addItem(QStringLiteral("96000"), QVariant(96000));
ui->bitrateBox->addItem(QStringLiteral("128000"), QVariant(128000));
// audio input initialization
init();
}
void AudioRecorder::init()
{
#if QT_CONFIG(permissions)
QMicrophonePermission microphonePermission;
switch (qApp->checkPermission(microphonePermission)) {
case Qt::PermissionStatus::Undetermined:
qApp->requestPermission(microphonePermission, this, &AudioRecorder::init);
return;
case Qt::PermissionStatus::Denied:
qWarning("Microphone permission is not granted!");
return;
case Qt::PermissionStatus::Granted:
break;
}
#endif
m_audioRecorder = new QMediaRecorder(this);
m_captureSession.setRecorder(m_audioRecorder);
m_captureSession.setAudioInput(new QAudioInput(this));
// audio devices
m_mediaDevices = new QMediaDevices(this);
connect(m_mediaDevices, &QMediaDevices::audioInputsChanged, this,
&AudioRecorder::updateDevices);
updateDevices();
// audio codecs and container formats
updateFormats();
connect(ui->audioCodecBox, &QComboBox::currentIndexChanged, this,
&AudioRecorder::updateFormats);
connect(ui->containerBox, &QComboBox::currentIndexChanged, this, &AudioRecorder::updateFormats);
// sample rate
constexpr auto allSamplingRates = std::array{
8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100,
48000, 64000, 88200, 96000, 128000, 176400, 192000,
};
QAudioDevice device = m_captureSession.audioInput()->device();
int minSamplingRate = device.minimumSampleRate();
int maxSamplingRate = device.maximumSampleRate();
for (int rate : allSamplingRates) {
if (rate < minSamplingRate || rate > maxSamplingRate)
continue;
ui->sampleRateBox->addItem(QString::number(rate), rate);
}
int preferredRate = device.preferredFormat().sampleRate();
if (preferredRate > 0) {
int index = ui->sampleRateBox->findData(device.preferredFormat().sampleRate());
ui->sampleRateBox->setCurrentIndex(index);
}
connect(m_audioRecorder, &QMediaRecorder::durationChanged, this,
&AudioRecorder::updateProgress);
connect(m_audioRecorder, &QMediaRecorder::recorderStateChanged, this,
&AudioRecorder::onStateChanged);
connect(m_audioRecorder, &QMediaRecorder::errorChanged, this,
&AudioRecorder::displayErrorMessage);
connect(m_audioRecorder, &QMediaRecorder::mediaFormatChanged, this,
&AudioRecorder::onMediaFormatChanged);
}
void AudioRecorder::updateProgress(qint64 duration)
{
if (m_audioRecorder->error() != QMediaRecorder::NoError || duration < 2000)
return;
ui->statusbar->showMessage(tr("Recorded %1 sec").arg(duration / 1000));
}
void AudioRecorder::onStateChanged(QMediaRecorder::RecorderState state)
{
QString statusMessage;
switch (state) {
case QMediaRecorder::RecordingState:
statusMessage = tr("Recording to %1").arg(m_audioRecorder->actualLocation().toString());
ui->recordButton->setText(tr("Stop"));
ui->pauseButton->setText(tr("Pause"));
break;
case QMediaRecorder::PausedState:
statusMessage = tr("Paused");
ui->recordButton->setText(tr("Stop"));
ui->pauseButton->setText(tr("Resume"));
break;
case QMediaRecorder::StoppedState:
statusMessage = tr("Stopped");
ui->recordButton->setText(tr("Record"));
ui->pauseButton->setText(tr("Pause"));
break;
}
ui->pauseButton->setEnabled(m_audioRecorder->recorderState() != QMediaRecorder::StoppedState);
if (m_audioRecorder->error() == QMediaRecorder::NoError)
ui->statusbar->showMessage(statusMessage);
}
static QVariant boxValue(const QComboBox *box)
{
int idx = box->currentIndex();
if (idx == -1)
return QVariant();
return box->itemData(idx);
}
void AudioRecorder::toggleRecord()
{
if (!m_audioRecorder) {
ui->statusbar->showMessage(QStringLiteral("AudioRecorder not set. "
"Microphone permission was not granted!"));
return;
}
if (m_audioRecorder->recorderState() == QMediaRecorder::StoppedState) {
m_captureSession.audioInput()->setDevice(
boxValue(ui->audioDeviceBox).value<QAudioDevice>());
m_audioRecorder->setMediaFormat(selectedMediaFormat());
m_audioRecorder->setAudioSampleRate(boxValue(ui->sampleRateBox).toInt());
m_audioRecorder->setAudioBitRate(boxValue(ui->bitrateBox).toInt());
m_audioRecorder->setAudioChannelCount(boxValue(ui->channelsBox).toInt());
m_audioRecorder->setQuality(QMediaRecorder::Quality(ui->qualitySlider->value()));
m_audioRecorder->setEncodingMode(ui->constantQualityRadioButton->isChecked()
? QMediaRecorder::ConstantQualityEncoding
: QMediaRecorder::ConstantBitRateEncoding);
m_audioRecorder->record();
} else {
m_audioRecorder->stop();
}
}
void AudioRecorder::togglePause()
{
if (m_audioRecorder->recorderState() != QMediaRecorder::PausedState)
m_audioRecorder->pause();
else
m_audioRecorder->record();
}
void AudioRecorder::setOutputLocation()
{
if (!m_audioRecorder) {
ui->statusbar->showMessage(QStringLiteral("AudioRecorder not set. "
"Microphone permission was not granted!"));
return;
}
#ifdef Q_OS_ANDROID
QMediaFormat format = selectedMediaFormat();
if (format.fileFormat() == QMediaFormat::UnspecifiedFormat)
format.resolveForEncoding(QMediaFormat::NoFlags);
QString fileName = QFileDialog::getSaveFileName(
this, tr("Save Recording"),
"output." + format.mimeType().preferredSuffix());
#else
QString fileName = QFileDialog::getSaveFileName();
#endif
m_audioRecorder->setOutputLocation(QUrl::fromLocalFile(fileName));
m_outputLocationSet = true;
}
void AudioRecorder::displayErrorMessage()
{
ui->statusbar->showMessage(m_audioRecorder->errorString());
}
void AudioRecorder::onMediaFormatChanged()
{
QMediaFormat format = m_audioRecorder->mediaFormat();
int formatIndex = ui->containerBox->findData(format.fileFormat());
if (formatIndex != -1)
ui->containerBox->setCurrentIndex(formatIndex);
int codecIndex = ui->audioCodecBox->findData(QVariant::fromValue(format.audioCodec()));
if (codecIndex != -1)
ui->audioCodecBox->setCurrentIndex(codecIndex);
}
void AudioRecorder::updateDevices()
{
const auto currentDevice = boxValue(ui->audioDeviceBox).value<QAudioDevice>();
int currentDeviceIndex = 0;
ui->audioDeviceBox->clear();
ui->audioDeviceBox->addItem(tr("Default"), {});
const QList<QAudioDevice> audioInputs = m_mediaDevices->audioInputs();
for (const auto &device : audioInputs) {
const auto name = device.description();
ui->audioDeviceBox->addItem(name, QVariant::fromValue(device));
if (device.id() == currentDevice.id())
currentDeviceIndex = ui->audioDeviceBox->count() - 1;
}
ui->audioDeviceBox->setCurrentIndex(currentDeviceIndex);
}
void AudioRecorder::updateFormats()
{
if (m_updatingFormats)
return;
m_updatingFormats = true;
QMediaFormat format;
if (ui->containerBox->count())
format.setFileFormat(boxValue(ui->containerBox).value<QMediaFormat::FileFormat>());
// Add all supported formats, remembering the previous choice
int currentIndex = 0;
ui->containerBox->clear();
ui->containerBox->addItem(tr("Default file format"),
QVariant::fromValue(QMediaFormat::UnspecifiedFormat));
const QList<QMediaFormat::FileFormat> supportedFormats = format.supportedFileFormats(QMediaFormat::Encode);
for (auto container : supportedFormats) {
if (container < QMediaFormat::Mpeg4Audio) // Skip video formats
continue;
if (container == format.fileFormat())
currentIndex = ui->containerBox->count();
ui->containerBox->addItem(QMediaFormat::fileFormatDescription(container),
QVariant::fromValue(container));
}
ui->containerBox->setCurrentIndex(currentIndex);
// Re-populate codecs, adding only those that the chosen file format supports.
// Remember current codec if it is supported by the file format.
QMediaFormat::AudioCodec currentCodec = QMediaFormat::AudioCodec::Unspecified;
if (ui->audioCodecBox->count())
currentCodec = boxValue(ui->audioCodecBox).value<QMediaFormat::AudioCodec>();
currentIndex = 0;
ui->audioCodecBox->clear();
ui->audioCodecBox->addItem(tr("Default audio codec"),
QVariant::fromValue(QMediaFormat::AudioCodec::Unspecified));
const QList<QMediaFormat::AudioCodec> supportedCodecs = format.supportedAudioCodecs(QMediaFormat::Encode);
for (auto codec : supportedCodecs) {
if (codec == currentCodec)
currentIndex = ui->audioCodecBox->count();
ui->audioCodecBox->addItem(QMediaFormat::audioCodecDescription(codec),
QVariant::fromValue(codec));
}
ui->audioCodecBox->setCurrentIndex(currentIndex);
m_updatingFormats = false;
}
QMediaFormat AudioRecorder::selectedMediaFormat() const
{
QMediaFormat format;
format.setFileFormat(boxValue(ui->containerBox).value<QMediaFormat::FileFormat>());
format.setAudioCodec(boxValue(ui->audioCodecBox).value<QMediaFormat::AudioCodec>());
return format;
}