Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions examples/stream/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ It's best to tune it to the specific use case, but a value around `0.6` should b
When silence is detected, it will transcribe the last `--length` milliseconds of audio and output
a transcription block that is suitable for parsing.

## Interactive start/stop

By default `whisper-stream` transcribes continuously from launch. Pass `-i` (`--interactive`) to
control capture from the keyboard instead: the tool starts paused, **SPACE** starts/stops recording,
and **Q** quits.

```bash
./build/bin/whisper-stream -m ./models/ggml-base.en.bin -i
```

This is handy for dictation / push-to-talk style usage where you only want to transcribe the audio
you explicitly record. It works in both the default and sliding-window (`--step 0`) modes, and only
takes effect when stdin is a terminal (piped/redirected input is unaffected).

## Building

The `whisper-stream` tool depends on SDL2 library to capture audio from the microphone. You can build it like this:
Expand Down
77 changes: 76 additions & 1 deletion examples/stream/stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,49 @@

#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <string>
#include <thread>
#include <vector>

// --- minimal cross-platform non-blocking keyboard, for interactive start/stop ---
#ifdef _WIN32
#include <conio.h>
static void kbd_enable() {}
static void kbd_restore() {}
static int kbd_get() { return _kbhit() ? _getch() : -1; }
#else
#include <termios.h>
#include <unistd.h>
#include <sys/select.h>
static struct termios g_kbd_old;
static bool g_kbd_saved = false;
static void kbd_restore() {
if (g_kbd_saved) { tcsetattr(STDIN_FILENO, TCSANOW, &g_kbd_old); g_kbd_saved = false; }
}
static void kbd_enable() {
if (!isatty(STDIN_FILENO) || g_kbd_saved) return;
tcgetattr(STDIN_FILENO, &g_kbd_old);
g_kbd_saved = true;
atexit(kbd_restore);
struct termios tio = g_kbd_old;
tio.c_lflag &= ~(ICANON | ECHO);
tio.c_cc[VMIN] = 0; tio.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &tio);
}
static int kbd_get() {
if (!isatty(STDIN_FILENO)) return -1;
fd_set fds; FD_ZERO(&fds); FD_SET(STDIN_FILENO, &fds);
struct timeval tv{0, 0};
if (select(STDIN_FILENO + 1, &fds, nullptr, nullptr, &tv) > 0) {
unsigned char c;
if (read(STDIN_FILENO, &c, 1) == 1) return c;
}
return -1;
}
#endif

// command-line parameters
struct whisper_params {
int32_t n_threads = std::min(4, (int32_t) std::thread::hardware_concurrency());
Expand All @@ -37,6 +75,7 @@ struct whisper_params {
bool save_audio = false; // save audio to wav file
bool use_gpu = true;
bool flash_attn = true;
bool interactive = false; // press SPACE to start/stop capture

std::string language = "en";
std::string model = "models/ggml-base.en.bin";
Expand Down Expand Up @@ -75,6 +114,7 @@ static bool whisper_params_parse(int argc, char ** argv, whisper_params & params
else if (arg == "-ng" || arg == "--no-gpu") { params.use_gpu = false; }
else if (arg == "-fa" || arg == "--flash-attn") { params.flash_attn = true; }
else if (arg == "-nfa" || arg == "--no-flash-attn") { params.flash_attn = false; }
else if (arg == "-i" || arg == "--interactive") { params.interactive = true; }

else {
fprintf(stderr, "error: unknown argument: %s\n", arg.c_str());
Expand Down Expand Up @@ -114,6 +154,7 @@ void whisper_print_usage(int /*argc*/, char ** argv, const whisper_params & para
fprintf(stderr, " -ng, --no-gpu [%-7s] disable GPU inference\n", params.use_gpu ? "false" : "true");
fprintf(stderr, " -fa, --flash-attn [%-7s] enable flash attention during inference\n", params.flash_attn ? "true" : "false");
fprintf(stderr, " -nfa, --no-flash-attn [%-7s] disable flash attention during inference\n", params.flash_attn ? "false" : "true");
fprintf(stderr, " -i, --interactive [%-7s] press SPACE to start/stop capture, Q to quit\n", params.interactive ? "true" : "false");
fprintf(stderr, "\n");
}

Expand Down Expand Up @@ -230,14 +271,47 @@ int main(int argc, char ** argv) {

wavWriter.open(filename, WHISPER_SAMPLE_RATE, 16, 1);
}
printf("[Start speaking]\n");
bool recording = true;
if (params.interactive) {
kbd_enable();
audio.pause(); // start paused; wait for the user to press SPACE
audio.clear();
recording = false;
printf("[Interactive mode: press SPACE to start/stop recording, Q to quit]\n");
} else {
printf("[Start speaking]\n");
}
fflush(stdout);

auto t_last = std::chrono::high_resolution_clock::now();
const auto t_start = t_last;

// main audio loop
while (is_running) {
// interactive start/stop via keyboard (SPACE toggles capture, Q quits)
if (params.interactive) {
const int key = kbd_get();
if (key == 'q' || key == 'Q') {
break;
}
if (key == ' ') {
recording = !recording;
if (recording) {
audio.clear();
audio.resume();
printf("\n[recording... press SPACE to stop]\n");
} else {
audio.pause();
printf("\n[stopped. press SPACE to record]\n");
}
fflush(stdout);
}
if (!recording) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
continue;
}
}

if (params.save_audio) {
wavWriter.write(pcmf32_new.data(), pcmf32_new.size());
}
Expand Down Expand Up @@ -429,6 +503,7 @@ int main(int argc, char ** argv) {
}

audio.pause();
kbd_restore();

whisper_print_timings(ctx);
whisper_free(ctx);
Expand Down