UBMicrophoneInput.cpp 8.74 KB
Newer Older
Craig Watson's avatar
Craig Watson committed
1
/*
2
 * Copyright (C) 2015-2018 Département de l'Instruction Publique (DIP-SEM)
Craig Watson's avatar
Craig Watson committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 *
 * This file is part of OpenBoard.
 *
 * OpenBoard is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License,
 * with a specific linking exception for the OpenSSL project's
 * "OpenSSL" library (or with modified versions of it that use the
 * same license as the "OpenSSL" library).
 *
 * OpenBoard is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with OpenBoard. If not, see <http://www.gnu.org/licenses/>.
 */

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
#include "UBMicrophoneInput.h"

UBMicrophoneInput::UBMicrophoneInput()
    : mAudioInput(NULL)
    , mIODevice(NULL)
    , mSeekPos(0)
{
}

UBMicrophoneInput::~UBMicrophoneInput()
{
    if (mAudioInput)
        delete mAudioInput;
}

bool UBMicrophoneInput::init()
{
    if (mAudioDeviceInfo.isNull()) {
        qWarning("No audio input device selected; using default");
        mAudioDeviceInfo = QAudioDeviceInfo::defaultInputDevice();
    }

    mAudioFormat = mAudioDeviceInfo.preferredFormat();
45 46 47 48 49 50 51 52 53 54 55 56

    /*
     *  https://ffmpeg.org/doxygen/3.1/group__lavu__sampfmts.html#gaf9a51ca15301871723577c730b5865c5
        The data described by the sample format is always in native-endian order.
        Sample values can be expressed by native C types, hence the lack of a signed 24-bit sample format
        even though it is a common raw audio data format.

        If a signed 24-bit sample format is natively preferred, we a set signed 16-bit sample format instead.
    */
    if (mAudioFormat.sampleSize() == 24)
        mAudioFormat.setSampleSize(16);

57 58 59 60 61
    mAudioInput = new QAudioInput(mAudioDeviceInfo, mAudioFormat, NULL);

    connect(mAudioInput, SIGNAL(stateChanged(QAudio::State)),
            this, SLOT(onAudioInputStateChanged(QAudio::State)));

Craig Watson's avatar
Craig Watson committed
62 63

    qDebug() << "Input device name: " << mAudioDeviceInfo.deviceName();
64 65 66 67
    qDebug() << "Input sample format: " << mAudioFormat.sampleSize() << "bit"
             << mAudioFormat.sampleType() << "at" << mAudioFormat.sampleRate() << "Hz"
             << "; codec: " << mAudioFormat.codec();

68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
    return true;
}

void UBMicrophoneInput::start()
{
    mIODevice = mAudioInput->start();

    connect(mIODevice, SIGNAL(readyRead()),
            this, SLOT(onDataReady()));

    if (mAudioInput->error() == QAudio::OpenError)
        qWarning() << "Error opening audio input";
}

void UBMicrophoneInput::stop()
{
    mAudioInput->stop();
}

QStringList UBMicrophoneInput::availableDevicesNames()
{
    QStringList names;
    QList<QAudioDeviceInfo> devices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);

    foreach (QAudioDeviceInfo device, devices) {
        names.push_back(device.deviceName());
    }

    return names;
}

void UBMicrophoneInput::setInputDevice(QString name)
{
    if (name.isEmpty()) {
        mAudioDeviceInfo = QAudioDeviceInfo::defaultInputDevice();
        return;
    }

    QList<QAudioDeviceInfo> devices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
    bool found = false;

    foreach (QAudioDeviceInfo device, devices) {
        if (device.deviceName() == name) {
            mAudioDeviceInfo = device;
            found = true;
            break;
        }
    }

    if (!found) {
        qWarning() << "Audio input device not found; using default instead";
        mAudioDeviceInfo = QAudioDeviceInfo::defaultInputDevice();
    }

}

Craig Watson's avatar
Craig Watson committed
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
int UBMicrophoneInput::channelCount()
{
    return mAudioFormat.channelCount();
}

int UBMicrophoneInput::sampleRate()
{
    return mAudioFormat.sampleRate();
}

/* Return the sample size in bits */
int UBMicrophoneInput::sampleSize()
{
    return mAudioFormat.sampleSize();
}

