From b97e4dc56ed69e95063f81644b2e3460f71b16e1 Mon Sep 17 00:00:00 2001 From: anprogrammer Date: Wed, 9 Jul 2014 21:10:36 -0400 Subject: [PATCH] Adds support for JS to configure ring-buffer size. The default buffer sizes chosen, at least in Win32, are extremely conservative. Sure, a skip in audio will be extremely rare, but for any type of real-time application (video-games), the audio is near-unusable, because sounds will play several seconds after the event that triggered them. I've exposed the buffer-size fields all the way up to the JS api, so that the application developer can choose the best balance between reliability and performance to fit their needs. So far this is only supported on Win32 (and ignored on other platforms), but I plan on implementing additional platforms in the future. --- deps/mpg123/src/audio.h | 2 ++ deps/mpg123/src/output/win32.c | 45 +++++++++++++++++----------------- index.js | 19 +++++++++++++- src/binding.cc | 4 +++ 4 files changed, 47 insertions(+), 23 deletions(-) diff --git a/deps/mpg123/src/audio.h b/deps/mpg123/src/audio.h index 6fff8cd..6b74bf2 100644 --- a/deps/mpg123/src/audio.h +++ b/deps/mpg123/src/audio.h @@ -58,6 +58,8 @@ typedef struct audio_output_struct long gain; /* output gain */ int channels; /* number of channels */ int format; /* format flags */ + int bufferSize; /* Size of each audio buffer to keep ahead */ + int numBuffers; /* Number of audio buffers to keep ahead */ int is_open; /* something opened? */ #define MPG123_OUT_QUIET 1 int auxflags; /* For now just one: quiet mode (for probing). */ diff --git a/deps/mpg123/src/output/win32.c b/deps/mpg123/src/output/win32.c index 249f222..257dba1 100644 --- a/deps/mpg123/src/output/win32.c +++ b/deps/mpg123/src/output/win32.c @@ -12,19 +12,10 @@ #include #include "debug.h" -/* - Buffer size and number of buffers in the playback ring - NOTE: This particular num/size combination performs best under heavy - loads for my system, however this may not be true for any hardware/OS out there. - Generally, BUFFER_SIZE < 8k || NUM_BUFFERS > 16 || NUM_BUFFERS < 4 are not recommended. -*/ -#define BUFFER_SIZE 0x10000 -#define NUM_BUFFERS 8 /* total 512k roughly 2.5 sec of CD quality sound */ - /* Buffer ring queue state */ struct queue_state { - WAVEHDR buffer_headers[NUM_BUFFERS]; + WAVEHDR* buffer_headers; /* The next buffer to be filled and put in playback */ int next_buffer; /* Buffer playback completion event */ @@ -46,12 +37,21 @@ static int open_win32(struct audio_output_struct *ao) /* Allocate queue state struct for this device */ state = calloc(1, sizeof(struct queue_state)); if(!state) return -1; - + + state->buffer_headers = calloc(ao->numBuffers, sizeof(WAVEHDR)); + if (!state->buffer_headers) { + free(state); + return -1; + } + ao->userptr = state; /* Allocate playback buffers */ - for(i = 0; i < NUM_BUFFERS; i++) - if(!(state->buffer_headers[i].lpData = malloc(BUFFER_SIZE))) - ereturn(-1, "Out of memory for playback buffers."); + for(i = 0; i < ao->numBuffers; i++) { + if(!(state->buffer_headers[i].lpData = malloc(ao->bufferSize))) { + ereturn(-1, "Out of memory for playback buffers."); + } + state->buffer_headers[i].dwFlags = WHDR_DONE; + } state->play_done_event = CreateEvent(0,FALSE,FALSE,0); if(state->play_done_event == INVALID_HANDLE_VALUE) return -1; @@ -118,7 +118,7 @@ static int write_win32(struct audio_output_struct *ao, unsigned char *buf, int l /* Check buffer header and wait if it's being played. Skip waiting if the buffer is not full yet */ - while(hdr->dwBufferLength == BUFFER_SIZE && !(hdr->dwFlags & WHDR_DONE)) + while(hdr->dwBufferLength == ao->bufferSize && !(hdr->dwFlags & WHDR_DONE)) { /* debug1("waiting for buffer %i...", state->next_buffer); */ WaitForSingleObject(state->play_done_event, INFINITE); @@ -133,13 +133,13 @@ static int write_win32(struct audio_output_struct *ao, unsigned char *buf, int l } /* Now see how much we want to stuff in and then stuff it in. */ - bufill = BUFFER_SIZE - hdr->dwBufferLength; + bufill = ao->bufferSize - hdr->dwBufferLength; if(len < bufill) bufill = len; rest_len = len - bufill; memcpy(hdr->lpData + hdr->dwBufferLength, buf, bufill); hdr->dwBufferLength += bufill; - if(hdr->dwBufferLength == BUFFER_SIZE) + if(hdr->dwBufferLength == ao->bufferSize) { /* Send the buffer out when it's full. */ res = waveOutPrepareHeader(state->waveout, hdr, sizeof(WAVEHDR)); if(res != MMSYSERR_NOERROR) ereturn(-1, "Can't write to audio output device (prepare)."); @@ -148,7 +148,7 @@ static int write_win32(struct audio_output_struct *ao, unsigned char *buf, int l if(res != MMSYSERR_NOERROR) ereturn(-1, "Can't write to audio output device."); /* Cycle to the next buffer in the ring queue */ - state->next_buffer = (state->next_buffer + 1) % NUM_BUFFERS; + state->next_buffer = (state->next_buffer + 1) % ao->numBuffers; } /* I'd like to propagate error codes or something... but there are no catchable surprises left. Anyhow: Here is the recursion that makes ravenexp happy;-) */ @@ -169,7 +169,7 @@ static void flush_win32(struct audio_output_struct *ao) /* FIXME: The very last output buffer is not played. This could be a problem on the feeding side. */ i = 0; z = state->next_buffer - 1; - for(i = 0; i < NUM_BUFFERS; i++) + for(i = 0; i < ao->numBuffers; i++) { if(!state->buffer_headers[i].dwFlags & WHDR_DONE) WaitForSingleObject(state->play_done_event, INFINITE); @@ -177,7 +177,7 @@ static void flush_win32(struct audio_output_struct *ao) waveOutUnprepareHeader(state->waveout, &state->buffer_headers[i], sizeof(WAVEHDR)); state->buffer_headers[i].dwFlags = 0; state->buffer_headers[i].dwBufferLength = 0; - z = (z + 1) % NUM_BUFFERS; + z = (z + 1) % ao->numBuffers; } } @@ -193,8 +193,9 @@ static int close_win32(struct audio_output_struct *ao) waveOutClose(state->waveout); CloseHandle(state->play_done_event); - for(i = 0; i < NUM_BUFFERS; i++) free(state->buffer_headers[i].lpData); - + for(i = 0; i < ao->numBuffers; i++) free(state->buffer_headers[i].lpData); + free(state->buffer_headers); + free(ao->userptr); ao->userptr = 0; return 0; diff --git a/index.js b/index.js index bdfbb5a..7cd2157 100644 --- a/index.js +++ b/index.js @@ -147,6 +147,15 @@ Speaker.prototype._open = function () { debug('setting default %o: %o', 'signed', this.bitDepth != 8); this.signed = this.bitDepth != 8; } + if (null == this.bufferSize) { + debug('setting defualt %o: %o', 'bufferSize', 0x10000); + this.bufferSize = 0x10000; + } + + if (null == this.numBuffers) { + debug('setting default %o: %o', 'numBuffers', 8); + this.numBuffers = 8; + } var format = exports.getFormat(this); if (null == format) { @@ -163,7 +172,7 @@ Speaker.prototype._open = function () { // initialize the audio handle // TODO: open async? this.audio_handle = new Buffer(binding.sizeof_audio_output_t); - var r = binding.open(this.audio_handle, this.channels, this.sampleRate, format); + var r = binding.open(this.audio_handle, this.channels, this.sampleRate, format, this.bufferSize, this.numBuffers); if (0 !== r) { throw new Error('open() failed: ' + r); } @@ -203,6 +212,14 @@ Speaker.prototype._format = function (opts) { debug('setting %o: %o', "signed", opts.signed); this.signed = opts.signed; } + if (null != opts.bufferSize) { + debug('setting %o: %o', "bufferSize", opts.bufferSize); + this.bufferSize = opts.bufferSize; + } + if (null != opts.numBuffers) { + debug('setting %o: %o', "numBuffers", opts.numBuffers); + this.numBuffers = opts.numBuffers; + } if (null != opts.samplesPerFrame) { debug('setting %o: %o', "samplesPerFrame", opts.samplesPerFrame); this.samplesPerFrame = opts.samplesPerFrame; diff --git a/src/binding.cc b/src/binding.cc index de3ce45..3e04942 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -34,6 +34,8 @@ NAN_METHOD(Open) { ao->channels = args[1]->Int32Value(); /* channels */ ao->rate = args[2]->Int32Value(); /* sample rate */ ao->format = args[3]->Int32Value(); /* MPG123_ENC_* format */ + ao->bufferSize = args[4]->Int32Value(); + ao->numBuffers = args[5]->Int32Value(); /* init_output() */ r = mpg123_output_module_info.init_output(ao); @@ -118,6 +120,8 @@ void Initialize(Handle target) { ao.channels = 2; ao.rate = 44100; ao.format = MPG123_ENC_SIGNED_16; + ao.numBuffers = 8; + ao.bufferSize = 0x10000; ao.open(&ao); target->Set(NanNew("formats"), NanNew(ao.get_formats(&ao))); ao.close(&ao);