whisper.rn

repository·main·Indexed 21 days ago

https://github.com/mybigday/whisper.rn

React Native bindings for high-performance speech recognition using whisper.cpp. It supports OpenAI's Whisper and NVIDIA's Parakeet models, featuring Voice Activity Detection (VAD) via Silero, real-time transcription through the RealtimeTranscriber, and Core ML support for iOS. The library provides WhisperContext and ParakeetContext classes for managing model lifecycles, transcription of audio files or raw PCM data, and resource release.

Tokens
23.4K
Snippets
70
Records
111
Agent score
73%

What's inside whisper.rn

  1. Configure RealtimeTranscriber dependencies

    main

    To use the RealtimeTranscriber, you must provide a RealtimeTranscriberDependencies object. This object requires an audioStream (implementing AudioStreamInterface) and exactly one transcription context: either whisperContext or parakeetContext.

    Optional dependencies include:

    • fs: An implementation of WavFileWriterFs for saving audio.
    • vadContext: A RealtimeVadContextLike for Voice Activity Detection.
    // Example dependency structure
    const dependencies: RealtimeTranscriberDependencies = {
      audioStream: myAudioStream,
      whisperContext: myWhisperContext, // Use either whisperContext OR parakeetContext
      // fs: myFsImplementation, (optional)
      // vadContext: myVadContext, (optional)
    };
  2. Use the RingBuffer class for audio pre-recording

    main

    The RingBuffer is a fixed-size circular buffer designed for managing audio data in real-time transcription scenarios. It is particularly useful for pre-recording audio, allowing you to maintain a fixed memory footprint while keeping only the most recent $N$ seconds of audio before speech is detected.

    Key characteristics:

    • Fixed Memory: Allocates a set amount of memory upfront and does not grow unbounded.
    • O(1) Writes: Writing data is highly efficient.
    • Overwrite Behavior: When the buffer reaches its capacity, the oldest data is automatically overwritten by new incoming data, ensuring you always have the most recent audio available.
    // Example: Creating a buffer for 5 seconds of audio (assuming 16kHz, 16-bit mono)
    // 16000 samples/sec * 2 bytes/sample * 5 seconds = 160,000 bytes
    const ringBuffer = new RingBuffer(160000);
    
    // Writing data
    ringBuffer.write(audioChunk);
    
    // Reading the most recent data in order
    const audioToProcess = ringBuffer.read();
  3. How RealtimeTranscriber works

    main

    The RealtimeTranscriber is designed for continuous audio processing with built-in intelligence for speech detection. It manages the complexity of audio streaming through several key mechanisms:

    1. Automatic Slice Management: It breaks continuous audio into manageable 'slices' based on duration.
    2. VAD-based Slicing: It uses Voice Activity Detection (VAD) to detect speech. It can automatically trigger a slice finalization when speech ends or during periods of silence, ensuring transcription happens naturally as people speak.
    3. Queue-based Processing: Transcription tasks are handled via a queue, allowing the system to process slices sequentially without losing audio data.
    4. Memory Management: It includes mechanisms to manage the memory used by audio slices to prevent leaks during long transcription sessions.
  4. Control transcription flow with onBeginTranscribe and onBeginVad

    main

    You can use onBeginTranscribe and onBeginVad to programmatically decide whether a specific audio slice should be processed. Both callbacks return a Promise<boolean>.

    • Returning true allows the engine to proceed with transcription or VAD for that slice.
    • Returning false prevents the engine from processing that slice.

    onBeginTranscribe provides sliceInfo containing audioData (Uint8Array), duration, sliceIndex, and an optional vadEvent.

  5. Manage transcription slice lifecycle

    main

    When performing realtime transcription, you must manually advance the transcription pointer through the managed slices using these methods:

    • getSliceForTranscription(): Returns the next available AudioSlice that is ready to be transcribed.
    • moveToNextTranscribeSlice(): Advances the internal pointer to the next slice in the queue.
    • markSliceAsProcessed(sliceIndex): Marks a specific slice index as completed so it can be cleaned up or ignored.
    • reset(): Clears all slices and resets all indices to their initial state.
  6. Use model and audio assets in Metro

    main

    To use .bin (models) or .mil (CoreML) files from your project assets, you must update your metro.config.js to include these extensions in the assetExts array.

    Warning: Bundling large models will significantly increase your app size. The React Native packager has a 2GB limit; for the large model (2.9GB), use quantized models instead.

    // metro.config.js
    const defaultAssetExts = require('metro-config/src/defaults/defaults').assetExts
    
    module.exports = {
      resolver: {
        assetExts: [
          ...defaultAssetExts,
          'bin', // whisper.rn: ggml model binary
          'mil', // whisper.rn: CoreML model asset
        ],
      },
    }

    Usage Example:

    const whisperContext = await initWhisper({
      filePath: require('../assets/ggml-tiny.en.bin'),
    })
    
    const { stop, promise } = whisperContext.transcribe(
      require('../assets/sample.wav'),
      options,
    )
    const defaultAssetExts = require('metro-config/src/defaults/defaults').assetExts
    
    module.exports = {
      resolver: {
        assetExts: [
          ...defaultAssetExts,
          'bin',
          'mil',
        ],
      },
    }
  7. Use quantized models to reduce memory and disk usage

    main

    Using quantized models can significantly decrease both memory footprint and disk space requirements, though this may result in a slight reduction in transcription accuracy.

    Tip: In Android testing (specifically on devices with Qualcomm or Google SoCs), the q8 quantized model has demonstrated performance improvements.

  8. Perform Realtime Transcription with RealtimeTranscriber

    main

    The RealtimeTranscriber provides enhanced realtime transcription using VAD, auto-slicing, and memory management. It requires an audio stream adapter and a filesystem module.

    Dependencies:

    • @fugood/react-native-audio-pcm-stream (for AudioPcmStreamAdapter)
    • A compatible filesystem module (e.g., react-native-fs)
    import { RealtimeTranscriber } from 'whisper.rn/realtime-transcription'
    import { AudioPcmStreamAdapter } from 'whisper.rn/realtime-transcription/adapters'
    import RNFS from 'react-native-fs'
    
    const whisperContext = await initWhisper({ /* ... */ })
    const vadContext = await initWhisperVad({ /* ... */ })
    const audioStream = new AudioPcmStreamAdapter()
    
    const transcriber = new RealtimeTranscriber(
      { whisperContext, vadContext, audioStream, fs: RNFS },
      {
        audioSliceSec: 30,
        vadPreset: 'default',
        autoSliceOnSpeechEnd: true,
        transcribeOptions: { language: 'en' },
      },
      {
        onTranscribe: (event) => console.log('Transcription:', event.data?.result),
        onVad: (event) => console.log('VAD:', event.type, event.confidence),
        onStatusChange: (isActive) => console.log('Status:', isActive),
        onError: (error) => console.error('Error:', error),
      },
    )
    
    await transcriber.start()
    await transcriber.stop()

    Using Parakeet for Realtime: Provide parakeetContext instead of whisperContext. Note that initialPrompt and promptPreviousSlices are Whisper-only and will be ignored. Parakeet audio must be mono, 16 kHz, signed 16-bit PCM.

    import { RealtimeTranscriber } from 'whisper.rn/realtime-transcription'
    import { AudioPcmStreamAdapter } from 'whisper.rn/realtime-transcription/adapters'
    import RNFS from 'react-native-fs'
    
    const transcriber = new RealtimeTranscriber(
      { whisperContext, vadContext, audioStream, fs: RNFS },
      {
        audioSliceSec: 30,
        vadPreset: 'default',
        autoSliceOnSpeechEnd: true,
        transcribeOptions: { language: 'en' },
      },
      {
        onTranscribe: (event) => console.log('Transcription:', event.data?.result),
        onVad: (event) => console.log('VAD:', event.type, event.confidence),
        onStatusChange: (isActive) => console.log('Status:', isActive),
        onError: (error) => console.error('Error:', error),
      },
    )
    
    await transcriber.start()
    await transcriber.stop()
  9. Enable Core ML support on iOS

    main

    To use Core ML on iOS (iOS 15.0+, tvOS 15.0+), you must provide both the .ggml model file and the corresponding .mlmodelc Core ML model files. The .mlmodelc is a directory containing specific files required for the model to run.

    Important Notes:

    • The .ggml model is still required as a fallback for the decoder or encoder.
    • The .mlmodelc directory typically contains 3 required files: model.mil, coremldata.bin, and weights/weight.bin.
    • Core ML models can be downloaded from Hugging Face.
    • If downloading at runtime, you must unzip the archive to access the .mlmodelc directory (e.g., using react-native-zip-archive).
    • To avoid bloating the Android bundle, it is recommended to move Core ML asset configurations into a platform-specific file like context-opts.ios.js.
    const whisperContext = await initWhisper({
      filePath: require('../assets/ggml-tiny.en.bin'),
      coreMLModelAsset: Platform.OS === 'ios' ? {
        filename: 'ggml-tiny.en-encoder.mlmodelc',
        assets: [
          require('../assets/ggml-tiny.en-encoder.mlmodelc/weights/weight.bin'),
          require('../assets/ggml-tiny.en-encoder.mlmodelc/model.mil'),
          require('../assets/ggml-tiny.en-encoder.mlmodelc/coremldata.bin'),
        ],
      } : undefined,
    })
  10. Choose an appropriate Whisper model type

    main
    To balance inference quality and device performance, select a model based on the target device's hardware capabilities and available memory. You can use libraries like react-native-device-info to detect device specifications and tailor your model selection accordingly. For detailed memory requirements of different model sizes, refer to the whisper.cpp memory usage documentation.
  11. Install whisper.rn

    main

    Install the package using npm:

    npm install whisper.rn

    iOS Setup

    • Run npx pod-install after installation.
    • By default, it uses a pre-built rnwhisper.xcframework. To build from source, set RNWHISPER_BUILD_FROM_SOURCE=1 in your Podfile.
    • For medium or large models, it is recommended to enable the Extended Virtual Addressing capability in your iOS project.

    Android Setup

    • If Proguard is enabled, add the following rule to android/app/proguard-rules.pro:
    # whisper.rn
    -keep class com.rnwhisper.** { *; }
    • It is recommended to use ndkVersion = "24.0.8215888" (or above) in your root project build configuration, especially for Apple Silicon Macs.

    Expo Setup

    • You must prebuild the project before using this library.
  12. Handle peerDependencies in Metro configuration

    main

    When integrating whisper.rn into a project, you may need to prevent multiple versions of peer dependencies from being loaded by Metro. This is achieved by:

    1. Adding the peer dependency paths to the resolver.blockList to block them at the project root.
    2. Adding the same dependencies to resolver.extraNodeModules to alias them to the versions located in the local node_modules.

    This pattern ensures that only one version of a dependency is loaded, preventing runtime conflicts.

    // Example pattern for resolving peer dependencies
    const modules = Object.keys({ ...pak.peerDependencies });
    
    const config = {
      resolver: {
        blockList: metroExclusionList(
          modules.map(
            (m) => new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`),
          ),
        ),
        extraNodeModules: modules.reduce((acc, name) => {
          acc[name] = path.join(__dirname, 'node_modules', name);
          return acc;
        }, {}),
      },
    };