/** Return the sample format in FFMpeg style (AVSampleFormat enum) */
int UBMicrophoneInput::sampleFormat()
{
    enum AVSampleFormat {
        AV_SAMPLE_FMT_NONE = -1,
        AV_SAMPLE_FMT_U8,
        AV_SAMPLE_FMT_S16,
        AV_SAMPLE_FMT_S32,
        AV_SAMPLE_FMT_FLT,
        AV_SAMPLE_FMT_DBL,
        AV_SAMPLE_FMT_U8P,
        AV_SAMPLE_FMT_S16P,
        AV_SAMPLE_FMT_S32P,
        AV_SAMPLE_FMT_FLTP,
        AV_SAMPLE_FMT_DBLP,
        AV_SAMPLE_FMT_NB
    };

    int sampleSize = mAudioFormat.sampleSize();
    QAudioFormat::SampleType sampleType = mAudioFormat.sampleType();

    switch (sampleType) {
        case QAudioFormat::Unknown:
            return AV_SAMPLE_FMT_NONE;

        case QAudioFormat::SignedInt:
            if (sampleSize == 16)
                return AV_SAMPLE_FMT_S16;
            if (sampleSize == 32)
                return AV_SAMPLE_FMT_S32;
            break;

        case QAudioFormat::UnSignedInt:
            if (sampleSize == 8)
                return AV_SAMPLE_FMT_U8;
            break;

        case QAudioFormat::Float:
            return AV_SAMPLE_FMT_FLT;

        default:
            return AV_SAMPLE_FMT_NONE;
    }

    return AV_SAMPLE_FMT_NONE;
}

QString UBMicrophoneInput::codec()
{
    return mAudioFormat.codec();
}

192
static qint64 uSecsElapsed = 0;
193 194 195 196
void UBMicrophoneInput::onDataReady()
{
    int numBytes = mAudioInput->bytesReady();

197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
    uSecsElapsed += mAudioFormat.durationForBytes(numBytes);

    // Only emit data every 100ms
    if (uSecsElapsed > 100000) {
        uSecsElapsed = 0;
        QByteArray data = mIODevice->read(numBytes);

        quint8 level = audioLevel(data);
        if (level != mLastAudioLevel) {
            mLastAudioLevel = level;
            emit audioLevelChanged(level);
        }

        emit dataAvailable(data);
    }
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
}

void UBMicrophoneInput::onAudioInputStateChanged(QAudio::State state)
{
    switch (state) {
        case QAudio::StoppedState:
            if (mAudioInput->error() != QAudio::NoError) {
                emit error(getErrorString(mAudioInput->error()));
            }
            break;

        // handle other states?

        default:
            break;
    }
}

230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
/**
 * @brief Calculate the current audio level of an array of samples and return it
 * @param data An array of audio samples
 * @return A value between 0 and 255
 *
 * Audio level is calculated as the RMS (root mean square) of the samples
 * in the supplied array.
 */
quint8 UBMicrophoneInput::audioLevel(const QByteArray &data)
{
    int bytesPerSample = mAudioFormat.bytesPerFrame() / mAudioFormat.channelCount();

    const char * ptr = data.constData();
    double sum = 0;
    int n_samples = data.size() / bytesPerSample;

    for (int i(0); i < (data.size() - bytesPerSample); i += bytesPerSample) {
        sum += pow(sampleRelativeLevel(ptr + i), 2);
    }

    double rms = sqrt(sum/n_samples);

    // The vu meter looks a bit better when the RMS isn't displayed linearly, as perceived sound
Craig Watson's avatar
Craig Watson committed
253
    // level increases logarithmically. So here RMS is substituted by rms^(1/e)
254 255 256 257 258 259 260 261
    rms = pow(rms, 1./exp(1));

    return UINT8_MAX * rms;
}

/**
 * @brief Calculate one sample's level relative to its maximum value
 * @param sample One sample, in the format specified by mAudioFormat
Craig Watson's avatar
Craig Watson committed
262 263
 * @return A double between 0 and 1.0, where 1.0 is the maximum value the sample can take,
 *         or -1 if the value couldn't be calculated.
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
 */
double UBMicrophoneInput::sampleRelativeLevel(const char* sample)
{
    QAudioFormat::SampleType type =  mAudioFormat.sampleType();
    int sampleSize = mAudioFormat.sampleSize();

    if (sampleSize == 16 && type == QAudioFormat::SignedInt)
        return double(*reinterpret_cast<const int16_t*>(sample))/INT16_MAX;

    if (sampleSize == 8 && type == QAudioFormat::SignedInt)
        return double(*reinterpret_cast<const int8_t*>(sample))/INT8_MAX;

    if (sampleSize == 16 && type == QAudioFormat::UnSignedInt)
        return double(*reinterpret_cast<const uint16_t*>(sample))/UINT16_MAX;

    if (sampleSize == 8 && type == QAudioFormat::UnSignedInt)
        return double(*reinterpret_cast<const uint8_t*>(sample))/UINT8_MAX;

    if (type == QAudioFormat::Float)
        return (*reinterpret_cast<const float*>(sample) + 1.0)/2.;

    return -1;
}
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308

/**
 * @brief Return a meaningful error string based on QAudio error codes
 */
QString UBMicrophoneInput::getErrorString(QAudio::Error errorCode)
{
    switch (errorCode) {
        case QAudio::NoError :
            return "";

        case QAudio::OpenError :
            return "Couldn't open the audio device";

        case QAudio::IOError :
            return "Error reading from audio device";

        case QAudio::UnderrunError :
            return "Underrun error";

        case QAudio::FatalError :
            return "Fatal error; audio device unusable";

309 310
        default:
            return "unhandled error...";
311 312 313
    }
    return "";
}