audiocapture.mm 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. #import <AVFoundation/AVFoundation.h>
  2. #include "audiocapture.h"
  3. #include <QDebug>
  4. #include <QCoreApplication>
  5. #include <cmath>
  6. // Target sample rate for ASR
  7. static const int TARGET_SAMPLE_RATE = 16000;
  8. class AudioCapture::Private : public QObject {
  9. Q_OBJECT
  10. public:
  11. Private(QObject* parent = nullptr) : QObject(parent), m_audioEngine(nil), m_isRecording(false), m_actualSampleRate(44100) {}
  12. ~Private() { stopRecording(); }
  13. // Simple linear interpolation resampling
  14. QByteArray resample(const float* input, int inputCount, int inputRate, int outputRate) {
  15. if (inputRate == outputRate) {
  16. QByteArray result(inputCount * sizeof(int16_t), 0);
  17. int16_t* out = (int16_t*)result.data();
  18. for (int i = 0; i < inputCount; i++) {
  19. float s = fmaxf(-1.0f, fminf(1.0f, input[i]));
  20. out[i] = (int16_t)(s * 32767.0f);
  21. }
  22. return result;
  23. }
  24. double ratio = (double)outputRate / inputRate;
  25. int outputCount = (int)(inputCount * ratio);
  26. QByteArray result(outputCount * sizeof(int16_t), 0);
  27. int16_t* out = (int16_t*)result.data();
  28. for (int i = 0; i < outputCount; i++) {
  29. double srcIndex = i / ratio;
  30. int srcIdx = (int)srcIndex;
  31. double frac = srcIndex - srcIdx;
  32. float s1 = (srcIdx < inputCount) ? input[srcIdx] : 0.0f;
  33. float s2 = (srcIdx + 1 < inputCount) ? input[srcIdx + 1] : s1;
  34. float s = s1 + (s2 - s1) * frac;
  35. s = fmaxf(-1.0f, fminf(1.0f, s));
  36. out[i] = (int16_t)(s * 32767.0f);
  37. }
  38. return result;
  39. }
  40. bool startRecording() {
  41. if (m_isRecording) return true;
  42. m_audioEngine = [[AVAudioEngine alloc] init];
  43. AVAudioInputNode* inputNode = [m_audioEngine inputNode];
  44. AVAudioFormat* recordingFormat = [inputNode inputFormatForBus:0];
  45. m_actualSampleRate = (int)recordingFormat.sampleRate;
  46. qDebug() << "Input format - sample rate:" << m_actualSampleRate
  47. << "channel count:" << recordingFormat.channelCount;
  48. qDebug() << "Will resample from" << m_actualSampleRate << "to" << TARGET_SAMPLE_RATE;
  49. __weak QObject* weakParent = this;
  50. [inputNode installTapOnBus:0 bufferSize:4096 format:recordingFormat block:^(AVAudioPCMBuffer* buffer, AVAudioTime* when) {
  51. if (!buffer || buffer.frameLength == 0) return;
  52. float* samples = buffer.floatChannelData[0];
  53. int numSamples = buffer.frameLength;
  54. // Resample to target rate
  55. QByteArray audioData = resample(samples, numSamples, m_actualSampleRate, TARGET_SAMPLE_RATE);
  56. QMetaObject::invokeMethod(weakParent, "handleAudioData", Qt::QueuedConnection, Q_ARG(QByteArray, audioData));
  57. }];
  58. NSError* error = nil;
  59. [m_audioEngine prepare];
  60. [m_audioEngine startAndReturnError:&error];
  61. if (error) {
  62. qWarning() << "Failed to start audio engine:" << error.localizedDescription.UTF8String;
  63. return false;
  64. }
  65. m_isRecording = true;
  66. qDebug() << "Audio recording started (resampling to 16kHz)";
  67. return true;
  68. }
  69. void stopRecording() {
  70. if (!m_isRecording) return;
  71. if (m_audioEngine) {
  72. [[m_audioEngine inputNode] removeTapOnBus:0];
  73. [m_audioEngine stop];
  74. m_audioEngine = nil;
  75. }
  76. m_isRecording = false;
  77. qDebug() << "Audio recording stopped";
  78. }
  79. bool isRecording() const { return m_isRecording; }
  80. public slots:
  81. void handleAudioData(const QByteArray& data) {
  82. float sum = 0.0f;
  83. const int16_t* samples = (const int16_t*)data.constData();
  84. int numSamples = data.size() / sizeof(int16_t);
  85. for (int i = 0; i < numSamples; i++) {
  86. float s = fabsf(samples[i] / 32768.0f);
  87. sum += s * s;
  88. }
  89. float rms = sqrtf(sum / numSamples);
  90. float level = qBound(0.0f, rms * 2.0f, 1.0f);
  91. emit audioLevelChanged(level);
  92. emit audioDataReady(data);
  93. }
  94. signals:
  95. void audioDataReady(const QByteArray& data);
  96. void audioLevelChanged(qreal level);
  97. private:
  98. AVAudioEngine* m_audioEngine;
  99. bool m_isRecording;
  100. int m_actualSampleRate;
  101. };
  102. AudioCapture::AudioCapture(QObject* parent)
  103. : QObject(parent)
  104. , m_private(new Private(this))
  105. {
  106. qDebug() << "AudioCapture created";
  107. connect(m_private, &Private::audioDataReady, this, &AudioCapture::audioDataReady);
  108. connect(m_private, &Private::audioLevelChanged, this, &AudioCapture::audioLevelChanged);
  109. }
  110. AudioCapture::~AudioCapture()
  111. {
  112. stop();
  113. }
  114. bool AudioCapture::start()
  115. {
  116. qDebug() << "AudioCapture::start()";
  117. return m_private->startRecording();
  118. }
  119. void AudioCapture::stop()
  120. {
  121. qDebug() << "AudioCapture::stop()";
  122. m_private->stopRecording();
  123. }
  124. bool AudioCapture::isActive() const
  125. {
  126. return m_private->isRecording();
  127. }
  128. #include "audiocapture.moc"