Edited at
QtDay 10

Qtでオーディオクラスを使う

More than 1 year has passed since last update.

Qtを使ってIP電話アプリを作っていて、音声を入出力する必要がありました。

Qtに限らず一般的に、オーディオデバイスを使うのって難しいよね、という固定観念がありました。WindowsのマルチメディアAPIが複雑怪奇なので、オーディオ制御するの面倒だなぁ、と思い込んでいました。LinuxやMacに至っては、APIの呼び出し方さえ知りません。それを試しにQtを使ってやってみたら、アホみたいに簡単だったというお話です。

Qtがサポートするマルチメディア機能は豊富(だと思う)のですが、初歩中の初歩、音を鳴らして、同時に入力するだけ、という、最も基本の機能を使ってみます。

とりあえず、Qtウィジェットアプリケーションを新規作成して、プログレスバーを配置します。

progressbar.png

プロジェクトファイルに、

QT += multimedia

を追加しておきます。

メインウィンドウのヘッダです。


MainWindow.h

#ifndef MAINWINDOW_H

#define MAINWINDOW_H

#include <QAudioInput>
#include <QAudioOutput>
#include <QMainWindow>
#include <memory>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
Q_OBJECT
private:
std::shared_ptr<QAudioInput> audio_input;
std::shared_ptr<QAudioOutput> audio_output;
QIODevice *audio_input_device;
QIODevice *audio_output_device;
int count = 0;
protected:
void timerEvent(QTimerEvent *event);
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void onReadyRead();
private:
Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H


メインウィンドウのソースです。


MainWindow.cpp

#include "MainWindow.h"

#include "ui_MainWindow.h"
#include <QAudioFormat>

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);

QAudioFormat format;
format.setByteOrder(QAudioFormat::LittleEndian);
format.setChannelCount(1);
format.setCodec("audio/pcm");
format.setSampleRate(8000);
format.setSampleSize(16);
format.setSampleType(QAudioFormat::SignedInt);
audio_input = std::shared_ptr<QAudioInput>(new QAudioInput(format));
audio_output = std::shared_ptr<QAudioOutput>(new QAudioOutput(format));
audio_input_device = audio_input->start();
audio_output_device = audio_output->start();
connect(audio_input_device, SIGNAL(readyRead()), this, SLOT(onReadyRead()));

ui->progressBar->setRange(0, 100);
ui->progressBar->setValue(0);

startTimer(10);
}

MainWindow::~MainWindow()
{
delete ui;
}

void MainWindow::onReadyRead()
{
QByteArray ba = audio_input_device->readAll();
int n = ba.size() / 2;
int16_t *p = (int16_t *)ba.data();
int max = 0;
for (int i = 0; i < n; i++) {
int v = p[i];
if (v < 0) v = -v;
if (max < v) {
max = v;
}
}
int pos = max * 100 / 32768;
ui->progressBar->setValue(pos);
}

void MainWindow::timerEvent(QTimerEvent *event)
{
int len = audio_output->bytesFree();
int n = len / 2;
if (n > 0) {
std::vector<int16_t> buf(n);
for (int i = 0; i < n; i++) {
buf[i] = (count & 8) ? 10000 : -10000;
count++;
if (count >= 8000) {
count = 0;
}
}
audio_output_device->write((char const *)&buf[0], buf.size() * 2);
}
}


とりあえず定数は決め打ちにしています。8kHz、16ビット、モノラルです。

オーディオ入力からのデータがある程度貯まったら、readyReadシグナルが発行されますので、データを受け取ります。このプログラムでは、最大値を求めてプログレスバーを更新しています。

10msごとのタイマイベントを発生させて、出力に空きがあれば、音声データを書き込みます。単純に500Hzの矩形波を生成しています。

ところで、QAudioInputやQAudioOutputというクラスですが、プラットフォームによって挙動が少し異なります。マルチスレッドの中で使うと、うまく動作しないことがあります。Qtのイベントループが廻るスレッド(メインのGUIスレッド)でQAudioInputやQAudioOutputを使うのが推奨のようです。