Building reliable wake word detection in C presents unique challenges for developers working on voice-activated applications. Whether you're developing for Linux, Windows, macOS, or Raspberry Pi, your application needs to recognize custom wake words—like "Alexa," "Hey Siri," or your own branded keywords—with minimal latency and maximum reliability.
Many developers initially turn to cloud-based keyword spotting APIs, but these solutions introduce significant drawbacks. Sending audio streams to remote servers introduces network latency, raises privacy concerns with sensitive voice data, and can fail entirely when internet connectivity is unreliable or unavailable. For production voice applications, especially those in IoT devices, embedded systems, or privacy-sensitive environments, on-device wake word detection is essential.
This tutorial demonstrates how to implement cross-platform wake word detection in C using Porcupine Wake Word, a lightweight on-device keyword detection engine. You'll learn how to integrate real-time audio capture, process audio frames efficiently, and trigger custom callbacks when wake words are detected—all without requiring cloud connectivity.
By the end of this tutorial, you'll have a working C application that performs low-latency wake word detection across Linux, Windows, macOS, and Raspberry Pi.
Important: This tutorial builds on the steps in How to Record Audio in C. Follow that tutorial to set up audio capture first if you haven't done so already.
Prerequisites
- C99-compatible compiler
- Windows: MinGW
Supported Platforms
- Linux (x86_64)
- macOS (x86_64, arm64)
- Windows (x86_64, arm64)
- Raspberry Pi (Zero, 3, 4, 5)
Project Setup
The tutorial will use the following directory structure:
For instructions on setting up audio capture (with pvrecorder), see: How to Record Audio in C
Step 1: Add Porcupine library files
- Create a folder named
porcupine/. - Download the Porcupine header files from GitHub and place them in:
- Download the Porcupine model file for your desired language and place it in:
Dynamic Loading Infrastructure
Porcupine Wake Word ships as a shared library (.so, .dylib, .dll). Instead of linking at compile time, we'll load the library at runtime.
We'll build helpers to:
- open a shared library
- fetch function pointers
- close it gracefully
These helpers remain identical whether you're using PvRecorder, Cheetah Streaming Speech-to-Text, Porcupine Wake Word, Rhino Speech-to-Intent, or other Picovoice engines.
Step 2: Platform-specific headers
Explaining the headers
- On Windows systems,
windows.hprovides theLoadLibraryfunction to load a shared library andGetProcAddressto retrieve individual function pointers. - On Unix-based systems,
dlopenanddlsymfrom thedlfcn.hheader provide the same functionality. - Lastly,
signal.hallows us to handleCtrl-Clater in this example.
Step 3. Define dynamic loading helper functions
3a. Open the shared library
3b. Load function symbols
3c. Close the library
3d. Print platform-correct errors
Implement Wake Word Detection
Now that loading infrastructure is in place, it's time to initialize Porcupine Wake Word, start capturing audio, and pass frames into the engine.
Step 4: Load the Porcupine library
Downloaded the correct library file for your OS and point library_path to the file.
Step 5. Initialize Porcupine
- Sign up for an account on Picovoice Console for free and obtain your
AccessKey - Replace
${ACCESS_KEY}with yourAccessKey - Download a keyword file and point
keyword_pathto the file.
You can also train your own custom keyword (like "Hey Jarvis" or "Start Engine") instead of using one of the default keywords.
Call pv_porcupine_init to create a Porcupine instance:
Refer to pv_porcupine_init for detailed explanation of parameters.
Step 6: Start keyword detection
Porcupine expects int16 PCM frames of a specific size. Retrieve that size and start listening for the keyword:
pv_porcupine_process_func processes a frame of the incoming audio stream and emits the detection result.
Incoming audio needs to have pv_porcupine_frame_length samples per frame. If you are not using PvRecorder for audio streaming, ensure the sample rate is equal to pv_sample_rate, 16-bit linearly-encoded, and single-channel.
Step 7: Cleanup
When done, delete Porcupine to free memory and close the library:
Complete Example: On-device Wake Word Detection in C
Here is the complete porcupine_tutorial.c you can copy, build, and run, complete with proper error handling and PvRecorder implementation:
- Replace
${ACCESS_KEY}with yourAccessKeyfrom Picovoice Console - update
model_pathto point to thePorcupinemodel file (.pv) - update
library_pathto point to the correctPorcupinelibrary for your OS - update
keyword_paths: to point to your chosen keyword file (.ppn) - update
pv_recorder_library_pathto point to the correctPvRecorderlibrary for your OS
This is a simplified example but includes all the necessary components to get started. Check out the Porcupine C demo on GitHub for a complete demo application.
Build & Run
Build and run the application:
Linux (gcc) and Raspberry Pi (gcc)
macOS (clang)
Windows (MinGW)
Troubleshooting Common Issues
1. Wake Word Never Triggers
Verify that your audio is coming from the intended microphone. If you're using PvRecorder for audio capture, validate that it's working properly first.
Tips:
- Confirm the microphone is not muted.
- Make sure your application is reading audio frames at the exact size returned by pv_porcupine_frame_length.
- Check that your sample rate and PCM format match the engine's requirements (pv_sample_rate, single-channel).
2. False Rejections or Missed Detections
If Porcupine rarely detects the keyword or fails in noisy environments, the sensitivity settings may be too low.
Solution:
Increase the sensitivity value (range 0.0–1.0) when initializing the engine. Higher sensitivity leads to more detections in noisy conditions but may slightly increase false alarms.
3. Too Many False Alarms
If wake word detection triggers too often or reacts to background speech, sensitivity may be set too high, or the keyword file may not be appropriate for the target environment.
Solution:
- Lower the sensitivity during pv_porcupine_init.
- Train a custom keyword designed for your specific wake phrase.
4. Porcupine Fails to Initialize
Initialization errors usually indicate mismatched files or platform issues. Common causes include using the wrong library, model, or keyword file for your system.
Solution:
- Download the correct binaries for your OS and architecture from the Porcupine repository.
- Match the keyword file (
.ppn) and model file (.pv) to the same language. - Ensure the keyword file and shared library (
.so,.dylib,.dll) are compatible with your target platform.
Example (English "bumblebee" keyword on Linux x86_64):
- Keyword file: bumblebee_linux.ppn
- Model file: porcupine_params.pv
- Library file: libpv_porcupine.so







