examples : refactor in order to reuse code and reduce duplication (#482)
* examples : refactor common code into a library * examples : refactor common SDL code into a library * make : update Makefile to use common libs * common : fix MSVC M_PI .. * addon.node : link common libguided
parent
0336161b7d
commit
09d7d2b68e
File diff suppressed because one or more lines are too long
@ -0,0 +1,226 @@
|
|||||||
|
#include "common-sdl.h"
|
||||||
|
|
||||||
|
audio_async::audio_async(int len_ms) {
|
||||||
|
m_len_ms = len_ms;
|
||||||
|
|
||||||
|
m_running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_async::~audio_async() {
|
||||||
|
if (m_dev_id_in) {
|
||||||
|
SDL_CloseAudioDevice(m_dev_id_in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool audio_async::init(int capture_id, int sample_rate) {
|
||||||
|
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
|
||||||
|
|
||||||
|
if (SDL_Init(SDL_INIT_AUDIO) < 0) {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetHintWithPriority(SDL_HINT_AUDIO_RESAMPLING_MODE, "medium", SDL_HINT_OVERRIDE);
|
||||||
|
|
||||||
|
{
|
||||||
|
int nDevices = SDL_GetNumAudioDevices(SDL_TRUE);
|
||||||
|
fprintf(stderr, "%s: found %d capture devices:\n", __func__, nDevices);
|
||||||
|
for (int i = 0; i < nDevices; i++) {
|
||||||
|
fprintf(stderr, "%s: - Capture device #%d: '%s'\n", __func__, i, SDL_GetAudioDeviceName(i, SDL_TRUE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_AudioSpec capture_spec_requested;
|
||||||
|
SDL_AudioSpec capture_spec_obtained;
|
||||||
|
|
||||||
|
SDL_zero(capture_spec_requested);
|
||||||
|
SDL_zero(capture_spec_obtained);
|
||||||
|
|
||||||
|
capture_spec_requested.freq = sample_rate;
|
||||||
|
capture_spec_requested.format = AUDIO_F32;
|
||||||
|
capture_spec_requested.channels = 1;
|
||||||
|
capture_spec_requested.samples = 1024;
|
||||||
|
capture_spec_requested.callback = [](void * userdata, uint8_t * stream, int len) {
|
||||||
|
audio_async * audio = (audio_async *) userdata;
|
||||||
|
audio->callback(stream, len);
|
||||||
|
};
|
||||||
|
capture_spec_requested.userdata = this;
|
||||||
|
|
||||||
|
if (capture_id >= 0) {
|
||||||
|
fprintf(stderr, "%s: attempt to open capture device %d : '%s' ...\n", __func__, capture_id, SDL_GetAudioDeviceName(capture_id, SDL_TRUE));
|
||||||
|
m_dev_id_in = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(capture_id, SDL_TRUE), SDL_TRUE, &capture_spec_requested, &capture_spec_obtained, 0);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "%s: attempt to open default capture device ...\n", __func__);
|
||||||
|
m_dev_id_in = SDL_OpenAudioDevice(nullptr, SDL_TRUE, &capture_spec_requested, &capture_spec_obtained, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_dev_id_in) {
|
||||||
|
fprintf(stderr, "%s: couldn't open an audio device for capture: %s!\n", __func__, SDL_GetError());
|
||||||
|
m_dev_id_in = 0;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "%s: obtained spec for input device (SDL Id = %d):\n", __func__, m_dev_id_in);
|
||||||
|
fprintf(stderr, "%s: - sample rate: %d\n", __func__, capture_spec_obtained.freq);
|
||||||
|
fprintf(stderr, "%s: - format: %d (required: %d)\n", __func__, capture_spec_obtained.format,
|
||||||
|
capture_spec_requested.format);
|
||||||
|
fprintf(stderr, "%s: - channels: %d (required: %d)\n", __func__, capture_spec_obtained.channels,
|
||||||
|
capture_spec_requested.channels);
|
||||||
|
fprintf(stderr, "%s: - samples per frame: %d\n", __func__, capture_spec_obtained.samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_sample_rate = capture_spec_obtained.freq;
|
||||||
|
|
||||||
|
m_audio.resize((m_sample_rate*m_len_ms)/1000);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool audio_async::resume() {
|
||||||
|
if (!m_dev_id_in) {
|
||||||
|
fprintf(stderr, "%s: no audio device to resume!\n", __func__);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_running) {
|
||||||
|
fprintf(stderr, "%s: already running!\n", __func__);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_PauseAudioDevice(m_dev_id_in, 0);
|
||||||
|
|
||||||
|
m_running = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool audio_async::pause() {
|
||||||
|
if (!m_dev_id_in) {
|
||||||
|
fprintf(stderr, "%s: no audio device to pause!\n", __func__);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_running) {
|
||||||
|
fprintf(stderr, "%s: already paused!\n", __func__);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_PauseAudioDevice(m_dev_id_in, 1);
|
||||||
|
|
||||||
|
m_running = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool audio_async::clear() {
|
||||||
|
if (!m_dev_id_in) {
|
||||||
|
fprintf(stderr, "%s: no audio device to clear!\n", __func__);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_running) {
|
||||||
|
fprintf(stderr, "%s: not running!\n", __func__);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
m_audio_pos = 0;
|
||||||
|
m_audio_len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// callback to be called by SDL
|
||||||
|
void audio_async::callback(uint8_t * stream, int len) {
|
||||||
|
if (!m_running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t n_samples = len / sizeof(float);
|
||||||
|
|
||||||
|
m_audio_new.resize(n_samples);
|
||||||
|
memcpy(m_audio_new.data(), stream, n_samples * sizeof(float));
|
||||||
|
|
||||||
|
//fprintf(stderr, "%s: %zu samples, pos %zu, len %zu\n", __func__, n_samples, m_audio_pos, m_audio_len);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
if (m_audio_pos + n_samples > m_audio.size()) {
|
||||||
|
const size_t n0 = m_audio.size() - m_audio_pos;
|
||||||
|
|
||||||
|
memcpy(&m_audio[m_audio_pos], stream, n0 * sizeof(float));
|
||||||
|
memcpy(&m_audio[0], &stream[n0], (n_samples - n0) * sizeof(float));
|
||||||
|
|
||||||
|
m_audio_pos = (m_audio_pos + n_samples) % m_audio.size();
|
||||||
|
m_audio_len = m_audio.size();
|
||||||
|
} else {
|
||||||
|
memcpy(&m_audio[m_audio_pos], stream, n_samples * sizeof(float));
|
||||||
|
|
||||||
|
m_audio_pos = (m_audio_pos + n_samples) % m_audio.size();
|
||||||
|
m_audio_len = std::min(m_audio_len + n_samples, m_audio.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio_async::get(int ms, std::vector<float> & result) {
|
||||||
|
if (!m_dev_id_in) {
|
||||||
|
fprintf(stderr, "%s: no audio device to get audio from!\n", __func__);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_running) {
|
||||||
|
fprintf(stderr, "%s: not running!\n", __func__);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.clear();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
if (ms <= 0) {
|
||||||
|
ms = m_len_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t n_samples = (m_sample_rate * ms) / 1000;
|
||||||
|
if (n_samples > m_audio_len) {
|
||||||
|
n_samples = m_audio_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.resize(n_samples);
|
||||||
|
|
||||||
|
int s0 = m_audio_pos - n_samples;
|
||||||
|
if (s0 < 0) {
|
||||||
|
s0 += m_audio.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s0 + n_samples > m_audio.size()) {
|
||||||
|
const size_t n0 = m_audio.size() - s0;
|
||||||
|
|
||||||
|
memcpy(result.data(), &m_audio[s0], n0 * sizeof(float));
|
||||||
|
memcpy(&result[n0], &m_audio[0], (n_samples - n0) * sizeof(float));
|
||||||
|
} else {
|
||||||
|
memcpy(result.data(), &m_audio[s0], n_samples * sizeof(float));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sdl_poll_events() {
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
switch (event.type) {
|
||||||
|
case SDL_QUIT:
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <SDL_audio.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
//
|
||||||
|
// SDL Audio capture
|
||||||
|
//
|
||||||
|
|
||||||
|
class audio_async {
|
||||||
|
public:
|
||||||
|
audio_async(int len_ms);
|
||||||
|
~audio_async();
|
||||||
|
|
||||||
|
bool init(int capture_id, int sample_rate);
|
||||||
|
|
||||||
|
// start capturing audio via the provided SDL callback
|
||||||
|
// keep last len_ms seconds of audio in a circular buffer
|
||||||
|
bool resume();
|
||||||
|
bool pause();
|
||||||
|
bool clear();
|
||||||
|
|
||||||
|
// callback to be called by SDL
|
||||||
|
void callback(uint8_t * stream, int len);
|
||||||
|
|
||||||
|
// get audio data from the circular buffer
|
||||||
|
void get(int ms, std::vector<float> & audio);
|
||||||
|
|
||||||
|
private:
|
||||||
|
SDL_AudioDeviceID m_dev_id_in = 0;
|
||||||
|
|
||||||
|
int m_len_ms = 0;
|
||||||
|
int m_sample_rate = 0;
|
||||||
|
|
||||||
|
std::atomic_bool m_running;
|
||||||
|
std::mutex m_mutex;
|
||||||
|
|
||||||
|
std::vector<float> m_audio;
|
||||||
|
std::vector<float> m_audio_new;
|
||||||
|
size_t m_audio_pos = 0;
|
||||||
|
size_t m_audio_len = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return false if need to quit
|
||||||
|
bool sdl_poll_events();
|
@ -0,0 +1,162 @@
|
|||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
// third-party utilities
|
||||||
|
// use your favorite implementations
|
||||||
|
#define DR_WAV_IMPLEMENTATION
|
||||||
|
#include "dr_wav.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
|
#ifndef M_PI
|
||||||
|
#define M_PI 3.14159265358979323846
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::string trim(const std::string & s) {
|
||||||
|
std::regex e("^\\s+|\\s+$");
|
||||||
|
return std::regex_replace(s, e, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string replace(const std::string & s, const std::string & from, const std::string & to) {
|
||||||
|
std::string result = s;
|
||||||
|
size_t pos = 0;
|
||||||
|
while ((pos = result.find(from, pos)) != std::string::npos) {
|
||||||
|
result.replace(pos, from.length(), to);
|
||||||
|
pos += to.length();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool read_wav(const std::string & fname, std::vector<float>& pcmf32, std::vector<std::vector<float>>& pcmf32s, bool stereo) {
|
||||||
|
drwav wav;
|
||||||
|
std::vector<uint8_t> wav_data; // used for pipe input from stdin
|
||||||
|
|
||||||
|
if (fname == "-") {
|
||||||
|
{
|
||||||
|
uint8_t buf[1024];
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
const size_t n = fread(buf, 1, sizeof(buf), stdin);
|
||||||
|
if (n == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
wav_data.insert(wav_data.end(), buf, buf + n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drwav_init_memory(&wav, wav_data.data(), wav_data.size(), nullptr) == false) {
|
||||||
|
fprintf(stderr, "error: failed to open WAV file from stdin\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "%s: read %zu bytes from stdin\n", __func__, wav_data.size());
|
||||||
|
}
|
||||||
|
else if (drwav_init_file(&wav, fname.c_str(), nullptr) == false) {
|
||||||
|
fprintf(stderr, "error: failed to open '%s' as WAV file\n", fname.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wav.channels != 1 && wav.channels != 2) {
|
||||||
|
fprintf(stderr, "%s: WAV file '%s' must be mono or stereo\n", __func__, fname.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stereo && wav.channels != 2) {
|
||||||
|
fprintf(stderr, "%s: WAV file '%s' must be stereo for diarization\n", __func__, fname.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wav.sampleRate != COMMON_SAMPLE_RATE) {
|
||||||
|
fprintf(stderr, "%s: WAV file '%s' must be %i kHz\n", __func__, fname.c_str(), COMMON_SAMPLE_RATE/1000);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wav.bitsPerSample != 16) {
|
||||||
|
fprintf(stderr, "%s: WAV file '%s' must be 16-bit\n", __func__, fname.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64_t n = wav_data.empty() ? wav.totalPCMFrameCount : wav_data.size()/(wav.channels*wav.bitsPerSample/8);
|
||||||
|
|
||||||
|
std::vector<int16_t> pcm16;
|
||||||
|
pcm16.resize(n*wav.channels);
|
||||||
|
drwav_read_pcm_frames_s16(&wav, n, pcm16.data());
|
||||||
|
drwav_uninit(&wav);
|
||||||
|
|
||||||
|
// convert to mono, float
|
||||||
|
pcmf32.resize(n);
|
||||||
|
if (wav.channels == 1) {
|
||||||
|
for (uint64_t i = 0; i < n; i++) {
|
||||||
|
pcmf32[i] = float(pcm16[i])/32768.0f;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (uint64_t i = 0; i < n; i++) {
|
||||||
|
pcmf32[i] = float(pcm16[2*i] + pcm16[2*i + 1])/65536.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stereo) {
|
||||||
|
// convert to stereo, float
|
||||||
|
pcmf32s.resize(2);
|
||||||
|
|
||||||
|
pcmf32s[0].resize(n);
|
||||||
|
pcmf32s[1].resize(n);
|
||||||
|
for (uint64_t i = 0; i < n; i++) {
|
||||||
|
pcmf32s[0][i] = float(pcm16[2*i])/32768.0f;
|
||||||
|
pcmf32s[1][i] = float(pcm16[2*i + 1])/32768.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void high_pass_filter(std::vector<float> & data, float cutoff, float sample_rate) {
|
||||||
|
const float rc = 1.0f / (2.0f * M_PI * cutoff);
|
||||||
|
const float dt = 1.0f / sample_rate;
|
||||||
|
const float alpha = dt / (rc + dt);
|
||||||
|
|
||||||
|
float y = data[0];
|
||||||
|
|
||||||
|
for (size_t i = 1; i < data.size(); i++) {
|
||||||
|
y = alpha * (y + data[i] - data[i - 1]);
|
||||||
|
data[i] = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool vad_simple(std::vector<float> & pcmf32, int sample_rate, int last_ms, float vad_thold, float freq_thold, bool verbose) {
|
||||||
|
const int n_samples = pcmf32.size();
|
||||||
|
const int n_samples_last = (sample_rate * last_ms) / 1000;
|
||||||
|
|
||||||
|
if (n_samples_last >= n_samples) {
|
||||||
|
// not enough samples - assume no speech
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (freq_thold > 0.0f) {
|
||||||
|
high_pass_filter(pcmf32, freq_thold, sample_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
float energy_all = 0.0f;
|
||||||
|
float energy_last = 0.0f;
|
||||||
|
|
||||||
|
for (int i = 0; i < n_samples; i++) {
|
||||||
|
energy_all += fabsf(pcmf32[i]);
|
||||||
|
|
||||||
|
if (i >= n_samples - n_samples_last) {
|
||||||
|
energy_last += fabsf(pcmf32[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
energy_all /= n_samples;
|
||||||
|
energy_last /= n_samples_last;
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
fprintf(stderr, "%s: energy_all: %f, energy_last: %f, vad_thold: %f, freq_thold: %f\n", __func__, energy_all, energy_last, vad_thold, freq_thold);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (energy_last > vad_thold*energy_all) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// needs to match WHISPER_SAMPLE_RATE
|
||||||
|
#define COMMON_SAMPLE_RATE 16000
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
std::string trim(const std::string & s);
|
||||||
|
|
||||||
|
std::string replace(
|
||||||
|
const std::string & s,
|
||||||
|
const std::string & from,
|
||||||
|
const std::string & to);
|
||||||
|
|
||||||
|
// Read WAV audio file and store the PCM data into pcmf32
|
||||||
|
// The sample rate of the audio must be equal to COMMON_SAMPLE_RATE
|
||||||
|
// If stereo flag is set and the audio has 2 channels, the pcmf32s will contain 2 channel PCM
|
||||||
|
bool read_wav(
|
||||||
|
const std::string & fname,
|
||||||
|
std::vector<float> & pcmf32,
|
||||||
|
std::vector<std::vector<float>> & pcmf32s,
|
||||||
|
bool stereo);
|
||||||
|
|
||||||
|
// Apply a high-pass frequency filter to PCM audio
|
||||||
|
// Suppresses frequencies below cutoff Hz
|
||||||
|
void high_pass_filter(
|
||||||
|
std::vector<float> & data,
|
||||||
|
float cutoff,
|
||||||
|
float sample_rate);
|
||||||
|
|
||||||
|
// Basic voice activity detection (VAD) using audio energy adaptive threshold
|
||||||
|
bool vad_simple(
|
||||||
|
std::vector<float> & pcmf32,
|
||||||
|
int sample_rate,
|
||||||
|
int last_ms,
|
||||||
|
float vad_thold,
|
||||||
|
float freq_thold,
|
||||||
|
bool verbose);
|
||||||
|
|
Loading…
Reference in new issue