Add support for recording to .flac
[ardour.git] / libs / backends / portaudio / portaudio_io.cc
index f0a6450c285c4fe11f3b4626d2c4f6560137982a..140de6cf973441c92cd0e7d41f1db9886b2c6b0e 100644 (file)
@@ -56,7 +56,7 @@ PortAudioIO::PortAudioIO ()
 
 PortAudioIO::~PortAudioIO ()
 {
-       pcm_stop();
+       close_stream();
 
        pa_deinitialize ();
        clear_device_lists ();
@@ -170,9 +170,9 @@ PortAudioIO::available_sample_rates(int device_id, std::vector<float>& sampleRat
 #ifdef WITH_ASIO
 bool
 PortAudioIO::get_asio_buffer_properties (int device_id,
-                                         long& min_size_frames,
-                                         long& max_size_frames,
-                                         long& preferred_size_frames,
+                                         long& min_size_samples,
+                                         long& max_size_samples,
+                                         long& preferred_size_samples,
                                          long& granularity)
 {
        // we shouldn't really need all these checks but it shouldn't hurt
@@ -191,9 +191,9 @@ PortAudioIO::get_asio_buffer_properties (int device_id,
        }
 
        PaError err = PaAsio_GetAvailableBufferSizes (device_id,
-                                                     &min_size_frames,
-                                                     &max_size_frames,
-                                                     &preferred_size_frames,
+                                                     &min_size_samples,
+                                                     &max_size_samples,
+                                                     &preferred_size_samples,
                                                      &granularity);
 
        if (err != paNoError) {
@@ -204,18 +204,27 @@ PortAudioIO::get_asio_buffer_properties (int device_id,
        return true;
 }
 
+static
 bool
-PortAudioIO::get_asio_buffer_sizes (int device_id, std::vector<uint32_t>& buffer_sizes)
+is_power_of_two (uint32_t v)
 {
-       long min_size_frames = 0;
-       long max_size_frames = 0;
-       long preferred_size_frames = 0;
+       return ((v != 0) && !(v & (v - 1)));
+}
+
+bool
+PortAudioIO::get_asio_buffer_sizes(int device_id,
+                                   std::vector<uint32_t>& buffer_sizes,
+                                   bool preferred_only)
+{
+       long min_size_samples = 0;
+       long max_size_samples = 0;
+       long preferred_size_samples = 0;
        long granularity = 0;
 
        if (!get_asio_buffer_properties (device_id,
-                                        min_size_frames,
-                                        max_size_frames,
-                                        preferred_size_frames,
+                                        min_size_samples,
+                                        max_size_samples,
+                                        preferred_size_samples,
                                         granularity)) {
                DEBUG_AUDIO (string_compose (
                    "Unable to get device buffer properties from device index %1\n", device_id));
@@ -223,34 +232,59 @@ PortAudioIO::get_asio_buffer_sizes (int device_id, std::vector<uint32_t>& buffer
        }
 
        DEBUG_AUDIO (string_compose ("ASIO buffer properties for device %1, "
-                                    "min_size_frames: %2, max_size_frames: %3, "
-                                    "preferred_size_frames: %4, granularity: %5\n",
+                                    "min_size_samples: %2, max_size_samples: %3, "
+                                    "preferred_size_samples: %4, granularity: %5\n",
                                     device_id,
-                                    min_size_frames,
-                                    max_size_frames,
-                                    preferred_size_frames,
+                                    min_size_samples,
+                                    max_size_samples,
+                                    preferred_size_samples,
                                     granularity));
 
-#ifdef USE_ASIO_MIN_MAX_BUFFER_SIZES
-       if (min_size_frames >= max_size_frames) {
-               buffer_sizes.push_back (preferred_size_frames);
+       bool driver_returns_one_size = (min_size_samples == max_size_samples) &&
+                                      (min_size_samples == preferred_size_samples);
+
+       if (preferred_only || driver_returns_one_size) {
+               buffer_sizes.push_back(preferred_size_samples);
                return true;
        }
 
-       long buffer_size = min_size_frames;
-       while (buffer_size <= max_size_frames) {
-               buffer_sizes.push_back (buffer_size);
+       long buffer_size = min_size_samples;
+
+       // If min size and granularity are power of two then just use values that
+       // are power of 2 even if the granularity allows for more values
+       bool use_power_of_two =
+           is_power_of_two(min_size_samples) && is_power_of_two(granularity);
 
-               if (granularity <= 0) {
-                       // buffer sizes are power of 2
-                       buffer_size = buffer_size * 2;
-               } else {
+       if (granularity <= 0 || use_power_of_two) {
+               // driver uses buffer sizes that are power of 2
+               while (buffer_size <= max_size_samples) {
+                       buffer_sizes.push_back(buffer_size);
+                       buffer_size *= 2;
+               }
+       } else {
+               if (min_size_samples == max_size_samples) {
+                       // The devices I have tested either return the same values for
+                       // min/max/preferred and changing buffer size is intended to only be
+                       // done via the control dialog or they return a range where min != max
+                       // but I guess min == max could happen if a driver only supports a single
+                       // buffer size
+                       buffer_sizes.push_back(min_size_samples);
+                       return true;
+               }
+
+               // If min_size_samples is not power of 2 use at most 8 of the possible
+               // buffer sizes spread evenly between min and max
+               long max_values = 8;
+               while (((max_size_samples - min_size_samples) / granularity) > max_values) {
+                       granularity *= 2;
+               }
+
+               while (buffer_size < max_size_samples) {
+                       buffer_sizes.push_back(buffer_size);
                        buffer_size += granularity;
                }
+               buffer_sizes.push_back(max_size_samples);
        }
-#else
-       buffer_sizes.push_back (preferred_size_frames);
-#endif
        return true;
 }
 #endif
@@ -272,7 +306,7 @@ PortAudioIO::available_buffer_sizes(int device_id, std::vector<uint32_t>& buffer
 {
 #ifdef WITH_ASIO
        if (get_current_host_api_type() == paASIO) {
-               if (get_asio_buffer_sizes (device_id, buffer_sizes)) {
+               if (get_asio_buffer_sizes (device_id, buffer_sizes, false)) {
                        return 0;
                }
        }
@@ -527,15 +561,23 @@ PortAudioIO::add_devices ()
        }
 }
 
-void
-PortAudioIO::discover()
+bool
+PortAudioIO::update_devices()
 {
-       DEBUG_AUDIO ("PortAudio: discover\n");
-       if (!pa_initialize()) return;
+       DEBUG_AUDIO ("Update devices\n");
+       if (_stream != NULL) return false;
+       pa_deinitialize();
+       if (!pa_initialize()) return false;
 
        clear_device_lists ();
-       add_none_devices ();
+
+       // ASIO doesn't support separate input/output devices so adding None
+       // doesn't make sense
+       if (get_current_host_api_type() != paASIO) {
+               add_none_devices ();
+       }
        add_devices ();
+       return true;
 }
 
 void
@@ -548,11 +590,15 @@ PortAudioIO::reset_stream_dependents ()
        _cur_output_latency = 0;
 }
 
-void
-PortAudioIO::pcm_stop ()
+PaErrorCode
+PortAudioIO::close_stream()
 {
-       if (_stream) {
-               Pa_CloseStream (_stream);
+       if (!_stream) return paNoError;
+
+       PaError err = Pa_CloseStream (_stream);
+
+       if (err != paNoError) {
+               return (PaErrorCode)err;
        }
        _stream = NULL;
 
@@ -560,17 +606,20 @@ PortAudioIO::pcm_stop ()
 
        free (_input_buffer); _input_buffer = NULL;
        free (_output_buffer); _output_buffer = NULL;
+       return paNoError;
 }
 
-int
-PortAudioIO::pcm_start()
+PaErrorCode
+PortAudioIO::start_stream()
 {
        PaError err = Pa_StartStream (_stream);
 
        if (err != paNoError) {
-               return -1;
+               DEBUG_AUDIO(string_compose("PortAudio failed to start stream %1\n",
+                                          Pa_GetErrorText(err)));
+               return (PaErrorCode)err;
        }
-       return 0;
+       return paNoError;
 }
 
 bool
@@ -689,14 +738,15 @@ PortAudioIO::get_output_stream_params(int device_output,
        return true;
 }
 
-PortAudioIO::ErrorCode
-PortAudioIO::pcm_setup (
-               int device_input, int device_output,
-               double sample_rate, uint32_t samples_per_period)
+PaErrorCode
+PortAudioIO::pre_stream_open(int device_input,
+                             PaStreamParameters& inputParam,
+                             int device_output,
+                             PaStreamParameters& outputParam)
 {
        if (!pa_initialize()) {
                DEBUG_AUDIO ("PortAudio Initialization Failed\n");
-               return InitializationError;
+               return paNotInitialized;
        }
 
        reset_stream_dependents ();
@@ -705,12 +755,9 @@ PortAudioIO::pcm_setup (
            "PortAudio Device IDs: i:%1 o:%2\n", device_input, device_output));
 
        if (device_input == DeviceNone && device_output == DeviceNone) {
-               return DeviceConfigNotSupportedError;
+               return paBadIODeviceCombination;
        }
 
-       PaStreamParameters inputParam;
-       PaStreamParameters outputParam;
-
        if (get_input_stream_params(device_input, inputParam)) {
                _capture_channels = inputParam.channelCount;
        }
@@ -721,16 +768,76 @@ PortAudioIO::pcm_setup (
 
        if (_capture_channels == 0 && _playback_channels == 0) {
                DEBUG_AUDIO("PortAudio no input or output channels.\n");
-               return DeviceConfigNotSupportedError;
+               return paBadIODeviceCombination;
        }
 
        DEBUG_AUDIO (string_compose ("PortAudio Channels: in:%1 out:%2\n",
                                     _capture_channels,
                                     _playback_channels));
 
+       return paNoError;
+}
+
+PaErrorCode
+PortAudioIO::open_callback_stream(int device_input,
+                                  int device_output,
+                                  double sample_rate,
+                                  uint32_t samples_per_period,
+                                  PaStreamCallback* callback,
+                                  void* data)
+{
+       PaStreamParameters inputParam;
+       PaStreamParameters outputParam;
+
+       PaErrorCode error_code =
+           pre_stream_open(device_input, inputParam, device_output, outputParam);
+
+       if (error_code != paNoError) return error_code;
+
+       PaError err = paNoError;
+
+       DEBUG_AUDIO ("Open Callback Stream\n");
+
+       err = Pa_OpenStream(&_stream,
+                           _capture_channels > 0 ? &inputParam : NULL,
+                           _playback_channels > 0 ? &outputParam : NULL,
+                           sample_rate,
+                           samples_per_period,
+                           paDitherOff,
+                           callback,
+                           data);
+
+       if (err != paNoError) {
+               DEBUG_AUDIO(string_compose("PortAudio failed to open stream %1\n",
+                                          Pa_GetErrorText(err)));
+               return paInternalError;
+       }
+
+       if (!set_sample_rate_and_latency_from_stream()) {
+               DEBUG_AUDIO ("PortAudio failed to query stream information.\n");
+               close_stream();
+               return paInternalError;
+       }
+
+       return paNoError;
+}
+
+PaErrorCode
+PortAudioIO::open_blocking_stream(int device_input,
+                                  int device_output,
+                                  double sample_rate,
+                                  uint32_t samples_per_period)
+{
+       PaStreamParameters inputParam;
+       PaStreamParameters outputParam;
+
+       PaErrorCode error_code =
+           pre_stream_open(device_input, inputParam, device_output, outputParam);
+
+       if (error_code != paNoError) return error_code;
+
        PaError err = paNoError;
 
-       // XXX re-consider using callback API, testing needed.
        err = Pa_OpenStream (
                        &_stream,
                        _capture_channels > 0 ? &inputParam: NULL,
@@ -741,21 +848,22 @@ PortAudioIO::pcm_setup (
                        NULL, NULL);
 
        if (err != paNoError) {
-               DEBUG_AUDIO ("PortAudio failed to start stream.\n");
-               return StreamOpenError;
+               DEBUG_AUDIO(string_compose("PortAudio failed to open stream %1\n",
+                                          Pa_GetErrorText(err)));
+               return (PaErrorCode)err;
        }
 
        if (!set_sample_rate_and_latency_from_stream()) {
                DEBUG_AUDIO ("PortAudio failed to query stream information.\n");
-               pcm_stop();
-               return StreamOpenError;
+               close_stream();
+               return paInternalError;
        }
 
        if (!allocate_buffers_for_blocking_api(samples_per_period)) {
-               pcm_stop();
-               return StreamOpenError;
+               close_stream();
+               return paInternalError;
        }
-       return NoError;
+       return paNoError;
 }
 
 int