Введение
Статьи были по разбору воспроизведение — в этой статье переходим к записи звука с микрофона через QAudioSource, и работе с «сырыми» PCM-данными (Pulse-Code Modulation, несжатое цифровое представление звуковой волны), что даёт основу для дальнейшей обработки сигнала и записи в файл.
Концепция
QAudioSource захватывает звук с выбранного устройства ввода (микрофона) согласно заданному формату (QAudioFormat — частота дискретизации, число каналов, формат сэмпла), предоставляя данные через QIODevice-интерфейс, из которого код приложения читает поступающие PCM-сэмплы напрямую, без какого-либо предварительного сжатия или кодирования. Это даёт максимальный контроль и доступ к «сырым» данным сигнала — необходимый для любой формы анализа или обработки звука до того, как он будет, возможно, сжат и сохранён в конкретном файловом формате.
Пример кода
// Захват аудио с микрофона и чтение PCM-данных
#include <QAudioSource>
#include <QAudioFormat>
#include <QMediaDevices>
class MicrophoneCapture : public QObject
{
Q_OBJECT
public:
MicrophoneCapture(QObject *parent = nullptr) : QObject(parent)
{
QAudioFormat format;
format.setSampleRate(44100); // 44.1 кГц — стандартная частота дискретизации
format.setChannelCount(1); // моно — достаточно для речи, экономит память
format.setSampleFormat(QAudioFormat::Int16); // 16-битные знаковые сэмплы
QAudioDevice inputDevice = QMediaDevices::defaultAudioInput();
if (!inputDevice.isFormatSupported(format)) {
qWarning() << "Формат не поддерживается выбранным устройством ввода!";
return;
}
m_audioSource = new QAudioSource(inputDevice, format, this);
}
void startCapture()
{
m_ioDevice = m_audioSource->start(); // возвращает QIODevice для чтения поступающих данных
connect(m_ioDevice, &QIODevice::readyRead, this, &MicrophoneCapture::processIncomingData);
}
void stopCapture() { m_audioSource->stop(); }
private slots:
void processIncomingData()
{
QByteArray rawData = m_ioDevice->readAll();
const qint16 *samples = reinterpret_cast<const qint16 *>(rawData.constData());
int sampleCount = rawData.size() / sizeof(qint16);
qint16 peak = 0;
for (int i = 0; i < sampleCount; ++i) {
peak = std::max(peak, static_cast<qint16>(std::abs(samples[i])));
}
emit peakLevelChanged(peak); // например, для индикатора уровня громкости в интерфейсе
}
signals:
void peakLevelChanged(qint16 peak);
private:
QAudioSource *m_audioSource = nullptr;
QIODevice *m_ioDevice = nullptr;
};
Пояснения к коду
Проверка isFormatSupported() перед созданием QAudioSource — обязательный шаг, поскольку не каждое физическое устройство ввода гарантированно поддерживает любую произвольно выбранную комбинацию частоты дискретизации, числа каналов и формата сэмпла, и попытка использовать неподдерживаемый формат может привести либо к ошибке, либо (что хуже) к неявному, тихому искажению захватываемого звука самой системой. processIncomingData() показывает прямой доступ к «сырым» байтам через reinterpret_cast в массив qint16 — поскольку формат был явно задан как Int16, каждый сэмпл занимает ровно 2 байта, и этот прямой доступ позволяет выполнить простой анализ (поиск пикового значения амплитуды) без какого-либо промежуточного декодирования, которое было бы необходимо для сжатых форматов.
Подводные камни
- Чтение данных из
QIODevice, возвращённогоstart(), без подписки на сигналreadyRead, в надежде на синхронное, блокирующее чтение —QAudioSourceспроектирован для асинхронной работы, при которой данные поступают порциями по мере захвата с микрофона, и попытка читать данные вне обработчикаreadyRead(например, в цикле с попытками опроса) либо не получит данные своевременно, либо потребует неэффективного активного ожидания, тогда как событийная, асинхронная модель Qt предполагает именно реакцию на сигнал о готовности новых данных. - Несогласованность формата (
QAudioFormat), использованного при захвате, с форматом, ожидаемым кодом дальнейшей обработки или записи — если код обработки/записи жёстко предполагает конкретное число каналов или битность сэмпла, отличное от реально заданного при созданииQAudioSource, интерпретация байтов будет неверной (например, интерпретация двух последовательных одноканальных сэмплов как одного, более широкого по разрядности сэмпла), что приведёт к искажённому, «шумящему» или вовсе неразборчивому результату без какой-либо явной ошибки на уровне кода. - Игнорирование задержки (latency) между реальным звуком и моментом получения соответствующих данных через
readyRead— захват и буферизация аудио вносят неизбежную, хотя обычно небольшую задержку, и для приложений, критичных к минимальной задержке (например, синхронизация с видео в реальном времени, статья 475), эта задержка должна быть явно учтена и, при необходимости, минимизирована через настройку размера внутреннего буфера (QAudioSource::setBufferSize()). - Запрос доступа к микрофону без явного учёта необходимости разрешения пользователя на современных операционных системах (macOS, Android, и современные версии Windows требуют явного, согласованного пользователем разрешения на доступ к микрофону) — попытка начать захват без предварительно полученного разрешения может либо завершиться ошибкой, либо системным диалогом запроса разрешения в неожидаемый код момент, и приложение должно явно обрабатывать оба возможных исхода (разрешение получено/отклонено пользователем), а не предполагать безусловный успешный доступ к микрофону.