React Native实时音频处理:C++ Turbo Module与Worklet函数间数据传输类型及优化方案问询
Great question—real-time audio processing with React Native Turbo Modules can be tricky when it comes to efficient data transfer between JS and C++. Let's break down what's going wrong and the best fixes for your case.
Why Your Current Code Crashes
JSI (JavaScript Interface) doesn’t natively support mapping plain JavaScript arrays directly to std::array<float,128> as function parameters or return values. When you try to pass a JS array to your foo function, the runtime can’t properly convert the type, leading to invalid memory access and an immediate crash.
Fix 1: Use TypedArrays (Float32Array) for Safe, Efficient Data Transfer
JS's typed arrays (like Float32Array) are built for binary data and map cleanly to C++ memory buffers via JSI. Here’s how to adjust your code:
App.tsx (JS Side)
let output = outputData[ch]; // Skip converting to a plain Array—use the Float32Array directly const input = inputData[ch]; // Assume inputData is already Float32Array const result = TurboModule.foo(input); output.set(result);
NativeModule.cpp (C++ Side)
#include <jsi/jsi.h> #include <algorithm> #include <stdexcept> using namespace facebook; jsi::Value NativeSampleModule::foo(jsi::Runtime& rt, const jsi::Value& inputVal) { // Validate input is a Float32Array if (!inputVal.isObject()) { throw jsi::JSError(rt, "Input must be a Float32Array"); } jsi::Object inputObj = inputVal.asObject(rt); if (!inputObj.isArrayBuffer(rt)) { throw jsi::JSError(rt, "Input must be a Float32Array (backed by ArrayBuffer)"); } // Access the underlying binary data jsi::ArrayBuffer inputBuffer = inputObj.getArrayBuffer(rt); float* inputData = static_cast<float*>(inputBuffer.data(rt)); size_t elementCount = inputBuffer.size(rt) / sizeof(float); // Enforce your 128-element requirement if (elementCount != 128) { throw jsi::JSError(rt, "Input must contain exactly 128 float elements"); } // --- Your audio processing logic here --- // For example, copy input to output (replace with your actual processing) jsi::ArrayBuffer outputBuffer = rt.global() .getPropertyAsFunction(rt, "ArrayBuffer") .callAsConstructor(rt, elementCount * sizeof(float)) .asObject(rt) .getArrayBuffer(rt); float* outputData = static_cast<float*>(outputBuffer.data(rt)); std::copy(inputData, inputData + elementCount, outputData); // Wrap the ArrayBuffer back into a Float32Array for JS return jsi::Object::createFromArrayBuffer(rt, outputBuffer); }
Fix 2: Zero-Copy Optimization (For Real-Time Performance)
If you don’t need to return a new array (e.g., you can modify the input buffer directly), you can avoid copying entirely—critical for low-latency audio:
// Modify the input buffer in-place instead of creating a new one void NativeSampleModule::processInPlace(jsi::Runtime& rt, const jsi::Value& inputVal) { jsi::Object inputObj = inputVal.asObject(rt); jsi::ArrayBuffer inputBuffer = inputObj.getArrayBuffer(rt); float* data = static_cast<float*>(inputBuffer.data(rt)); size_t count = inputBuffer.size(rt) / sizeof(float); // Process the data directly (e.g., apply gain, filter) for (size_t i = 0; i < count; ++i) { data[i] *= 0.5f; // Example: reduce volume by 50% } }
On the JS side, just call the function and use the original array (since it’s modified in-place):
TurboModule.processInPlace(inputData[ch]); output.set(inputData[ch]);
What About "Pointer-like" References in JS?
JavaScript doesn’t support raw pointers, but ArrayBuffer gives you the next best thing: direct access to the underlying binary memory from C++. As long as you ensure the ArrayBuffer isn’t garbage-collected while C++ is using it (which won’t happen if the JS side keeps a reference to the Float32Array during processing), this is safe and efficient.
Key Takeaways
- Never use plain JS arrays for real-time audio data—typed arrays (
Float32Array) are the correct choice. - Use
ArrayBufferin JSI to directly access memory between JS and C++, minimizing copies. - For low-latency processing, modify buffers in-place instead of creating new ones.
内容的提问来源于stack exchange,提问作者Giles.Spreadborough

