cancel playback functionality, on write event
This commit is contained in:
parent
62c69c4875
commit
f6a3582649
7 changed files with 105 additions and 53 deletions
|
|
@ -11,7 +11,7 @@
|
||||||
"test": "node test.js"
|
"test": "node test.js"
|
||||||
},
|
},
|
||||||
"gypfile": true,
|
"gypfile": true,
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://gitlab.com/api/v4/projects/28849281/packages/npm/"
|
"registry": "https://gitlab.com/api/v4/projects/28849281/packages/npm/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
/* JS callbacks */
|
/* JS callbacks */
|
||||||
napi_threadsafe_function js_on_data;
|
napi_threadsafe_function js_on_data;
|
||||||
|
napi_threadsafe_function js_on_playback;
|
||||||
|
|
||||||
/* Calls js_emit_ts from the main thread and translates data to node */
|
/* Calls js_emit_ts from the main thread and translates data to node */
|
||||||
static void call_js_on_data( napi_env env,
|
static void call_js_on_data( napi_env env,
|
||||||
|
|
@ -56,11 +57,30 @@ static void call_js_on_data( napi_env env,
|
||||||
status = napi_call_function(env, undefined, on_data, argc, &args[0], NULL);
|
status = napi_call_function(env, undefined, on_data, argc, &args[0], NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void call_js_on_playback( napi_env env,
|
||||||
|
napi_value on_data,
|
||||||
|
void* context,
|
||||||
|
void* data )
|
||||||
|
{
|
||||||
|
(void) context;
|
||||||
|
napi_status status;
|
||||||
|
|
||||||
|
napi_value channel;
|
||||||
|
status = napi_create_string_utf8(env, "write", NAPI_AUTO_LENGTH, &channel);
|
||||||
|
|
||||||
|
napi_value undefined;
|
||||||
|
status = napi_get_undefined(env, &undefined);
|
||||||
|
|
||||||
|
size_t argc = 1;
|
||||||
|
napi_value args[1] = { channel };
|
||||||
|
status = napi_call_function(env, undefined, on_data, argc, &args[0], NULL);
|
||||||
|
}
|
||||||
|
|
||||||
/* Takes in a Node Event Emitter and creates JS threadsafe functions to be
|
/* Takes in a Node Event Emitter and creates JS threadsafe functions to be
|
||||||
* called from the PortAudio callbacks.
|
* called from the PortAudio callbacks.
|
||||||
*/
|
*/
|
||||||
void init_callbacks( napi_env env,
|
void init_callbacks( napi_env env,
|
||||||
napi_value* js_emit,
|
napi_value* js_emit,
|
||||||
PaUtilRingBuffer* inputBuffer )
|
PaUtilRingBuffer* inputBuffer )
|
||||||
{
|
{
|
||||||
napi_status status;
|
napi_status status;
|
||||||
|
|
@ -70,7 +90,7 @@ void init_callbacks( napi_env env,
|
||||||
NAPI_AUTO_LENGTH,
|
NAPI_AUTO_LENGTH,
|
||||||
&resource_name );
|
&resource_name );
|
||||||
|
|
||||||
/* Create thread-safe callback js_cb_safe */
|
/* Create thread-safe js_on_data callback */
|
||||||
status = napi_create_threadsafe_function( env,
|
status = napi_create_threadsafe_function( env,
|
||||||
*js_emit,
|
*js_emit,
|
||||||
NULL,
|
NULL,
|
||||||
|
|
@ -82,7 +102,19 @@ void init_callbacks( napi_env env,
|
||||||
inputBuffer, /* context */
|
inputBuffer, /* context */
|
||||||
call_js_on_data,
|
call_js_on_data,
|
||||||
&js_on_data );
|
&js_on_data );
|
||||||
/* Optional: create another cb with js_emit here */
|
|
||||||
|
/* Create thread-safe js_on_write callback */
|
||||||
|
status = napi_create_threadsafe_function( env,
|
||||||
|
*js_emit,
|
||||||
|
NULL,
|
||||||
|
resource_name,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
inputBuffer, /* context */
|
||||||
|
call_js_on_playback,
|
||||||
|
&js_on_playback );
|
||||||
}
|
}
|
||||||
|
|
||||||
int input_callback( const void* input,
|
int input_callback( const void* input,
|
||||||
|
|
@ -136,24 +168,18 @@ int output_callback( const void* input,
|
||||||
if (framesToRead != 0)
|
if (framesToRead != 0)
|
||||||
{
|
{
|
||||||
PaUtil_ReadRingBuffer(rb, output, framesToRead);
|
PaUtil_ReadRingBuffer(rb, output, framesToRead);
|
||||||
printf("Wrote %i frames\n", framesToRead);
|
|
||||||
|
napi_status status;
|
||||||
|
status = napi_call_threadsafe_function( js_on_playback,
|
||||||
|
NULL,
|
||||||
|
napi_tsfn_nonblocking );
|
||||||
}
|
}
|
||||||
|
|
||||||
return paContinue;
|
return paContinue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// short* sample = (short*) output;
|
||||||
|
// for (int i = 0; i < framesToRead * 2; ++i)
|
||||||
|
// {
|
||||||
|
// printf("%hu", sample[i]);
|
||||||
|
// }
|
||||||
|
|
||||||
// short* sample = (short*) output;
|
|
||||||
// for (int i = 0; i < framesToRead * 2; ++i)
|
|
||||||
// {
|
|
||||||
// printf("%hu", sample[i]);
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ typedef struct
|
||||||
}
|
}
|
||||||
audio_info;
|
audio_info;
|
||||||
|
|
||||||
void init_callbacks( napi_env env,
|
void init_callbacks( napi_env env,
|
||||||
napi_value* js_emit,
|
napi_value* js_emit,
|
||||||
PaUtilRingBuffer* inputBuffer );
|
PaUtilRingBuffer* inputBuffer );
|
||||||
|
|
||||||
int input_callback( const void* input,
|
int input_callback( const void* input,
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,15 @@ PaUtilRingBuffer outputBuffer;
|
||||||
void* inputBufferStore;
|
void* inputBufferStore;
|
||||||
void* outputBufferStore;
|
void* outputBufferStore;
|
||||||
|
|
||||||
/* Sample format is hard-coded paInt16 (unsigned short, 2 bytes) */
|
pthread_t pb_th;
|
||||||
|
pthread_mutex_t mutex;
|
||||||
|
|
||||||
PaError Na_Initialize(napi_env env, napi_value* js_cb)
|
PaError Na_Initialize(napi_env env, napi_value* js_emit)
|
||||||
{
|
{
|
||||||
init_callbacks(env, js_cb, &inputBuffer);
|
init_callbacks(env, js_emit, &inputBuffer);
|
||||||
|
|
||||||
|
pthread_mutex_init(&mutex, NULL);
|
||||||
|
|
||||||
PaError err;
|
PaError err;
|
||||||
err = Pa_Initialize();
|
err = Pa_Initialize();
|
||||||
return err;
|
return err;
|
||||||
|
|
@ -132,6 +136,8 @@ PaError Na_CloseOutputStream()
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool cancel_pb_th = false;
|
||||||
|
|
||||||
static void WriteLargeDataToOutputBuffer(void* arg)
|
static void WriteLargeDataToOutputBuffer(void* arg)
|
||||||
{
|
{
|
||||||
playback_data* pd = (playback_data*) arg;
|
playback_data* pd = (playback_data*) arg;
|
||||||
|
|
@ -140,9 +146,9 @@ static void WriteLargeDataToOutputBuffer(void* arg)
|
||||||
ring_buffer_size_t framesToWrite;
|
ring_buffer_size_t framesToWrite;
|
||||||
ring_buffer_size_t framesWritten;
|
ring_buffer_size_t framesWritten;
|
||||||
|
|
||||||
// printing data works here...
|
pthread_mutex_lock(&mutex);
|
||||||
|
cancel_pb_th = false;
|
||||||
while (pd->len > 0)
|
while (pd->len > 0 && !cancel_pb_th)
|
||||||
{
|
{
|
||||||
writableSpace = PaUtil_GetRingBufferWriteAvailable(&outputBuffer);
|
writableSpace = PaUtil_GetRingBufferWriteAvailable(&outputBuffer);
|
||||||
if (writableSpace != 0)
|
if (writableSpace != 0)
|
||||||
|
|
@ -158,6 +164,7 @@ static void WriteLargeDataToOutputBuffer(void* arg)
|
||||||
pd->data += framesWritten * 2; /* convert to bytes */
|
pd->data += framesWritten * 2; /* convert to bytes */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
|
||||||
free(arg);
|
free(arg);
|
||||||
}
|
}
|
||||||
|
|
@ -167,8 +174,6 @@ void Na_WriteToOutputBuffer(void* data, size_t size)
|
||||||
ring_buffer_size_t writableSpace;
|
ring_buffer_size_t writableSpace;
|
||||||
writableSpace = PaUtil_GetRingBufferWriteAvailable(&outputBuffer);
|
writableSpace = PaUtil_GetRingBufferWriteAvailable(&outputBuffer);
|
||||||
|
|
||||||
// printing data works here...
|
|
||||||
|
|
||||||
/* If there is not enough space to write all the data at once, we must
|
/* If there is not enough space to write all the data at once, we must
|
||||||
* start a new thread in order to prevent blocking.
|
* start a new thread in order to prevent blocking.
|
||||||
*/
|
*/
|
||||||
|
|
@ -178,15 +183,22 @@ void Na_WriteToOutputBuffer(void* data, size_t size)
|
||||||
playback_data* pd = (playback_data*) malloc(sizeof(playback_data));
|
playback_data* pd = (playback_data*) malloc(sizeof(playback_data));
|
||||||
pd->data = data;
|
pd->data = data;
|
||||||
pd->len = len;
|
pd->len = len;
|
||||||
pthread_t w_th;
|
pthread_create(&pb_th, NULL, (void*) WriteLargeDataToOutputBuffer, pd);
|
||||||
pthread_create(&w_th, NULL, (void*) WriteLargeDataToOutputBuffer, pd);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
pthread_mutex_lock(&mutex);
|
||||||
PaUtil_WriteRingBuffer(&outputBuffer, data, writableSpace);
|
PaUtil_WriteRingBuffer(&outputBuffer, data, writableSpace);
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Na_CancelPlayback()
|
||||||
|
{
|
||||||
|
printf("NACORE:Na_CancelPlayback\n");
|
||||||
|
cancel_pb_th = true;
|
||||||
|
}
|
||||||
|
|
||||||
PaError Na_Terminate()
|
PaError Na_Terminate()
|
||||||
{
|
{
|
||||||
printf("NACORE:Na_Terminate\n");
|
printf("NACORE:Na_Terminate\n");
|
||||||
|
|
@ -205,25 +217,10 @@ PaError Na_Terminate()
|
||||||
free_buffer(&inputBuffer);
|
free_buffer(&inputBuffer);
|
||||||
free_buffer(&outputBuffer);
|
free_buffer(&outputBuffer);
|
||||||
|
|
||||||
|
pthread_mutex_destroy(&mutex);
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// /* Data being passed into the thread must be allocated to heap so that
|
|
||||||
// * it isn't deleted when this function returns.
|
|
||||||
// */
|
|
||||||
// playback_data* pd = (playback_data*) malloc(sizeof(playback_data));
|
|
||||||
// pd->data = (void*) malloc(len * 2);
|
|
||||||
// memcpy(pd->data, data, 2);
|
|
||||||
// pd->len = len;
|
|
||||||
|
|
||||||
// short* sample = (short*) pd->data;
|
|
||||||
// int count = (int) pd->len;
|
|
||||||
// for (int i = 0; i < count; i++)
|
|
||||||
// {
|
|
||||||
// printf("%hu", sample[i]);
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ typedef struct
|
||||||
}
|
}
|
||||||
playback_data;
|
playback_data;
|
||||||
|
|
||||||
PaError Na_Initialize(napi_env env, napi_value* js_cb);
|
PaError Na_Initialize(napi_env env, napi_value* js_emit);
|
||||||
|
|
||||||
PaError Na_GetDefaultInputDevice(PaDeviceIndex* deviceIndex);
|
PaError Na_GetDefaultInputDevice(PaDeviceIndex* deviceIndex);
|
||||||
|
|
||||||
|
|
@ -24,6 +24,8 @@ PaError Na_CloseOutputStream();
|
||||||
|
|
||||||
void Na_WriteToOutputBuffer(void* data, size_t size);
|
void Na_WriteToOutputBuffer(void* data, size_t size);
|
||||||
|
|
||||||
|
void Na_CancelPlayback();
|
||||||
|
|
||||||
PaError Na_Terminate();
|
PaError Na_Terminate();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -161,6 +161,16 @@ napi_value WriteToOutputStream(napi_env env, napi_callback_info info)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
napi_value CancelPlayback(napi_env env, napi_callback_info info)
|
||||||
|
{
|
||||||
|
printf("NAPI::CancelPlayback\n");
|
||||||
|
(void) env;
|
||||||
|
(void) info;
|
||||||
|
|
||||||
|
Na_CancelPlayback();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
napi_value Terminate(napi_env env, napi_callback_info info)
|
napi_value Terminate(napi_env env, napi_callback_info info)
|
||||||
{
|
{
|
||||||
printf("NAPI::Terminate\n");
|
printf("NAPI::Terminate\n");
|
||||||
|
|
@ -190,9 +200,10 @@ napi_value Init(napi_env env, napi_value exports)
|
||||||
DECLARE_NAPI_METHOD("CloseInputStream", CloseInputStream),
|
DECLARE_NAPI_METHOD("CloseInputStream", CloseInputStream),
|
||||||
DECLARE_NAPI_METHOD("CloseOutputStream", CloseOutputStream),
|
DECLARE_NAPI_METHOD("CloseOutputStream", CloseOutputStream),
|
||||||
DECLARE_NAPI_METHOD("WriteToOutputStream", WriteToOutputStream),
|
DECLARE_NAPI_METHOD("WriteToOutputStream", WriteToOutputStream),
|
||||||
|
DECLARE_NAPI_METHOD("CancelPlayback", CancelPlayback)
|
||||||
};
|
};
|
||||||
|
|
||||||
status = napi_define_properties(env, exports, 9, desc);
|
status = napi_define_properties(env, exports, 10, desc);
|
||||||
|
|
||||||
return exports;
|
return exports;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,10 @@ emitter.on("data", (int16Arr) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
emitter.on("write", () => {
|
||||||
|
console.log("Writing frames");
|
||||||
|
});
|
||||||
|
|
||||||
const setStreams = () => {
|
const setStreams = () => {
|
||||||
|
|
||||||
const defaultInput = nodeAudio.core.GetDefaultInputDevice();
|
const defaultInput = nodeAudio.core.GetDefaultInputDevice();
|
||||||
|
|
@ -48,6 +52,18 @@ const stopRecording = () => {
|
||||||
/* Play recorded audio */
|
/* Play recorded audio */
|
||||||
console.log("Playing recorded audio...");
|
console.log("Playing recorded audio...");
|
||||||
nodeAudio.core.WriteToOutputStream(pcmAudio.buffer);
|
nodeAudio.core.WriteToOutputStream(pcmAudio.buffer);
|
||||||
|
|
||||||
|
/* Two writes test */
|
||||||
|
// setTimeout(() => {
|
||||||
|
// console.log("Playing again...");
|
||||||
|
// nodeAudio.core.WriteToOutputStream(pcmAudio.buffer);
|
||||||
|
// }, 2000);
|
||||||
|
|
||||||
|
/* Cancel mid-playback test */
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log("Cancelling...");
|
||||||
|
nodeAudio.core.CancelPlayback();
|
||||||
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeAudio.core.Initialize(emitter.emit.bind(emitter));
|
nodeAudio.core.Initialize(emitter.emit.bind(emitter));
|
||||||
|
|
@ -69,4 +85,4 @@ stdin.on( 'data', function( key ) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
nodeAudio.core.Terminate();
|
nodeAudio.core.Terminate();
|
||||||
process.exit();
|
process.exit();
|
||||||
}, 30000);
|
}, 20000);
|
||||||
Loading…
Reference in a new issue