Running a non-blocking, high-performance activity in nativescript/javascript - performance

This question is about running a non-blocking, high-performance activity in nativescript that is needed for the simple task of reading and saving raw audio from the microphone by directly accessing the hardware through the native Android API. I believe I have brought the nativescript framework to the edge of its capabilities, and I need experts' help.
I'm building a WAV audio recorder in Nativescript Android. Native implementation is described here (relevant code below).
In short, this can be done by reading audio steam from an android.media.AudioRecord buffer, and then writing the buffer to a file in a separate thread, as described:
Native Android implementation
startRecording() is triggered by a button press, and starts a new Thread that runs writeAudioDataToFile():
private void startRecording() {
// ... init Recorder
recorder.startRecording();
isRecording = true;
recordingThread = new Thread(new Runnable() {
#Override
public void run() {
writeAudioDataToFile();
}
}, "AudioRecorder Thread");
recordingThread.start();
}
Recording is stopped by setting isRecording to false (stopRecording() is triggered by a button press):
private void stopRecording() {
isRecording = false;
recorder.stop();
recorder.release();
recordingThread = null;
}
Reading and saving buffer is stopped if 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]);
}
}
}
My Nativescript javascript implementation:
I wrote a nativescript typescript code that does the same as the native Android code above. The problem #1 I faced was that I can't run while(isRecording) because the javascript thread would be busy running inside the while loop, and would never be able to catch button clicks to run stopRecording().
I tried to solve problem #1 by using setInterval for asynchronous execution, like this:
startRecording() is triggered by a button press, and sets a time interval of 10ms that executes writeAudioDataToFile():
startRecording() {
this.audioRecord.startRecording();
this.audioBufferSavingTimer = setInterval(() => this.writeAudioDataToFile(), 10);
}
writeAudioDataToFile() callbacks are queued up every 10ms:
writeAudioDataToFile() {
let bufferReadResult = this.audioRecord.read(
this.buffer,
0,
this.minBufferSize / 4
);
for (let i = 0; i < bufferReadResult; i++) {
dos.writeShort(buffer[i]);
}
}
Recording is stopped by clearing the time interval (stopRecording() is triggered by button press):
stopRecording() {
clearInterval(this.audioBufferSavingTimer);
this.audioRecord.stop();
this.audioRecord.release();
}
Problem #2: While this works well, in many cases it makes the UI freeze for 1-10 seconds (for example after clicking a button to stop recording).
I tried to change the time interval that executes writeAudioDataToFile() from 10ms to 0ms and up to 1000ms (while having a very big buffer), but then the UI freezes were longer and, and I experienced loss in the saved data (buffered data that was not saved to the file).
I tried to offload this operation to a separate Thread by using a nativescript worker thread as described here, where startRecording() and stopRecording() are called by messages sent to the thread like this:
global.onmessage = function(msg) {
if (msg.data === 'startRecording') {
startRecording();
} else if (msg.data === 'stopRecording') {
stopRecording();
}
}
This solved the UI problem, but created problem #3: The recorder stop was not executed on time (i.e. recording stops 10 to 50 seconds after the 'stopRecording' msg.data is received by the worker thread). I tried to use different time intervals in the setInterval inside the worker thread (0ms to 1000ms) but that didn't solve the problem and even made stopRecording() be executed with greater delays.
Does anyone have an idea of how to perform such a non-blocking high-performance recording activity in nativescript/javascript?
Is there a better approach to solve problem #1 (javascript asynchronous execution) that I described above?
Thanks

I would keep the complete Java implementation in actual Java, you can do this by creating a java file in your plugin folder:
platforms/android/java, so maybe something like:
platforms/android/java/org/nativescript/AudioRecord.java
In there you can do everything threaded, so you won't be troubled by the UI being blocked. You can call the Java methods directly from NativeScript for starting and stopping the recording. When you build your project, the Java file will automatically be compiled and included.
You can generate typings from your Java class by grabbing classes.jar from the generated .aar file of your plugin ({plugin_name}.aar) and generate type declarations for it: https://docs.nativescript.org/core-concepts/android-runtime/metadata/generating-typescript-declarations
This way you have all the method/class/type information available in your editor.

