
Near‑ultrasound acoustic protocol enabling secure proximity data exchange via FSK modulation, DSP pipeline (Liquid DSP), forward error correction and optional AES encryption; reactive transmit/receive API and debugging tools.
Whisper
Whisper is a communication protocol designed for decentralized and secure data exchange using near-ultrasound acoustic waves. It enables devices to communicate in close proximity without relying on traditional wireless technologies such as Wi-Fi, Bluetooth, or cellular networks.
Whisper provides flexible integration options for various platforms. Choose the one that fits your project best.
Add the dependency to your Android app's build.gradle file:
dependencies {
implementation("io.github.shadadman:whisper-android:0.90.0")
}Alternatively, you can build the Android AAR directly from the source.
Clone the repository:
git clone https://github.com/ShadAdman/Whisper.git
cd Whisper
Run the Gradle task:
./gradlew whisper:assembleRelease
The generated AAR can be found at:
whisper/build/outputs/aar/
To integrate Whisper into your iOS project using Swift Package Manager, add the following repository URL in Xcode:
https://github.com/ShadAdman/Whisper
For desktop or jvm applications, add the JVM dependency:
dependencies {
implementation("io.github.shadadman:whisper-jvm:0.90.0")
}Alternatively, you can include the standalone JAR file in your project's libs directory.
You can also build the JVM JAR directly from the source.
Clone the repository:
git clone https://github.com/ShadAdman/Whisper.git
cd Whisper
Run the Gradle task:
./gradlew :whisper:desktopJar
The generated JAR can be found at:
whisper/build/libs/
For native applications or embedded, you can build Whisper directly from the source repository and generate the required native headers and shared/static libraries.
[!IMPORTANT] Linux Build Requirement: To build for Linux targets, you must have the ALSA and OpenSSL development headers installed on your system. On Ubuntu/Debian, run:
sudo apt-get install libasound2-dev libssl-dev
Clone the repository:
git clone https://github.com/ShadAdman/Whisper.git
cd Whisper
To generate shared and static binaries use:
./gradlew :whisper:linkReleaseSharedLinuxX64
or:
./gradlew :whisper:linkReleaseStaticLinuxX64
The generated native binaries are placed under the:
whisper/build/bin/
If you are building a Kotlin Multiplatform project, add the dependency to your commonMain source set:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.shadadman:whisper:0.90.0")
}
}
}To effectively use Whisper, it is helpful to understand the underlying principles that make acoustic data transfer possible.
Acoustic communication uses sound waves to transmit information. Whisper operates in the near-ultrasound frequency range, typically between 18 kHz and 22 kHz. These frequencies are generally inaudible to most adult humans but can be captured by standard device microphones and reproduced by speakers.
Whisper uses Frequency Shift Keying (FSK) to represent data. In FSK, different frequencies are assigned to represent specific bit values. For example, one frequency might represent a binary 0, while another represents a binary 1. By switching between these frequencies over time, the protocol can encode a stream of data into a sound signal.
Digital Signal Processing (DSP) is used to clean and prepare the audio signal before it is analyzed. Whisper leverages Liquid DSP, a comprehensive and highly optimized software-defined radio (SDR) library. This allows us to employ a sophisticated pipeline that includes:
By using Liquid DSP as our engine, Whisper gains access to advanced modem designs, robust synchronization algorithms, and efficient filtering techniques that would be impractical to implement from scratch. This architectural choice ensures that Whisper is built on a battle-tested foundation, ready for future enhancements like higher-order modulations (PSK, QAM) or advanced channel equalization.
Sound is an unstable medium for data transfer due to background noise and physical obstructions. Whisper includes Forward Error Correction to improve reliability. By sending redundant information along with the actual data, the receiving device can reconstruct the original message even if some parts of the acoustic signal were corrupted or lost.
The Whisper API is built using Kotlin Coroutines and Flows, providing a reactive way to handle data transmission and reception.
The Whisper object serves as the primary entry point for the library. You can customize its behavior using WhisperConfig.
val config = WhisperConfig(
sampleRate = 48000,
carrierFrequency = 19000f,
fecConfig = FecConfig(enabled = true, redundancy = 2),
encryptionKey = "your-16-byte-key".encodeToByteArray(),
encryptor = AesEncryptor()
)
Whisper.configure(config)The WhisperConfig class accepts the following parameters:
FecConfig class:
ByteArray representing the secret key for encryption. When provided, Whisper will automatically encrypt/decrypt all transmitted and received data payloads.WhisperEncryptor implementation. By default, it uses SimpleXorEncryptor. For production, AesEncryptor() is recommended as it uses platform-native hardware acceleration for secure communication.To start listening for incoming signals, call startListening(). This initializes the audio engine and begins processing the microphone input. You can then collect data from the receivedData flow.
// Start the microphone and detection engine
Whisper.startListening()
// Observe incoming data
scope.launch {
Whisper.receivedData.collect { data ->
val message = data.decodeToString()
println("Received: $message")
}
}
// Stop listening when finished
Whisper.stopListening()Transmission is handled by the transmit function. It takes a byte array, encodes it into an acoustic signal, and plays it through the device speaker.
val message = "Hello from Whisper".encodeToByteArray()
scope.launch {
Whisper.transmit(message)
}You can use the playTestTone() function to verify that the audio hardware is working correctly. This will emit a 2-second tone at the configured carrier frequency.
scope.launch {
Whisper.playTestTone()
}If you need lower-level information about the signal state, such as when a carrier frequency is detected, you can observe the carrierEvents flow.
scope.launch {
Whisper.carrierEvents.collect { event ->
when (event) {
is CarrierDetected -> println("Signal detected")
is CarrierLost -> println("Signal lost")
}
}
}The project includes a comprehensive sample application located in the sample directory. This application demonstrates the core capabilities of the Whisper library using Compose Multiplatform.
Features of the sample app include:
You can run the sample app on Android, iOS, or Desktop to test the protocol between multiple devices.
Whisper is built on a modern, high-performance stack:
While Liquid DSP is a comprehensive library, its inclusion provides Whisper with a significant "future-proof" advantage. It allows us to rapidly evolve the protocol—moving from simple FSK to more complex waveforms or adding sophisticated adaptive filtering—without changing our core engine.
Whisper isn't a replacement for Wi-Fi or Bluetooth; it's a specialized tool for specific environments where radio waves are impractical, unavailable, or insecure.
Whisper uses a bandpass filter to ignore frequencies outside the 18-22 kHz range. Most environmental noise (talking, music, traffic) is below 15 kHz. However, extremely loud metallic noises or specialized ultrasound jammers can cause interference. In these cases, increasing the FEC redundancy is recommended.
The effective range of Whisper is typically 1-5 meters depending on the speaker volume and microphone sensitivity. Sound follows the inverse square law, so signal strength drops rapidly with distance. If you need more range, you should lower the carrier frequency (closer to 17 kHz) or increase the transmission volume.
Whisper is optimized for low-bandwidth, high-reliability data like text, authentication tokens, or peer discovery info. Sending large files (megabytes) via sound is slow (approx. 100-500 bps). For large data, we recommend using Whisper to exchange Wi-Fi Direct or Bluetooth credentials, then switching to those high-speed channels.
Some hearing aids can amplify high-frequency sounds. While Whisper operates near the edge of human hearing, users with sensitive equipment might hear a very faint 'whistle' or 'static'. We recommend providing a toggle in your app to disable acoustic features for accessibility.
Multipath interference is a common challenge in acoustic communication. Whisper's FSK modem includes guard intervals between symbols to allow echoes to die down before the next bit is processed, ensuring the decoder doesn't get confused by reflected waves.
Pets such as dogs or cats can hear high-frequency sounds. While Whisper operates near the edge of human hearing, animals might hear a very faint 'whistle' or 'static'. We recommend providing a toggle in your app to disable acoustic features for accessibility.
Whisper provides built-in support for securing data payloads via the whisper-crypto module. By default, it supports:
AesEncryptor().WhisperEncryptor interface to use your own cryptographic algorithms.While Whisper provides these tools, it is still proximity-based and operates over acoustic waves. Users should be aware that encrypted acoustic signals can still be recorded by nearby microphones, even if they cannot be easily decrypted.
Whisper
Whisper is a communication protocol designed for decentralized and secure data exchange using near-ultrasound acoustic waves. It enables devices to communicate in close proximity without relying on traditional wireless technologies such as Wi-Fi, Bluetooth, or cellular networks.
Whisper provides flexible integration options for various platforms. Choose the one that fits your project best.
Add the dependency to your Android app's build.gradle file:
dependencies {
implementation("io.github.shadadman:whisper-android:0.90.0")
}Alternatively, you can build the Android AAR directly from the source.
Clone the repository:
git clone https://github.com/ShadAdman/Whisper.git
cd Whisper
Run the Gradle task:
./gradlew whisper:assembleRelease
The generated AAR can be found at:
whisper/build/outputs/aar/
To integrate Whisper into your iOS project using Swift Package Manager, add the following repository URL in Xcode:
https://github.com/ShadAdman/Whisper
For desktop or jvm applications, add the JVM dependency:
dependencies {
implementation("io.github.shadadman:whisper-jvm:0.90.0")
}Alternatively, you can include the standalone JAR file in your project's libs directory.
You can also build the JVM JAR directly from the source.
Clone the repository:
git clone https://github.com/ShadAdman/Whisper.git
cd Whisper
Run the Gradle task:
./gradlew :whisper:desktopJar
The generated JAR can be found at:
whisper/build/libs/
For native applications or embedded, you can build Whisper directly from the source repository and generate the required native headers and shared/static libraries.
[!IMPORTANT] Linux Build Requirement: To build for Linux targets, you must have the ALSA and OpenSSL development headers installed on your system. On Ubuntu/Debian, run:
sudo apt-get install libasound2-dev libssl-dev
Clone the repository:
git clone https://github.com/ShadAdman/Whisper.git
cd Whisper
To generate shared and static binaries use:
./gradlew :whisper:linkReleaseSharedLinuxX64
or:
./gradlew :whisper:linkReleaseStaticLinuxX64
The generated native binaries are placed under the:
whisper/build/bin/
If you are building a Kotlin Multiplatform project, add the dependency to your commonMain source set:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.shadadman:whisper:0.90.0")
}
}
}To effectively use Whisper, it is helpful to understand the underlying principles that make acoustic data transfer possible.
Acoustic communication uses sound waves to transmit information. Whisper operates in the near-ultrasound frequency range, typically between 18 kHz and 22 kHz. These frequencies are generally inaudible to most adult humans but can be captured by standard device microphones and reproduced by speakers.
Whisper uses Frequency Shift Keying (FSK) to represent data. In FSK, different frequencies are assigned to represent specific bit values. For example, one frequency might represent a binary 0, while another represents a binary 1. By switching between these frequencies over time, the protocol can encode a stream of data into a sound signal.
Digital Signal Processing (DSP) is used to clean and prepare the audio signal before it is analyzed. Whisper leverages Liquid DSP, a comprehensive and highly optimized software-defined radio (SDR) library. This allows us to employ a sophisticated pipeline that includes:
By using Liquid DSP as our engine, Whisper gains access to advanced modem designs, robust synchronization algorithms, and efficient filtering techniques that would be impractical to implement from scratch. This architectural choice ensures that Whisper is built on a battle-tested foundation, ready for future enhancements like higher-order modulations (PSK, QAM) or advanced channel equalization.
Sound is an unstable medium for data transfer due to background noise and physical obstructions. Whisper includes Forward Error Correction to improve reliability. By sending redundant information along with the actual data, the receiving device can reconstruct the original message even if some parts of the acoustic signal were corrupted or lost.
The Whisper API is built using Kotlin Coroutines and Flows, providing a reactive way to handle data transmission and reception.
The Whisper object serves as the primary entry point for the library. You can customize its behavior using WhisperConfig.
val config = WhisperConfig(
sampleRate = 48000,
carrierFrequency = 19000f,
fecConfig = FecConfig(enabled = true, redundancy = 2),
encryptionKey = "your-16-byte-key".encodeToByteArray(),
encryptor = AesEncryptor()
)
Whisper.configure(config)The WhisperConfig class accepts the following parameters:
FecConfig class:
ByteArray representing the secret key for encryption. When provided, Whisper will automatically encrypt/decrypt all transmitted and received data payloads.WhisperEncryptor implementation. By default, it uses SimpleXorEncryptor. For production, AesEncryptor() is recommended as it uses platform-native hardware acceleration for secure communication.To start listening for incoming signals, call startListening(). This initializes the audio engine and begins processing the microphone input. You can then collect data from the receivedData flow.
// Start the microphone and detection engine
Whisper.startListening()
// Observe incoming data
scope.launch {
Whisper.receivedData.collect { data ->
val message = data.decodeToString()
println("Received: $message")
}
}
// Stop listening when finished
Whisper.stopListening()Transmission is handled by the transmit function. It takes a byte array, encodes it into an acoustic signal, and plays it through the device speaker.
val message = "Hello from Whisper".encodeToByteArray()
scope.launch {
Whisper.transmit(message)
}You can use the playTestTone() function to verify that the audio hardware is working correctly. This will emit a 2-second tone at the configured carrier frequency.
scope.launch {
Whisper.playTestTone()
}If you need lower-level information about the signal state, such as when a carrier frequency is detected, you can observe the carrierEvents flow.
scope.launch {
Whisper.carrierEvents.collect { event ->
when (event) {
is CarrierDetected -> println("Signal detected")
is CarrierLost -> println("Signal lost")
}
}
}The project includes a comprehensive sample application located in the sample directory. This application demonstrates the core capabilities of the Whisper library using Compose Multiplatform.
Features of the sample app include:
You can run the sample app on Android, iOS, or Desktop to test the protocol between multiple devices.
Whisper is built on a modern, high-performance stack:
While Liquid DSP is a comprehensive library, its inclusion provides Whisper with a significant "future-proof" advantage. It allows us to rapidly evolve the protocol—moving from simple FSK to more complex waveforms or adding sophisticated adaptive filtering—without changing our core engine.
Whisper isn't a replacement for Wi-Fi or Bluetooth; it's a specialized tool for specific environments where radio waves are impractical, unavailable, or insecure.
Whisper uses a bandpass filter to ignore frequencies outside the 18-22 kHz range. Most environmental noise (talking, music, traffic) is below 15 kHz. However, extremely loud metallic noises or specialized ultrasound jammers can cause interference. In these cases, increasing the FEC redundancy is recommended.
The effective range of Whisper is typically 1-5 meters depending on the speaker volume and microphone sensitivity. Sound follows the inverse square law, so signal strength drops rapidly with distance. If you need more range, you should lower the carrier frequency (closer to 17 kHz) or increase the transmission volume.
Whisper is optimized for low-bandwidth, high-reliability data like text, authentication tokens, or peer discovery info. Sending large files (megabytes) via sound is slow (approx. 100-500 bps). For large data, we recommend using Whisper to exchange Wi-Fi Direct or Bluetooth credentials, then switching to those high-speed channels.
Some hearing aids can amplify high-frequency sounds. While Whisper operates near the edge of human hearing, users with sensitive equipment might hear a very faint 'whistle' or 'static'. We recommend providing a toggle in your app to disable acoustic features for accessibility.
Multipath interference is a common challenge in acoustic communication. Whisper's FSK modem includes guard intervals between symbols to allow echoes to die down before the next bit is processed, ensuring the decoder doesn't get confused by reflected waves.
Pets such as dogs or cats can hear high-frequency sounds. While Whisper operates near the edge of human hearing, animals might hear a very faint 'whistle' or 'static'. We recommend providing a toggle in your app to disable acoustic features for accessibility.
Whisper provides built-in support for securing data payloads via the whisper-crypto module. By default, it supports:
AesEncryptor().WhisperEncryptor interface to use your own cryptographic algorithms.While Whisper provides these tools, it is still proximity-based and operates over acoustic waves. Users should be aware that encrypted acoustic signals can still be recorded by nearby microphones, even if they cannot be easily decrypted.