diff options
author | gnieboer <gnieboer@corpcomm.net> | 2016-03-28 02:30:21 +0300 |
---|---|---|
committer | gnieboer <gnieboer@corpcomm.net> | 2016-04-17 16:52:39 +0300 |
commit | 03669f5e7b4943e34909b1fed8d186ce2eebbf15 (patch) | |
tree | d1a63409267a395bc710fa1c4deadc5a4758e520 /gr-audio/lib | |
parent | 6efdc63d1843700d104a79b52d03c47a3d84c478 (diff) |
Completely refactored windows audio sink to use multiple buffers, eliminates skipping sounds when nperiods and period_time are set within reason. Default values should work fine for most. Set verbose=true to see additional info. output device can be set to either the ordinal number of the device to use, or the string name of the device. Setting a string in verbose mode will also display a list of choices in the console on run
Diffstat (limited to 'gr-audio/lib')
-rw-r--r-- | gr-audio/lib/windows/windows_sink.cc | 424 | ||||
-rw-r--r-- | gr-audio/lib/windows/windows_sink.h | 13 |
2 files changed, 246 insertions, 191 deletions
diff --git a/gr-audio/lib/windows/windows_sink.cc b/gr-audio/lib/windows/windows_sink.cc index 6598c973d4..4ec798b0ba 100644 --- a/gr-audio/lib/windows/windows_sink.cc +++ b/gr-audio/lib/windows/windows_sink.cc @@ -27,6 +27,8 @@ #include "audio_registry.h" #include <windows_sink.h> #include <gnuradio/io_signature.h> +#include <gnuradio/prefs.h> +#include <gnuradio/logger.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> @@ -36,27 +38,29 @@ #include <stdexcept> #include <string> #include <sstream> +#include "boost/lexical_cast.hpp" namespace gr { namespace audio { sink::sptr windows_sink_fcn(int sampling_rate, - const std::string &device_name, - bool) + const std::string &device_name, + bool) { return sink::sptr (new windows_sink(sampling_rate, device_name)); } - static const double CHUNK_TIME = 0.1; //0.001; // 100 ms - - // FIXME these should query some kind of user preference + static const double CHUNK_TIME = prefs::singleton()->get_double("audio_windows", "period_time", 0.1); // 100 ms (below 3ms distortion will likely occur regardless of number of buffers, will likely be a higher limit on slower machines) + static const int nPeriods = prefs::singleton()->get_long("audio_windows", "nperiods", 4); // 4 should be more than enough with a normal chunk time (2 will likely work as well)... at 3ms chunks 10 was enough on a fast machine + static const bool verbose = prefs::singleton()->get_bool("audio_windows", "verbose", false); + static const std::string default_device = prefs::singleton()->get_string("audio_windows", "standard_output_device", "default"); static std::string default_device_name() { - return "WAVE_MAPPER"; + return (default_device == "default" ? "WAVE_MAPPER" : default_device); } windows_sink::windows_sink(int sampling_freq, const std::string device_name) @@ -65,28 +69,63 @@ namespace gr { io_signature::make(0, 0, 0)), d_sampling_freq(sampling_freq), d_device_name(device_name.empty() ? default_device_name() : device_name), - d_fd(-1), d_buffer(0), d_chunk_size(0) + d_fd(-1), d_buffers(0), d_chunk_size(0) { + /* Initialize the WAVEFORMATEX for 16-bit, 44KHz, stereo */ + wave_format.wFormatTag = WAVE_FORMAT_PCM; + wave_format.nChannels = 2; // changing this will require adjustments to the work routine. + wave_format.wBitsPerSample = 16; // changing this will necessitate changing buffer type from short. + wave_format.nSamplesPerSec = d_sampling_freq; // 44100 is default but up to flowgraph settings; + wave_format.nBlockAlign = + wave_format.nChannels * (wave_format.wBitsPerSample / 8); + wave_format.nAvgBytesPerSec = + wave_format.nSamplesPerSec * wave_format.nBlockAlign; + wave_format.cbSize = 0; + + d_chunk_size = (int)(d_sampling_freq * CHUNK_TIME); // Samples per chunk + set_output_multiple(d_chunk_size); + d_buffer_size = d_chunk_size * wave_format.nChannels * (wave_format.wBitsPerSample / 8); // room for 16-bit audio on two channels. + d_wave_write_event = CreateEvent(NULL, FALSE, FALSE, NULL); - if(open_waveout_device() < 0) { - //fprintf(stderr, "audio_windows_sink:open_waveout_device() failed\n"); + if (open_waveout_device() < 0) { perror("audio_windows_sink:open_waveout_device() failed\n"); throw - std::runtime_error ("audio_windows_sink:open_waveout_device() failed"); + std::runtime_error("audio_windows_sink:open_waveout_device() failed"); } - - d_chunk_size = (int)(d_sampling_freq * CHUNK_TIME); - set_output_multiple(d_chunk_size); - - d_buffer = new short[d_chunk_size * 2]; + else if (verbose) { + GR_LOG_INFO(logger, "Opened windows waveout device"); + } + d_buffers = new LPWAVEHDR[nPeriods]; + for (int i = 0; i < nPeriods; i++) + { + d_buffers[i] = new WAVEHDR; + d_buffers[i]->dwLoops = 0L; + d_buffers[i]->dwFlags = WHDR_DONE; + d_buffers[i]->dwBufferLength = d_buffer_size; + d_buffers[i]->lpData = new CHAR[d_buffer_size]; + } + if (verbose) GR_LOG_INFO(logger, boost::format("Initialized %1% %2%ms audio buffers, total memory used: %3$0.2fkB") % (nPeriods) % (CHUNK_TIME * 1000) % ((d_buffer_size * nPeriods) / 1024.0)); } windows_sink::~windows_sink() { + // stop playback and set all buffers to DONE. + waveOutReset(d_h_waveout); + // Now we can deallocate the buffers + for (int i = 0; i < nPeriods; i++) + { + if (d_buffers[i]->dwFlags & (WHDR_DONE | WHDR_PREPARED)) { + waveOutUnprepareHeader(d_h_waveout, d_buffers[i], sizeof(d_buffers[i])); + } + else { + + } + delete d_buffers[i]->lpData; + } /* Free the callback Event */ CloseHandle(d_wave_write_event); waveOutClose(d_h_waveout); - delete [] d_buffer; + delete [] d_buffers; } int @@ -95,66 +134,71 @@ namespace gr { gr_vector_void_star & output_items) { const float *f0, *f1; - bool playtestsound = false; - if(playtestsound) { - // dummy - f0 = (const float*)input_items[0]; - for(int i = 0; i < noutput_items; i += d_chunk_size) { - for(int j = 0; j < d_chunk_size; j++) { - d_buffer[2*j + 0] = (short)(sin(2.0 * 3.1415926535897932384626 * - (float)j * 1000.0 / (float)d_sampling_freq) * - 8192 + 0); //+32767 - d_buffer[2*j + 1] = d_buffer[2*j + 0]; + // Pick the first available wave header (buffer) + // If none available, then wait until the processing event if fired and check again + // Not all events free up a buffer, so it could take more than one loop to get one + // however, to avoid a lock, only wait 1 second for a freed up buffer then abort. + LPWAVEHDR chosen_header = NULL; + int c = 0; + while (!chosen_header) + { + ResetEvent(d_wave_write_event); + for (int i = 0; i < nPeriods; i++) + { + if (d_buffers[i]->dwFlags & WHDR_DONE) { + // uncomment the below to see which buffers are being consumed + // printf("%d ", i); + chosen_header = d_buffers[i]; + break; } - f0 += d_chunk_size; - if(write_waveout - ((HPSTR)d_buffer, 2*d_chunk_size * sizeof(short)) < 0) { - fprintf(stderr, "audio_windows_sink: write failed\n"); - perror("audio_windows_sink: write failed"); + } + if (!chosen_header) { + WaitForSingleObject(d_wave_write_event, 100); + printf("aO"); + } + if (c++ > 10) { + for (int i = 0; i < nPeriods; i++) { + printf("%d: %d\n", i, d_buffers[i]->dwFlags); } - } - // break; + perror("audio_windows_sink: no audio buffers available"); + return -1; + } } - else { - switch(input_items.size ()) { - case 1: // mono input - f0 = (const float*)input_items[0]; - - for(int i = 0; i < noutput_items; i += d_chunk_size) { - for(int j = 0; j < d_chunk_size; j++) { - d_buffer[2*j + 0] = (short)(f0[j] * 32767); - d_buffer[2*j + 1] = (short)(f0[j] * 32767); - } - f0 += d_chunk_size; - if(write_waveout - ((HPSTR)d_buffer, 2*d_chunk_size * sizeof(short)) < 0) { - //fprintf(stderr, "audio_windows_sink: write failed\n"); - perror("audio_windows_sink: write failed"); - } - } - break; - case 2: // stereo input - f0 = (const float*)input_items[0]; - f1 = (const float*)input_items[1]; + short *d_buffer = (short *)chosen_header->lpData; - for(int i = 0; i < noutput_items; i += d_chunk_size) { - for(int j = 0; j < d_chunk_size; j++) { - d_buffer[2*j + 0] = (short)(f0[j] * 32767); - d_buffer[2*j + 1] = (short)(f1[j] * 32767); - } - f0 += d_chunk_size; - f1 += d_chunk_size; - if(write_waveout - ((HPSTR)d_buffer, 2*d_chunk_size * sizeof(short)) < 0) { - //fprintf(stderr, "audio_windows_sink: write failed\n"); - perror("audio_windows_sink: write failed"); - } + switch (input_items.size()) { + case 1: // mono input + f0 = (const float*)input_items[0]; + + for (int i = 0; i < noutput_items; i += d_chunk_size) { + for (int j = 0; j < d_chunk_size; j++) { + d_buffer[2 * j + 0] = (short)(f0[j] * 32767); + d_buffer[2 * j + 1] = (short)(f0[j] * 32767); } - break; - } + f0 += d_chunk_size; + } + break; + case 2: // stereo input + f0 = (const float*)input_items[0]; + f1 = (const float*)input_items[1]; + + for (int i = 0; i < noutput_items; i += d_chunk_size) { + for (int j = 0; j < d_chunk_size; j++) { + d_buffer[2 * j + 0] = (short)(f0[j] * 32767); + d_buffer[2 * j + 1] = (short)(f1[j] * 32767); + } + f0 += d_chunk_size; + f1 += d_chunk_size; + } + break; + } + if (write_waveout + (chosen_header) < 0) { + perror("audio_windows_sink: write failed"); } + return noutput_items; } @@ -162,154 +206,162 @@ namespace gr { windows_sink::string_to_int(const std::string & s) { int i; - std::istringstream (s) >> i; + std::istringstream(s) >> i; return i; - } //ToInt() + } - int - windows_sink::open_waveout_device(void) + MMRESULT windows_sink::is_format_supported(LPWAVEFORMATEX pwfx, UINT uDeviceID) { - UINT /*UINT_PTR */ u_device_id; - - /** Identifier of the waveform-audio output device to open. It - can be either a device identifier or a handle of an open - waveform-audio input device. You can use the following flag - instead of a device identifier. - * - * Value Meaning - * WAVE_MAPPER The function selects a waveform-audio output - * device capable of playing the given format. - */ - if(d_device_name.empty () || default_device_name () == d_device_name) - u_device_id = WAVE_MAPPER; - else - u_device_id = (UINT) string_to_int (d_device_name); - // Open a waveform device for output using event callback. - - unsigned long result; - //HWAVEOUT outHandle; - WAVEFORMATEX wave_format; + return (waveOutOpen( + NULL, // ptr can be NULL for query + uDeviceID, // the device identifier + pwfx, // defines requested format + NULL, // no callback + NULL, // no instance data + WAVE_FORMAT_QUERY)); // query only, do not open device + } - /* Initialize the WAVEFORMATEX for 16-bit, 44KHz, stereo */ - wave_format.wFormatTag = WAVE_FORMAT_PCM; - wave_format.nChannels = 2; - wave_format.nSamplesPerSec = d_sampling_freq; //44100; - wave_format.wBitsPerSample = 16; - wave_format.nBlockAlign = - wave_format.nChannels * (wave_format.wBitsPerSample / 8); - wave_format.nAvgBytesPerSec = - wave_format.nSamplesPerSec * wave_format.nBlockAlign; - wave_format.cbSize = 0; + bool windows_sink::is_number(const std::string& s) + { + std::string::const_iterator it = s.begin(); + while (it != s.end() && std::isdigit(*it)) ++it; + return !s.empty() && it == s.end(); + } - /* Open the (preferred) Digital Audio Out device. */ - result = waveOutOpen(&d_h_waveout, WAVE_MAPPER, - &wave_format, - (DWORD_PTR)d_wave_write_event, - 0, CALLBACK_EVENT | WAVE_ALLOWSYNC); - //|WAVE_FORMAT_DIRECT | CALLBACK_EVENT| WAVE_ALLOWSYNC + UINT windows_sink::find_device(std::string szDeviceName) + { + UINT result = -1; + UINT num_devices = waveOutGetNumDevs(); + if (num_devices > 0) { + // what the device name passed as a number? + if (is_number(szDeviceName)) + { + // a number, so must be referencing a device ID (which incremement from zero) + UINT num = std::stoul(szDeviceName); + if (num < num_devices) { + result = num; + } + else { + GR_LOG_INFO(logger, boost::format("Warning: waveOut deviceID %d was not found, defaulting to WAVE_MAPPER") % num); + result = WAVE_MAPPER; + } - if(result) { - //fprintf(stderr, "audio_windows_sink: Failed to open waveform output device.\n"); - perror("audio_windows_sink: Failed to open waveform output device."); - //LocalUnlock(hFormat); - //LocalFree(hFormat); - //mmioClose(hmmio, 0); - return -1; + } + else { + // device name passed as string + for (UINT i = 0; i < num_devices; i++) + { + WAVEOUTCAPS woc; + if (waveOutGetDevCaps(i, &woc, sizeof(woc)) != MMSYSERR_NOERROR) + { + perror("Error: Could not retrieve wave out device capabilities for device"); + return -1; + } + if (woc.szPname == szDeviceName) + { + result = i; + } + if (verbose) GR_LOG_INFO(logger, boost::format("WaveOut Device %d: %s") % i % woc.szPname); + } + if (result == -1) { + GR_LOG_INFO(logger, boost::format("Warning: waveOut device '%s' was not found, defaulting to WAVE_MAPPER") % szDeviceName); + result = WAVE_MAPPER; + } + } + } + else { + perror("Error: No WaveOut devices present or accessible"); } + return result; + } - // - // Do not Swallow the "open" event. - // - //WaitForSingleObject(d_wave_write_event, INFINITE); + int + windows_sink::open_waveout_device(void) + { + UINT u_device_id; + unsigned long result; - // Allocate and lock memory for the header. + /** Identifier of the waveform-audio output device to open. It + can be either a device identifier or a handle of an open + waveform-audio input device. You can use the following flag + instead of a device identifier. + WAVE_MAPPER The function selects a waveform-audio output + device capable of playing the given format. + */ + if (d_device_name.empty() || default_device_name() == d_device_name) + u_device_id = WAVE_MAPPER; + else + // The below could be uncommented to allow selection of different device handles + // however it is unclear what other devices are out there and how a user + // would know the device ID so at the moment we will ignore that setting + // and stick with WAVE_MAPPER + u_device_id = find_device(d_device_name); + if (verbose) GR_LOG_INFO(logger, boost::format("waveOut Device ID: %1%") % (u_device_id)); - d_h_wave_hdr = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, - (DWORD)sizeof(WAVEHDR)); - if(d_h_wave_hdr == NULL) { - //GlobalUnlock(hData); - //GlobalFree(hData); - //fprintf(stderr, "audio_windows_sink: Not enough memory for header.\n"); - perror("audio_windows_sink: Not enough memory for header."); + // Check if the sampling rate/bits/channels are good to go with the device. + MMRESULT supported = is_format_supported(&wave_format, u_device_id); + if (supported != MMSYSERR_NOERROR) { + char err_msg[50]; + waveOutGetErrorText(supported, err_msg, 50); + GR_LOG_INFO(logger, boost::format("format error: %s") % err_msg); + perror("audio_windows_sink: Requested audio format is not supported by device driver"); return -1; } - d_lp_wave_hdr = (LPWAVEHDR)GlobalLock(d_h_wave_hdr); - if(d_lp_wave_hdr == NULL) { - //GlobalUnlock(hData); - //GlobalFree(hData); - //fprintf(stderr, "audio_windows_sink: Failed to lock memory for header.\n"); - perror("audio_windows_sink: Failed to lock memory for header."); + // Open a waveform device for output using event callback. + result = waveOutOpen(&d_h_waveout, u_device_id, + &wave_format, + (DWORD_PTR)d_wave_write_event, + 0, CALLBACK_EVENT | WAVE_ALLOWSYNC); + + if (result) { + perror("audio_windows_sink: Failed to open waveform output device."); return -1; } - //d_lp_wave_hdr->dwFlags = WHDR_DONE; return 0; } int - windows_sink::write_waveout(HPSTR lp_data, DWORD dw_data_size) + windows_sink::write_waveout(LPWAVEHDR lp_wave_hdr) { UINT w_result; - int teller = 100; - // After allocation, set up and prepare header. - /*while ((d_lp_wave_hdr->dwFlags & WHDR_DONE)==0 && teller>0) { - teller--; - Sleep(1); - } */ - // Wait until previous wave write completes (first event is the open event). - WaitForSingleObject(d_wave_write_event, 100); // INFINITE - d_lp_wave_hdr->lpData = lp_data; - d_lp_wave_hdr->dwBufferLength = dw_data_size; - d_lp_wave_hdr->dwFlags = 0L; + /* Clear the WHDR_DONE bit (which the driver set last time that - this WAVEHDR was sent via waveOutWrite and was played). Some - drivers need this to be cleared */ - //d_lp_wave_hdr->dwFlags &= ~WHDR_DONE; + this WAVEHDR was sent via waveOutWrite and was played). Some + drivers need this to be cleared */ + lp_wave_hdr->dwFlags = 0L; - d_lp_wave_hdr->dwLoops = 0L; w_result = - waveOutPrepareHeader(d_h_waveout, d_lp_wave_hdr, sizeof(WAVEHDR)); - if(w_result != 0) { - //GlobalUnlock(hData); - //GlobalFree(hData); - //fprintf(stderr, "audio_windows_sink: Failed to waveOutPrepareHeader. error %i\n",w_result); + waveOutPrepareHeader(d_h_waveout, lp_wave_hdr, sizeof(WAVEHDR)); + if (w_result != 0) { perror("audio_windows_sink: Failed to waveOutPrepareHeader"); + return -1; } - // Now the data block can be sent to the output device. The - // waveOutWrite function returns immediately and waveform - // data is sent to the output device in the background. - //while(!readyforplayback) Sleep(1); - //readyforplayback=false; - - w_result = waveOutWrite(d_h_waveout, d_lp_wave_hdr, sizeof(WAVEHDR)); - if(w_result != 0) { - //GlobalUnlock(hData); - //GlobalFree(hData); - //fprintf(stderr, "audio_windows_sink: Failed to write block to device.error %i\n",w_result); + + w_result = waveOutWrite(d_h_waveout, lp_wave_hdr, sizeof(WAVEHDR)); + if (w_result != 0) { perror("audio_windows_sink: Failed to write block to device"); - switch(w_result) { - case MMSYSERR_INVALHANDLE: - fprintf(stderr, "Specified device handle is invalid.\n"); - break; - case MMSYSERR_NODRIVER: - fprintf(stderr, " No device driver is present.\n"); - break; - case MMSYSERR_NOMEM: - fprintf(stderr, " Unable to allocate or lock memory.\n"); - break; - case WAVERR_UNPREPARED: - fprintf(stderr, - " The data block pointed to by the pwh parameter hasn't been prepared.\n"); - break; - default: - fprintf(stderr, "Unknown error %i\n", w_result); - } - waveOutUnprepareHeader(d_h_waveout, d_lp_wave_hdr, sizeof(WAVEHDR)); + switch (w_result) { + case MMSYSERR_INVALHANDLE: + fprintf(stderr, "Specified device handle is invalid.\n"); + break; + case MMSYSERR_NODRIVER: + fprintf(stderr, " No device driver is present.\n"); + break; + case MMSYSERR_NOMEM: + fprintf(stderr, " Unable to allocate or lock memory.\n"); + break; + case WAVERR_UNPREPARED: + fprintf(stderr, + " The data block pointed to by the pwh parameter hasn't been prepared.\n"); + break; + default: + fprintf(stderr, "Unknown error %i\n", w_result); + } + waveOutUnprepareHeader(d_h_waveout, lp_wave_hdr, sizeof(WAVEHDR)); return -1; } - //WaitForSingleObject(d_wave_write_event, INFINITE); return 0; } - } /* namespace audio */ } /* namespace gr */ diff --git a/gr-audio/lib/windows/windows_sink.h b/gr-audio/lib/windows/windows_sink.h index 3d21cc47a3..2bfdbd318d 100644 --- a/gr-audio/lib/windows/windows_sink.h +++ b/gr-audio/lib/windows/windows_sink.h @@ -47,17 +47,20 @@ namespace gr { int d_sampling_freq; std::string d_device_name; int d_fd; - short *d_buffer; - int d_chunk_size; + LPWAVEHDR *d_buffers; + DWORD d_chunk_size; + DWORD d_buffer_size; HWAVEOUT d_h_waveout; - HGLOBAL d_h_wave_hdr; - LPWAVEHDR d_lp_wave_hdr; HANDLE d_wave_write_event; + WAVEFORMATEX wave_format; protected: int string_to_int(const std::string & s); int open_waveout_device(void); - int write_waveout(HPSTR lp_data, DWORD dw_data_size); + int write_waveout(LPWAVEHDR lp_wave_hdr); + MMRESULT is_format_supported(LPWAVEFORMATEX pwfx, UINT uDeviceID); + bool is_number(const std::string& s); + UINT find_device(std::string szDeviceName); public: windows_sink(int sampling_freq, |