10
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

QtAdvent Calendar 2017

Day 10

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

Last updated at Posted at 2017-12-10

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を使うのが推奨のようです。

10
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?