0

这个问题是关于在 nativescript 中运行一个非阻塞、高性能的活动,这是通过原生 Android API 直接访问硬件从麦克风读取和保存原始音频的简单任务所需的。我相信我已经将 nativescript 框架发挥到了极致,我需要专家的帮助。

我正在 Nativescript Android 中构建一个 WAV 录音机。本机实现在此处描述(相关代码如下)。

简而言之,这可以通过从 android.media.AudioRecord 缓冲区读取音频流,然后将缓冲区写入单独线程中的文件来完成,如下所述:

原生 Android 实现

startRecording()由按下按钮触发,并启动一个运行的新线程writeAudioDataToFile()

private void startRecording() {
    // ... init Recorder

    recorder.startRecording();

    isRecording = true;

    recordingThread = new Thread(new Runnable() {
        @Override
        public void run() {
            writeAudioDataToFile();
        }
     }, "AudioRecorder Thread");

    recordingThread.start();
}

isRecording通过设置为falsestopRecording()由按下按钮触发)停止录制:

private void stopRecording() {
   isRecording = false;

   recorder.stop();
   recorder.release();

   recordingThread = null;
}

如果 isRecording = false,则停止读取和保存缓冲区:

private void writeAudioDataToFile() {
    // ... init file and buffer
    ByteArrayOutputStream recData = new ByteArrayOutputStream(); 
    DataOutputStream dos = new DataOutputStream(recData);

    int read = 0;

    while(isRecording) {
        read = recorder.read(data, 0, bufferSize);

        for(int i = 0; i < bufferReadResult; i++) {
            dos.writeShort(buffer[i]);
        }
    }
}

我的Nativescript javascript 实现:

我编写了一个与上面的原生 Android 代码相同的 nativescript 打字稿代码。我面临的问题 #1 是我无法运行while(isRecording),因为 javascript 线程将忙于在while循环内运行,并且永远无法捕获按钮点击运行stopRecording()

我试图通过使用setInterval异步执行来解决问题 #1,如下所示:

startRecording()由按下按钮触发,并设置执行的时间间隔为 10 毫秒writeAudioDataToFile()

startRecording() {
    this.audioRecord.startRecording();

    this.audioBufferSavingTimer = setInterval(() => this.writeAudioDataToFile(), 10);
}

writeAudioDataToFile()回调每 10 毫秒排队一次:

writeAudioDataToFile() {
    let bufferReadResult = this.audioRecord.read(
        this.buffer,
        0,
        this.minBufferSize / 4
    );

    for (let i = 0; i < bufferReadResult; i++) {
        dos.writeShort(buffer[i]);
    }

}

通过清除时间间隔(stopRecording()由按下按钮触发)停止录制:

stopRecording() {
    clearInterval(this.audioBufferSavingTimer);

    this.audioRecord.stop();
    this.audioRecord.release();
}

问题 2:虽然这很好用,但在许多情况下,它会使 UI 冻结 1-10 秒(例如在单击按钮停止录制后)。

我尝试将执行的时间间隔writeAudioDataToFile()从 10 毫秒更改为 0 毫秒,再到 1000 毫秒(虽然缓冲区很大),但是 UI 冻结时间更长,并且我在保存的数据中遇到了丢失(未保存的缓冲数据到文件)。

我尝试通过使用此处描述的 nativescript 工作线程将此操作卸载到单独的线程,其中startRecording()stopRecording()由发送到线程的消息调用,如下所示:

global.onmessage = function(msg) {

    if (msg.data === 'startRecording') {
        startRecording();
    } else if (msg.data === 'stopRecording') {
       stopRecording();
    } 

}

这解决了 UI 问题,但产生了问题 #3:记录器停止未按时执行(即在工作线程接收到 'stopRecording' msg.data 后 10 到 50 秒停止记录)。我尝试在setInterval工作线程内部使用不同的时间间隔(0ms 到 1000ms),但这并没有解决问题,甚至导致stopRecording()执行延迟更大。

有谁知道如何在 nativescript/javascript 中执行这种非阻塞的高性能记录活动?有没有更好的方法来解决我上面描述的问题 #1(javascript 异步执行)?

谢谢

4

1 回答 1

1

我会在实际 Java 中保留完整的 Java 实现,您可以通过在插件文件夹中创建一个 java 文件来做到这一点:platforms/android/java,所以可能类似于:platforms/android/java/org/nativescript/AudioRecord.java

在那里你可以做所有线程化的事情,所以你不会因为 UI 被阻塞而烦恼。您可以直接从 NativeScript 调用 Java 方法来开始和停止录制。构建项目时,Java 文件将自动编译并包含在内。

您可以通过从插件({plugin_name}.aar)生成的 .aar 文件中获取 classes.jar 并为其生成类型声明来从 Java 类生成类型:https ://docs.nativescript.org/core-concepts/ android-runtime/元数据/generating-typescript-declarations

这样,您就可以在编辑器中获得所有可用的方法/类/类型信息。

于 2020-04-11T09:51:24.170 回答