Related

Unity: Hololens wont run thread when deployed but works in editor

I'm trying to send a UDP broadcast on the Hololens but for some reason the thread won't get executed. Interestingly, when I compile it on my old laptop in VS it works?! I compared the installed SDK, settings everything but I cannot figure out why it works when I compile it on my old laptop. It was a mere coincidence that I discovered that in the first place.
This calls the thread:
public void StartThread()
{
// create thread for reading UDP messages
readThread = new Thread(new ThreadStart(ReceiveData));
readThread.IsBackground = true;
readThread.Start();
}
And the thread looks like this:
private void ReceiveData()
{
client = new UdpClient(port);
client.EnableBroadcast = true;
Debug.Log("Thread Started");
while (true)
{
try
....
The debug line won't even get executed. I works in Unity but not on Hololens, except I compile it on an old machine of mine.
Any thoughts? I'm at total loss here.
As derHugo suggested, the thread needed to be started like
public void StartThread()
{
// create thread for reading UDP messages
readThread = new Thread(ReceiveData);
readThread.IsBackground = true;
readThread.Start();
}
That did the trick.

Framerate independent event in Unity3D

I use the libpd4unity package to communicate with Pure Data. I receive a bang from Pure Data with LibPD.Bang. On a bang event I play sound by FMOD.
Problem is, that I receive bangs frequently, for example once every 500 ms but event doesn't trigger in specific length of frame. Usually length change 1 frame less or more.
Is there a solution for this problem? For example a framerate independent event? I want to know if event (delegate) in Unity3D is framerate independent or not.
Because there is tempo for playing each sound and just 1 frame ruins rhythm.
I need to sync sounds for playing by each separate bang.
Regarding your question on whether delegates are dependent or independent from Unity's framerate, there's no straight answer. It depends on how your delegates are called. Are they called from a thread? Are they executed in a thread?
Coroutines are not framerate independent, they are executed in Unity's loop.
The following script should shine a light on the difference between handling delegates in coroutines and in threads.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
public class DelegatesAndFramerate : MonoBehaviour {
delegate void MyDelegate();
MyDelegate myDelegate1; // done with coroutines
MyDelegate myDelegate2; // done with threads
Thread thread;
bool threadDone = false;
private int frameCount = 0;
private int delegate1CallCount = 0;
private int delegate2CallCount = 0;
private int callerLoopsCount_coroutine = 0;
private int callerLoopsCount_thread = 0;
void Start () {
myDelegate1 += Elab1;
myDelegate2 += Elab2;
StartCoroutine(CallerCoroutine());
thread = new Thread(new ThreadStart(CallerThread));
thread.Start();
}
void Update()
{
frameCount++;
}
void Elab1()
{
delegate1CallCount++;
}
void Elab2()
{
delegate2CallCount++;
}
IEnumerator CallerCoroutine()
{
while(true)
{
callerLoopsCount_coroutine++;
myDelegate1();
yield return null;
}
}
void CallerThread()
{
while(!threadDone)
{
callerLoopsCount_thread++;
myDelegate2();
}
}
void OnDestroy()
{
Debug.Log("Frame Count: " + frameCount);
Debug.Log("Delegate Call Count (Coroutine): " + delegate1CallCount);
Debug.Log("Delegate Call Count (Thread): " + delegate2CallCount);
Debug.Log("Caller Loops Count (Coroutine): " + callerLoopsCount_coroutine);
Debug.Log("Caller Loops Count (Thread): " + callerLoopsCount_thread);
threadDone = true;
thread.Join();
}
}
If you attach it to a GameObject and let Unity play for some seconds you'll see that the times the delegate was called from a coroutine is equal to the number of executed frames whilst the times the delegate was called from the thread will be way bigger.
I have experience in interfacing softwares similar to Pure Data and I think what you need is a (rather typical) thread with all your delegates there, create a queue of commands for Unity and digest it in Unity's Update.
Not knowing libPD in the specific this might not be the best practice for the case but it is a widely used approach. Basically the producer-consumer pattern.
Basing on the example GUITextScript.cs, libPD only requires you to subscribe to the right delegates. You don't have control on when these are executed, the library has; so if you keep having this issue it's worth submitting a bug report to the developers I guess.

When is a windows service considered "started"

We have a process that is executed as a windows service,
This process serves as an interface server processing incoming messages, transforms them and sends them out to another interface.
it is a rather heavy process, it needs to load a lot of things into memory and that takes some time (few minutes).
due to its nature, when we start it using its windows service, it remains in "starting" status for a very long time (sometimes more than 20 minutes)
even when we can see the process already works and process messages just fine (going by its logs).
so the question is - when is a service considered "starting" and when is it considered "started"? based on what factors?
Starting status finish when onstart is completed.
You should write starting code after onstart event.
puclic class Service1
{
private Timer timer = new Timer();
protected override void OnStart(string[] args)
{
this.timer.Elapsed += new ElapsedEventHandler(OnElapsedTime);
this.timer.Interval = 1 * 1000; // 1 second
this.timer.Enabled = true;
}
private void OnElapsedTime(object source, ElapsedEventArgs e)
{
this.timer.Enabled = false; // OnElapsedTime run only one time
// Write your code
}
}

Gamepad hot plugging does not work

In OS X my gamepads are recognised correctly in SDL_PollEvent() at application startup. However, when I try hot plugging new gamepads or removing old gamepads, the SDL_PollEvent() does not trigger either SDL_CONTROLLERDEVICEADDED or SDL_CONTROLLERDEVICEREMOVED. The same code works correctly in Windows when I hot plug game controllers.
A more interesting note is that if I resize the window of my application, the hot plugging works. After resize event all the hot plugging events are triggered. It almost seems that the gamepad events are put in a some kind of waiting queue which is purged when the resize event happens. My SDL_PollEvent() code is quite standard as seen below.
case SDL_CONTROLLERDEVICEADDED:
if (SDL_IsGameController(e.cdevice.which))
{
SDL_GameController *pad = SDL_GameControllerOpen(e.cdevice.which);
if (pad)
{
SDL_Joystick *joy = SDL_GameControllerGetJoystick(pad);
int instanceID = SDL_JoystickInstanceID(joy);
if(m_gameControllers.count(instanceID) == 0)
{
m_gameControllers.insert(std::make_pair(instanceID, pad));
}
}
}
break;
case SDL_CONTROLLERDEVICEREMOVED:
{
auto it = m_gameControllers.find(e.cdevice.which);
if (it != m_gameControllers.end())
{
SDL_GameController* pad = m_gameControllers[e.cdevice.which];
SDL_GameControllerClose(pad);
m_gameControllers.erase(it);
}
}
break;
Has anyone else experienced this?
After some struggling I found the solution: Call SDL_PollEvent() from the main thread. Initially I called the gamepad handling method from the CVDisplayLink thread which resulted the described behaviour.
In my case the solution was simply to add dispatch_async call to my gamepad handling function.
dispatch_async(dispatch_get_main_queue(),^ { handleGamePad();});

Not receiving messages after sometime

I am using JNA to access User32 functions (I dont think it has got to do with Java here, more of concept problem). In my application, I have a Java process which communicates with the Canon SDK. To dispatch any messages I am using the below function:
private void peekMessage(WinUser.MSG msg) throws InterruptedException {
int hasMessage = lib.GetMessage(msg, null, 0, 0);
if (hasMessage != 0) {
lib.TranslateMessage(msg);
lib.DispatchMessage(msg);
}
Thread.sleep(1);
}
peekMessage is called in a loop and it all works well. Whenever an Image is taken from camera, I get the event and do the rest.
But I have observed, say after about 15 seconds (sometimes never or sometimes just at start) of no activity with camera, taking picture does not give me any download event. Later the whole application becomes unusable as it doesn't get any events from camera.
What can be the reason for this? Please let me know of any other info needed, I can paste the respective code along.
Edit:
Initialization:
Map<String, Integer> options = new HashMap<String, Integer>();
lib = User32.INSTANCE;
hMod = Kernel32.INSTANCE.GetModuleHandle("");
options.put(Library.OPTION_CALLING_CONVENTION, StdCallLibrary.STDCALL_CONVENTION);
this.EDSDK = (EdSdkLibrary) Native.loadLibrary("EDSDK/dll/EDSDK.dll", EdSdkLibrary.class, options);
private void runNow() throws InterruptedException {
while (!Thread.currentThread().isInterrupted()) {
Task task = queue.poll();
if (task != null) {
int taskResult = task.call();
switch (taskResult) {
case (Task.INITIALIZE_STATE):
break;
case (Task.PROCESS_STATE):
break;
case (Task.TERMINATE_STATE): {
//queue.add(new InitializeTask());
Thread.currentThread().interrupt();
break;
}
default:
;
}
}
getOSEvents();
}
}
WinUser.MSG msg = new WinUser.MSG();
private void getOSEvents() throws InterruptedException {
if (isMac) {
receiveEvents();
} else {
peekMessage(msg);
}
}
Above, whenever I get my camera event, it add's it to the queue and in each loop I check the queue to process any Task. One more important information: This is a process running on cmd and has no window. I just need the events from my camera and nothing else.
The code where I register callback functions:
/**
* Adds handlers.
*/
private void addHandlers() {
EdSdkLibrary.EdsVoid context = new EdSdkLibrary.EdsVoid(new Pointer(0));
int result = EDSDK.EdsSetObjectEventHandler(edsCamera, new NativeLong(EdSdkLibrary.kEdsObjectEvent_All), new ObjectEventHandler(), context).intValue();
//above ObjectEventHandler contains a function "apply" which is set as callback function
context = new EdSdkLibrary.EdsVoid(new Pointer(0));
result = EDSDK.EdsSetCameraStateEventHandler(edsCamera, new NativeLong(EdSdkLibrary.kEdsStateEvent_All), new StateEventHandler(), context).intValue();
//above StateEventHandler contains a function "apply" which is set as callback function
context = new EdSdkLibrary.EdsVoid(new Pointer(0));
result = EDSDK.EdsSetPropertyEventHandler(edsCamera, new NativeLong(EdSdkLibrary.kEdsStateEvent_All), new PropertyEventHandler(), context).intValue();
//above PropertyEventHandler contains a function "apply" which is set as callback function
}
You are getting ALL messages from ALL windows that belong to this thread, that includes all mouse moves, paints etc. if you aren't rapidly calling this function your message queue will overflow and cause the behavior you describe.
The sleep you definitely don't want as GetMessage yields if no message is waiting.
So if there exists a normal message pump(s) (i.e GetMessage/DispatchMessage) loop somewhere else for this threads window(s) then you should let that pump do most of the work, perhaps use wMsgFilterMin, wMsgFilterMax to just get the event message you require; or even better in this case use peekmessage with PM_NOREMOVE (then you will need your sleep
call as peekmessage returns immediately).
Alternatively provide the hWnd of the window that generates the event to reduce the workload.
Use spy++ to look into which windows this thread owns and what messages are being produced.
To take this answer further please provide answers to: what else is this thread doing and what windows does it own; also is this message pump the only one or do you call into the SDK API where it may be pumping messages too?
There is an OpenSource project wrapping EDSDK with JNA and it has a version of your code that is probably working better:
https://github.com/kritzikratzi/edsdk4j/blob/master/src/edsdk/api/CanonCamera.java#L436
Unfortunately this is not platform independent and specifically the way things work on windows. I am currently in the process of trying to get a MacOS version of things working at:
https://github.com/WolfgangFahl/edsdk4j

Resources