はじめに
これは Qt Advent Calendar 2024 13日目の記事です。
Qt6 で生まれ変わった QtMultimedia も、Qt 6.8 までくるとだいぶ落ち着いてきましたね。
しかし、実際に利用してみるとなにかおかしいな?となるのが Qt のいいところ。
そんなわけで、そういう時にはとりあえずバグレポを書くなり、パッチを送るなりしましょう。
QtMultimedia で、マイクから音声を取得する方法
Qt では QAudioSource というクラスを利用して、簡単にマイクから音声データを取得することが可能です。
QFile destinationFile; // Class member
QAudioSource* audio; // Class member
{
destinationFile.setFileName("/tmp/test.raw");
destinationFile.open( QIODevice::WriteOnly | QIODevice::Truncate );
QAudioFormat format;
// Set up the desired format, for example:
format.setSampleRate(8000);
format.setChannelCount(1);
format.setSampleFormat(QAudioFormat::UInt8);
QAudioDevice info = QMediaDevices::defaultAudioInput();
if (!info.isFormatSupported(format)) {
qWarning() << "Default format not supported, trying to use the nearest.";
}
audio = new QAudioSource(format, this);
connect(audio, &QAudioSource::stateChanged, this, &AudioInputExample::handleStateChanged);
QTimer::singleShot(3000, this, &AudioInputExample::stopRecording);
audio->start(&destinationFile);
// Records audio for 3000ms
}
QAudioSource には start() メソッドが2種類用意されています。
前者は、QAudioSource (の内部実装)側が QIODevice を用意し、利用する際にはその QIODevice からデータを読み込みます。こちらは Push Mode
と呼ばれます。
後者は、利用する側が用意した QIODevice に対して QAudioSource がデータを書き込む形になります。Pull Mode
と呼ばれるようです。
上記のサンプルコードは、後者が利用されています。
Push Mode が微妙に使いにくい
QtMultimedia には、マイクからデータを取得するサンプルアプリがあります。
ソースコードは QtMultimedia の中 にあります。
このサンプルは、Push と Pull を切り替える機能が用意されていて、モードが切り替わる際に前述の2つの start()
を呼び分けています。
void InputTest::toggleMode()
{
m_audioInput->stop();
toggleSuspend();
// Change between pull and push modes
if (m_pullMode) {
m_modeButton->setText(tr("Enable push mode"));
m_audioInput->start(m_audioInfo.data());
} else {
m_modeButton->setText(tr("Enable pull mode"));
auto *io = m_audioInput->start();
if (!io)
return;
connect(io, &QIODevice::readyRead, [this, io]() {
static const qint64 BufferSize = 4096;
const qint64 len = qMin(m_audioInput->bytesAvailable(), BufferSize);
QByteArray buffer(len, 0);
qint64 l = io->read(buffer.data(), len);
if (l > 0) {
const qreal level = m_audioInfo->calculateLevel(buffer.constData(), l);
m_canvas->setLevel(level);
}
});
}
m_pullMode = !m_pullMode;
}
Push モードの際には、
connect(io, &QIODevice::readyRead, [this, io]() {...}
の中で、データを受信した際の処理を行っているのですが、受信したデータの大きさの取得を m_audioInput->bytesAvailable()
という形で行っています。
これはちょっとおかしくて、io
が QIODevice
なので、io->bytesAvailable()
とするのが普通ですよね。
しかし、これは私が試したLinux環境では動きません(でした)。
調査と結果
qtmultimedia/src/multimedia/pulseaudio/qpulseaudiosource_p.h の以下のクラスが QAudioSource::start() で返ってくる QIODevice の実態です。
class PulseInputPrivate : public QIODevice
{
Q_OBJECT
public:
PulseInputPrivate(QPulseAudioSource *audio);
~PulseInputPrivate() override = default;
qint64 readData(char *data, qint64 len) override;
qint64 writeData(const char *data, qint64 len) override;
void trigger();
private:
QPulseAudioSource *m_audioDevice;
};
bytesAvailable()
が未実装ですね。動いていない原因が判明してすっきりしました。
なおしてみた
以下の変更を追加して
diff --git a/src/multimedia/pulseaudio/qpulseaudiosource.cpp b/src/multimedia/pulseaudio/qpulseaudiosource.cpp
index 87bba2427..b41c54769 100644
--- a/src/multimedia/pulseaudio/qpulseaudiosource.cpp
+++ b/src/multimedia/pulseaudio/qpulseaudiosource.cpp
@@ -545,6 +545,11 @@ PulseInputPrivate::PulseInputPrivate(QPulseAudioSource *audio)
m_audioDevice = qobject_cast<QPulseAudioSource *>(audio);
}
+qint64 PulseInputPrivate::bytesAvailable() const
+{
+ return m_audioDevice->bytesReady();
+}
+
qint64 PulseInputPrivate::readData(char *data, qint64 len)
{
return m_audioDevice->read(data, len);
diff --git a/src/multimedia/pulseaudio/qpulseaudiosource_p.h b/src/multimedia/pulseaudio/qpulseaudiosource_p.h
index d652f81a0..d3e4adc1b 100644
--- a/src/multimedia/pulseaudio/qpulseaudiosource_p.h
+++ b/src/multimedia/pulseaudio/qpulseaudiosource_p.h
@@ -102,6 +102,7 @@ public:
PulseInputPrivate(QPulseAudioSource *audio);
~PulseInputPrivate() override = default;
+ qint64 bytesAvailable() const override;
qint64 readData(char *data, qint64 len) override;
qint64 writeData(const char *data, qint64 len) override;
611208: PulseAudio: Enable bytesAvailable() for QAudioSource::start() を作成しました。
これからレビューをしてもらうところですが、近い将来修正が取り込まれるといいなと思っています。
17日に取り込まれました!
おわりに
Qt をお使いのみなさん、アドベントカレンダーになんでもいいので記事を書いて、盛り上げていきましょう!
明日は、Qt 勉強会 @ Tokyo R #34 が新宿で開催されます。興味のある方は、気軽にふらっと遊びにきてください!