/* Copyright (C) 2019 * swift project Community / Contributors * * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, * or distributed except according to the terms contained in the LICENSE file. */ //! \file #include "input.h" #include "blackmisc/logmessage.h" #include "blacksound/audioutilities.h" #include #include #include #include #include using namespace BlackMisc; using namespace BlackMisc::Audio; using namespace BlackSound; namespace BlackCore { namespace Afv { namespace Audio { CAudioInputBuffer::CAudioInputBuffer(QObject *parent) : QIODevice(parent) { this->setObjectName("CAudioInputBuffer"); } void CAudioInputBuffer::start(int channelCount) { m_channelCount = channelCount; m_buffer.clear(); if (!this->isOpen()) { open(QIODevice::WriteOnly | QIODevice::Unbuffered); } } void CAudioInputBuffer::stop() { this->close(); } qint64 CAudioInputBuffer::readData(char *data, qint64 maxlen) { Q_UNUSED(data) Q_UNUSED(maxlen) return 0; } qint64 CAudioInputBuffer::writeData(const char *data, qint64 len) { m_buffer.append(data, static_cast(len)); const int byteCount = 1920 * m_channelCount; while (m_buffer.size() > byteCount) { // qDebug() << QDateTime::currentMSecsSinceEpoch() << "CAudioInputBuffer::writeData " << m_buffer.size(); emit frameAvailable(m_buffer.left(byteCount)); m_buffer.remove(0, byteCount); } return len; } CInput::CInput(int sampleRate, QObject *parent) : QObject(parent), m_sampleRate(sampleRate), m_encoder(sampleRate, 1, OPUS_APPLICATION_VOIP) { this->setObjectName("CInput"); m_encoder.setBitRate(16 * 1024); } bool CInput::setVolume(double volume) { if (qFuzzyCompare(m_volume, volume)) { return false; } m_volume = volume; return true; } void CInput::start(const CAudioDeviceInfo &inputDevice) { if (m_started) { return; } m_device = inputDevice; m_inputFormat.setSampleRate(m_sampleRate); m_inputFormat.setChannelCount(1); m_inputFormat.setSampleSize(16); m_inputFormat.setSampleType(QAudioFormat::SignedInt); m_inputFormat.setByteOrder(QAudioFormat::LittleEndian); m_inputFormat.setCodec("audio/pcm"); QAudioDeviceInfo selectedDevice = getLowestLatencyDevice(inputDevice, m_inputFormat); m_audioInput.reset(new QAudioInput(selectedDevice, m_inputFormat)); m_audioInputBuffer.start(m_inputFormat.channelCount()); #ifdef Q_OS_MAC CMacOSMicrophoneAccess::AuthorizationStatus status = m_micAccess.getAuthorizationStatus(); if (status == CMacOSMicrophoneAccess::Authorized) { m_audioInput->start(&m_audioInputBuffer); connect(&m_audioInputBuffer, &CAudioInputBuffer::frameAvailable, this, &CInput::audioInDataAvailable); m_started = true; return; } else if (status == CMacOSMicrophoneAccess::NotDetermined) { connect(&m_micAccess, &CMacOSMicrophoneAccess::permissionRequestAnswered, this, &CInput::delayedInitMicrophone); m_micAccess.requestAccess(); CLogMessage(this).info(u"MacOS requested input device"); } else { CLogMessage(this).error(u"Microphone access not granted. Voice input will not work."); return; } #else m_audioInput->start(&m_audioInputBuffer); connect(&m_audioInputBuffer, &CAudioInputBuffer::frameAvailable, this, &CInput::audioInDataAvailable); m_started = true; #endif } void CInput::stop() { if (!m_started) { return; } m_started = false; if (m_audioInput) { m_audioInput->stop(); } m_audioInput.reset(); m_audioInputBuffer.stop(); } void CInput::audioInDataAvailable(const QByteArray &frame) { QVector samples = convertBytesTo16BitPCM(frame); if (m_inputFormat.channelCount() == 2) { samples = convertFromStereoToMono(samples); } for (qint16 &sample : samples) { int value = qRound(sample * m_volume); if (value > std::numeric_limits::max()) value = std::numeric_limits::max(); if (value < std::numeric_limits::min()) value = std::numeric_limits::min(); sample = static_cast(value); qint16 sampleInput = qAbs(sample); m_maxSampleInput = qMax(qAbs(sampleInput), m_maxSampleInput); } int length; const QByteArray encodedBuffer = m_encoder.encode(samples, samples.size(), &length); m_opusBytesEncoded += length; m_sampleCount += samples.size(); if (m_sampleCount >= SampleCountPerEvent) { InputVolumeStreamArgs inputVolumeStreamArgs; qint16 maxInt = std::numeric_limits::max(); inputVolumeStreamArgs.PeakRaw = static_cast(m_maxSampleInput) / maxInt; inputVolumeStreamArgs.PeakDB = static_cast(20 * std::log10(inputVolumeStreamArgs.PeakRaw)); double db = qBound(minDb, inputVolumeStreamArgs.PeakDB, maxDb); double ratio = (db - minDb) / (maxDb - minDb); if (ratio < 0.30) { ratio = 0.0; } if (ratio > 1.0) { ratio = 1.0; } inputVolumeStreamArgs.PeakVU = ratio; emit inputVolumeStream(inputVolumeStreamArgs); m_sampleCount = 0; m_maxSampleInput = 0; } OpusDataAvailableArgs opusDataAvailableArgs = { m_audioSequenceCounter++, encodedBuffer }; emit opusDataAvailable(opusDataAvailableArgs); } #ifdef Q_OS_MAC void CInput::delayedInitMicrophone() { m_audioInput->start(&m_audioInputBuffer); connect(&m_audioInputBuffer, &CAudioInputBuffer::frameAvailable, this, &CInput::audioInDataAvailable); m_started = true; } #endif } // ns } // ns } // ns