Help us understand the problem. What is going on with this article?

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

soramimi_jp
C++とQtが好き。電子工作もやる。第一種情報処理技術者と第二種電気工事士を持ってる。ワンチップマイコンのファームウェアからPCのデスクトップアプリまで。PCより大規模なシステムは守備範囲外。うちの子かわいい(ドール)。40代独身おっさん(´・ω・`)
http://www.soramimi.jp/
AI-medical-service
近未来の内視鏡医療を実現する医療ベンチャー
https://www.ai-ms.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away