|
@@ -0,0 +1,425 @@
|
|
|
+#include "AudioRecord.h"
|
|
|
+#include "spdlog.h"
|
|
|
+
|
|
|
+
|
|
|
+#include <alsa/pcm.h>
|
|
|
+#include <chrono>
|
|
|
+#include <cstdint>
|
|
|
+#include <stdio.h>
|
|
|
+#include <stdlib.h>
|
|
|
+
|
|
|
+
|
|
|
+#include "spdlog/spdlog.h"
|
|
|
+
|
|
|
+
|
|
|
+/* 获取声卡名称列表 */
|
|
|
+bool getAudioDevices(std::list<AudioDevice_t> &devices)
|
|
|
+{
|
|
|
+ snd_ctl_t *card_handle;
|
|
|
+ snd_ctl_card_info_t *info;
|
|
|
+ int card = -1;
|
|
|
+ while (snd_card_next(&card) >= 0 && card >= 0)
|
|
|
+ {
|
|
|
+ char name[32];
|
|
|
+ snprintf(name, sizeof(name), "hw:%d", card);
|
|
|
+
|
|
|
+ if (snd_ctl_open(&card_handle, name, 0) < 0) {
|
|
|
+ continue; // 无法打开声卡
|
|
|
+ }
|
|
|
+
|
|
|
+ snd_ctl_card_info_alloca(&info);
|
|
|
+ if (snd_ctl_card_info(card_handle, info) < 0) {
|
|
|
+ snd_ctl_close(card_handle);
|
|
|
+ continue; // 获取声卡信息失败
|
|
|
+ }
|
|
|
+ AudioDevice_t device;
|
|
|
+ /* 获取声卡信息 */
|
|
|
+ device.CardNumber = card;
|
|
|
+ device.CardID = snd_ctl_card_info_get_id(info);
|
|
|
+ device.CardName = snd_ctl_card_info_get_name(info);
|
|
|
+ device.CardDriver = snd_ctl_card_info_get_driver(info);
|
|
|
+ device.CardLongName = snd_ctl_card_info_get_longname(info);
|
|
|
+ device.CardMixername = snd_ctl_card_info_get_mixername(info);
|
|
|
+ device.CardComponents = snd_ctl_card_info_get_components(info);
|
|
|
+
|
|
|
+ /* 获取声卡中的所有pcm设备 */
|
|
|
+ snd_pcm_info_t *pcm_info;
|
|
|
+ snd_pcm_info_alloca(&pcm_info);
|
|
|
+ int pcmDevice = -1;
|
|
|
+ while(true)
|
|
|
+ {
|
|
|
+ if(snd_ctl_pcm_next_device(card_handle, &pcmDevice) < 0 || pcmDevice < 0)
|
|
|
+ {
|
|
|
+ break; // 没有更多的PCM设备
|
|
|
+ }
|
|
|
+ snd_pcm_info_set_device(pcm_info, pcmDevice);
|
|
|
+ snd_pcm_info_set_subdevice(pcm_info, 0); // 获取主设备
|
|
|
+ snd_pcm_info_set_stream(pcm_info, SND_PCM_STREAM_CAPTURE); // 获取捕获设备信息
|
|
|
+
|
|
|
+ if(snd_ctl_pcm_info(card_handle, pcm_info) < 0)
|
|
|
+ {
|
|
|
+ continue; // 获取PCM设备信息失败
|
|
|
+ }
|
|
|
+ PCMDevice_t pcmDeviceInfo;
|
|
|
+ pcmDeviceInfo.PCMDevice = snd_pcm_info_get_device(pcm_info);
|
|
|
+ pcmDeviceInfo.SubDevice = snd_pcm_info_get_subdevice(pcm_info);
|
|
|
+ pcmDeviceInfo.CardNumber = card;
|
|
|
+ pcmDeviceInfo.PCMID = snd_pcm_info_get_id(pcm_info);
|
|
|
+ pcmDeviceInfo.PCMName = snd_pcm_info_get_name(pcm_info);
|
|
|
+ pcmDeviceInfo.PCMSubName = snd_pcm_info_get_subdevice_name(pcm_info);
|
|
|
+
|
|
|
+ // 将PCM设备信息添加到声卡设备列表中
|
|
|
+ device.PCMDevices.push_back(pcmDeviceInfo);
|
|
|
+ }
|
|
|
+
|
|
|
+ devices.push_back(device);
|
|
|
+
|
|
|
+ snd_ctl_close(card_handle);
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool getPCMAudioDevice(std::list<AudioDeviceDesc_t> &devices)
|
|
|
+{
|
|
|
+ void **hints;
|
|
|
+ int err = snd_device_name_hint(-1, "pcm", &hints);
|
|
|
+ if (err < 0) {
|
|
|
+ SPDLOG_ERROR("无法获取声卡设备信息: {}", snd_strerror(err));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (hints == NULL) {
|
|
|
+ SPDLOG_ERROR("没有找到任何声卡设备");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ void **n = hints;
|
|
|
+ while (*n != NULL) {
|
|
|
+ char *name = snd_device_name_get_hint(*n, "NAME"); // 设备字符
|
|
|
+ char *desc = snd_device_name_get_hint(*n, "DESC"); // 设备描述
|
|
|
+ char *ioid = snd_device_name_get_hint(*n, "IOID"); // 输入/输出类型
|
|
|
+ char *card = snd_device_name_get_hint(*n, "CARD"); // 声卡名称
|
|
|
+ char* devName = snd_device_name_get_hint(*n, "DEV"); /* 设备编号 */
|
|
|
+
|
|
|
+ if (name == nullptr)
|
|
|
+ {
|
|
|
+ continue; // 跳过没有名称的设备
|
|
|
+ }
|
|
|
+ AudioDeviceDesc_t deviceDesc;
|
|
|
+ deviceDesc.DeviceName = name;
|
|
|
+ deviceDesc.DeviceDesc = desc ? desc : "无描述";
|
|
|
+ deviceDesc.IOID = ioid ? ioid : "未知类型";
|
|
|
+ deviceDesc.Card = card ? card : "未知声卡";
|
|
|
+ deviceDesc.DevNum = devName ? devName : "未知设备编号";
|
|
|
+
|
|
|
+ devices.push_back(deviceDesc);
|
|
|
+
|
|
|
+ if (name) free(name);
|
|
|
+ if (desc) free(desc);
|
|
|
+ if (ioid) free(ioid);
|
|
|
+ if (card) free(card);
|
|
|
+ if (devName) free(devName);
|
|
|
+ n++;
|
|
|
+ }
|
|
|
+ snd_device_name_free_hint(hints);
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// void record(std::string deviceName)
|
|
|
+// {
|
|
|
+// uint32_t sample_rate = 44100;
|
|
|
+// uint32_t channels = 2;
|
|
|
+// int i;
|
|
|
+// int err;
|
|
|
+
|
|
|
+// long unsigned int rframes = 44100; // 每次读取的帧数
|
|
|
+// short* buf = (short*)malloc(rframes * sizeof(short) * channels);
|
|
|
+// if (!buf) {
|
|
|
+// fprintf(stderr, "Failed to allocate buffer memory\n");
|
|
|
+// exit(1);
|
|
|
+// }
|
|
|
+// snd_pcm_t *capture_handle;
|
|
|
+// snd_pcm_hw_params_t *hw_params;
|
|
|
+
|
|
|
+// if ((err = snd_pcm_open (&capture_handle, deviceName.c_str(), SND_PCM_STREAM_CAPTURE, 0)) < 0) {
|
|
|
+// fprintf (stderr, "cannot open audio device %s (%s)\n",
|
|
|
+// deviceName.c_str(),
|
|
|
+// snd_strerror (err));
|
|
|
+// exit (1);
|
|
|
+// }
|
|
|
+// /* 给参数分配内存 */
|
|
|
+// if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
|
|
|
+// fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",
|
|
|
+// snd_strerror (err));
|
|
|
+// exit (1);
|
|
|
+// }
|
|
|
+// /* 初始化硬件参数结构 */
|
|
|
+// if ((err = snd_pcm_hw_params_any (capture_handle, hw_params)) < 0) {
|
|
|
+// fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",
|
|
|
+// snd_strerror (err));
|
|
|
+// exit (1);
|
|
|
+// }
|
|
|
+// /* 设置硬件参数 */
|
|
|
+// if ((err = snd_pcm_hw_params_set_access (capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
|
|
|
+// fprintf (stderr, "cannot set access type (%s)\n",
|
|
|
+// snd_strerror (err));
|
|
|
+// exit (1);
|
|
|
+// }
|
|
|
+// /* 设置采样格式 */
|
|
|
+// // SND_PCM_FORMAT_S16_LE: 16位小端格式
|
|
|
+// // SND_PCM_FORMAT_FLOAT_LE: 32位小端浮点格式
|
|
|
+// // SND_PCM_FORMAT_U8: 8位无符号格式
|
|
|
+// // SND_PCM_FORMAT_S32_LE: 32位小端整数格式
|
|
|
+// // SND_PCM_FORMAT_S24_LE: 24位小端整数格式
|
|
|
+// if ((err = snd_pcm_hw_params_set_format (capture_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
|
|
|
+// fprintf (stderr, "cannot set sample format (%s)\n",
|
|
|
+// snd_strerror (err));
|
|
|
+// exit (1);
|
|
|
+// }
|
|
|
+// /* 设置采样率 */
|
|
|
+// if ((err = snd_pcm_hw_params_set_rate_near (capture_handle, hw_params, &sample_rate, 0)) < 0) {
|
|
|
+// fprintf (stderr, "cannot set sample rate (%s)\n",
|
|
|
+// snd_strerror (err));
|
|
|
+// exit (1);
|
|
|
+// }
|
|
|
+// /* 设置通道数 */
|
|
|
+// if ((err = snd_pcm_hw_params_set_channels (capture_handle, hw_params, channels)) < 0) {
|
|
|
+// fprintf (stderr, "cannot set channel count (%s)\n",
|
|
|
+// snd_strerror (err));
|
|
|
+// exit (1);
|
|
|
+// }
|
|
|
+// /* 设置交错模式 */
|
|
|
+// err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
|
|
|
+// if (err < 0) {
|
|
|
+// fprintf(stderr, "cannot set interleaved mode (%s)\n", snd_strerror(err));
|
|
|
+// exit(1);
|
|
|
+// }
|
|
|
+// /* 设置周期大小 */
|
|
|
+// err = snd_pcm_hw_params_set_period_size(capture_handle, hw_params, 882, 0);
|
|
|
+// if (err < 0) {
|
|
|
+// fprintf(stderr, "cannot set period size (%s)\n", snd_strerror(err));
|
|
|
+// exit(1);
|
|
|
+// }
|
|
|
+// /* 设置缓冲区大小 */
|
|
|
+// err = snd_pcm_hw_params_set_buffer_size(capture_handle, hw_params, 8820);
|
|
|
+// if (err < 0) {
|
|
|
+// fprintf(stderr, "cannot set buffer size (%s)\n", snd_strerror(err));
|
|
|
+// exit(1);
|
|
|
+// }
|
|
|
+// /* 设置参数给声卡 */
|
|
|
+// if ((err = snd_pcm_hw_params (capture_handle, hw_params)) < 0) {
|
|
|
+// fprintf (stderr, "cannot set parameters (%s)\n",
|
|
|
+// snd_strerror (err));
|
|
|
+// exit (1);
|
|
|
+// }
|
|
|
+// /* 释放参数内存 */
|
|
|
+// snd_pcm_hw_params_free (hw_params);
|
|
|
+
|
|
|
+// if ((err = snd_pcm_prepare (capture_handle)) < 0) {
|
|
|
+// fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
|
|
|
+// snd_strerror (err));
|
|
|
+// exit (1);
|
|
|
+// }
|
|
|
+// auto f1 = fopen("r1.wav", "wb");
|
|
|
+// if(!f1) {
|
|
|
+// fprintf(stderr, "Failed to open file for writing\n");
|
|
|
+// exit(1);
|
|
|
+// }
|
|
|
+// std::chrono::steady_clock::time_point start_time = std::chrono::steady_clock::now();
|
|
|
+// for (i = 0; i < 10; ++i)
|
|
|
+// {
|
|
|
+// long unsigned int readNum;
|
|
|
+// start_time = std::chrono::steady_clock::now();
|
|
|
+// if ((readNum = snd_pcm_readi (capture_handle, buf, rframes)) != rframes) {
|
|
|
+// fprintf (stderr, "read from audio interface failed (%s)\n",
|
|
|
+// snd_strerror (readNum));
|
|
|
+// exit (1);
|
|
|
+// }
|
|
|
+// auto end_time = std::chrono::steady_clock::now();
|
|
|
+// auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
|
|
|
+// SPDLOG_DEBUG("第 {} 次录音,读取数据量: {} 帧,{} 字节,耗时: {} 毫秒", i + 1, readNum, readNum, duration.count());
|
|
|
+
|
|
|
+// if (fwrite(buf, sizeof(short), readNum * 2, f1) != readNum * 2) {
|
|
|
+// fprintf(stderr, "Failed to write data to file\n");
|
|
|
+// fclose(f1);
|
|
|
+// exit(1);
|
|
|
+// }
|
|
|
+// }
|
|
|
+// fclose(f1);
|
|
|
+
|
|
|
+// snd_pcm_close (capture_handle);
|
|
|
+// free(buf);
|
|
|
+// printf("录音完成,数据已保存到 r1.wav\n");
|
|
|
+
|
|
|
+// exit (0);
|
|
|
+// }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief Set the Record Params object
|
|
|
+ *
|
|
|
+ * @param sampleRate 采样率
|
|
|
+ * @param bits 位深度,支持8位,16位,24位,32位
|
|
|
+ * @param channels 通道数
|
|
|
+ */
|
|
|
+void AudioRecord::setRecordParams(int sampleRate, int bits, int channels)
|
|
|
+{
|
|
|
+ m_sampleRate = sampleRate;
|
|
|
+ m_bitDepth = bits;
|
|
|
+ m_channels = channels;
|
|
|
+ m_oneSampleSize = (m_bitDepth / 8) * m_channels; // 单个采样点的大小,单位字节
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/* 打开录音通道 */
|
|
|
+bool AudioRecord::openRecordChannel(const std::string &deviceName)
|
|
|
+{
|
|
|
+ m_deviceName = deviceName;
|
|
|
+ /* 计算缓冲区大小和周期大小
|
|
|
+ * 周期大小是指一个周期有多少个采样点,缓冲区大小是周期大小 * 周期个数,这里不需要设置周期个数
|
|
|
+ * 直接设置缓冲区大小,系统会自动计算周期个数,缓冲区是个环形队列
|
|
|
+ * 周期大小最好设置成可以被采样率的整数除尽,每次取出1秒数据的时候,间隔相差时间最短 */
|
|
|
+ snd_pcm_uframes_t buffer_size = m_sampleRate / 5; // 设置缓冲区大小为采样率的1/5秒
|
|
|
+ snd_pcm_uframes_t period_size = buffer_size / 10; // 设置周期大小为缓冲区大小的1/10秒
|
|
|
+
|
|
|
+ _snd_pcm_format bit_frame = _snd_pcm_format::SND_PCM_FORMAT_UNKNOWN;
|
|
|
+ if(m_bitDepth == 8)
|
|
|
+ {
|
|
|
+ bit_frame = SND_PCM_FORMAT_S8; // 8位采样格式
|
|
|
+ }
|
|
|
+ else if (m_bitDepth == 16)
|
|
|
+ {
|
|
|
+ bit_frame = SND_PCM_FORMAT_S16_LE; // 16位小端格式
|
|
|
+ }
|
|
|
+ else if (m_bitDepth == 24)
|
|
|
+ {
|
|
|
+ bit_frame = SND_PCM_FORMAT_S24_LE; // 24位小端格式
|
|
|
+ }
|
|
|
+ else if (m_bitDepth == 32)
|
|
|
+ {
|
|
|
+ bit_frame = SND_PCM_FORMAT_S32_LE; // 32位小端格式
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ SPDLOG_ERROR("不支持的采样位深度: {}", m_bitDepth);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ snd_pcm_hw_params_t *hw_params;
|
|
|
+ int err;
|
|
|
+ /* 打开声卡设备 */
|
|
|
+ if ((err = snd_pcm_open(&m_captureHandle, deviceName.c_str(), SND_PCM_STREAM_CAPTURE, 0)) < 0) {
|
|
|
+ SPDLOG_ERROR("无法打开音频设备 {}: {}", deviceName, snd_strerror(err));
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ /* 分配硬件参数结构 */
|
|
|
+ if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
|
|
|
+ SPDLOG_ERROR("无法分配硬件参数结构: {}", snd_strerror(err));
|
|
|
+ snd_pcm_close(m_captureHandle);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ /* 初始化硬件参数结构 */
|
|
|
+ if ((err = snd_pcm_hw_params_any(m_captureHandle, hw_params)) < 0) {
|
|
|
+ SPDLOG_ERROR("无法初始化硬件参数结构: {}", snd_strerror(err));
|
|
|
+ goto snd_free;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ /* 设置访问类型 */
|
|
|
+ if ((err = snd_pcm_hw_params_set_access(m_captureHandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
|
|
|
+ SPDLOG_ERROR("无法设置访问类型: {}", snd_strerror(err));
|
|
|
+ goto snd_free;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ /* 设置采样格式 */
|
|
|
+ if ((err = snd_pcm_hw_params_set_format(m_captureHandle, hw_params, bit_frame)) < 0) {
|
|
|
+ SPDLOG_ERROR("无法设置采样格式: {}", snd_strerror(err));
|
|
|
+ goto snd_free;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ /* 设置采样率 */
|
|
|
+ if ((err = snd_pcm_hw_params_set_rate(m_captureHandle, hw_params, m_sampleRate, 0)) < 0) {
|
|
|
+ SPDLOG_ERROR("无法设置采样率: {}", snd_strerror(err));
|
|
|
+ goto snd_free;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ /* 设置通道数 */
|
|
|
+ if ((err = snd_pcm_hw_params_set_channels(m_captureHandle, hw_params, m_channels)) < 0) {
|
|
|
+ SPDLOG_ERROR("无法设置通道数: {}", snd_strerror(err));
|
|
|
+ goto snd_free;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ /* 设置缓冲区大小 */
|
|
|
+ if ((err = snd_pcm_hw_params_set_buffer_size(m_captureHandle, hw_params, buffer_size)) < 0) {
|
|
|
+ SPDLOG_ERROR("无法设置缓冲区大小: {}", snd_strerror(err));
|
|
|
+ goto snd_free;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ /* 设置周期大小 */
|
|
|
+ if ((err = snd_pcm_hw_params_set_period_size(m_captureHandle, hw_params, period_size, 0)) < 0) {
|
|
|
+ SPDLOG_ERROR("无法设置周期大小: {}", snd_strerror(err));
|
|
|
+ goto snd_free;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ /* 应用硬件参数 */
|
|
|
+ if ((err = snd_pcm_hw_params(m_captureHandle, hw_params)) < 0) {
|
|
|
+ SPDLOG_ERROR("无法应用硬件参数: {}", snd_strerror(err));
|
|
|
+ goto snd_free;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ /* 释放硬件参数结构 */
|
|
|
+ snd_pcm_hw_params_free(hw_params);
|
|
|
+ /* 准备PCM设备 */
|
|
|
+ if ((err = snd_pcm_prepare(m_captureHandle)) < 0) {
|
|
|
+ SPDLOG_ERROR("无法准备PCM设备: {}", snd_strerror(err));
|
|
|
+ snd_pcm_close(m_captureHandle);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+
|
|
|
+snd_free:
|
|
|
+ snd_pcm_hw_params_free(hw_params);
|
|
|
+ snd_pcm_close(m_captureHandle);
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief 读取录音大小
|
|
|
+ *
|
|
|
+ * @param buffer 数据缓冲区指针
|
|
|
+ * @param bufferSize 缓冲区大小,缓冲区大小需要大于等于 recordFrames * 单个采样点的大小
|
|
|
+ * 例如:44100帧 * 2通道 * 2字节
|
|
|
+ * @param recordFrames 需要读取的帧数,一帧就是一个采样点,16bit / 2 * 2 通道 = 4字节
|
|
|
+ * 44100帧就是1秒
|
|
|
+ * @return uint32_t 读取到的字节数
|
|
|
+ */
|
|
|
+int AudioRecord::recordAudio(char* buffer, uint32_t bufferSize, uint32_t recordFrames)
|
|
|
+{
|
|
|
+ if(m_captureHandle == nullptr)
|
|
|
+ {
|
|
|
+ SPDLOG_WARN("PCM设备未打开,请先调用 openAudioChannel 打开设备");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ if (buffer == nullptr || bufferSize == 0)
|
|
|
+ {
|
|
|
+ SPDLOG_WARN("缓冲区指针为空或缓冲区大小为0");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ if(recordFrames * m_oneSampleSize > bufferSize)
|
|
|
+ {
|
|
|
+ SPDLOG_ERROR("缓冲区大小不足,无法存储 {} 帧数据", recordFrames);
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ auto readFrames = snd_pcm_readi(m_captureHandle, buffer, recordFrames);
|
|
|
+ if (readFrames < 0)
|
|
|
+ {
|
|
|
+ SPDLOG_ERROR("获取录音数据失败: {}", snd_strerror(readFrames));
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ return readFrames * m_oneSampleSize; // 返回读取到的字节数
|
|
|
+}
|
|
|
+
|
|
|
+
|