Nsound  0.9.4
AudioBackendLibportaudio.cc
Go to the documentation of this file.
1 //-----------------------------------------------------------------------------
2 //
3 // $Id: AudioBackendLibportaudio.cc 875 2014-09-27 22:25:13Z weegreenblobbie $
4 //
5 // Copyright (c) 2005-2006 Nick Hilton
6 //
7 // weegreenblobbie_yahoo_com (replace '_' with '@' and '.')
8 //
9 //-----------------------------------------------------------------------------
10 
11 //-----------------------------------------------------------------------------
12 //
13 // This program is free software; you can redistribute it and/or modify
14 // it under the terms of the GNU General Public License as published by
15 // the Free Software Foundation; either version 2 of the License, or
16 // (at your option) any later version.
17 //
18 // This program is distributed in the hope that it will be useful,
19 // but WITHOUT ANY WARRANTY; without even the implied warranty of
20 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 // GNU Library General Public License for more details.
22 //
23 // You should have received a copy of the GNU General Public License
24 // along with this program; if not, write to the Free Software
25 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26 //
27 //-----------------------------------------------------------------------------
28 
29 //-----------------------------------------------------------------------------
30 //
31 // Some PortAudio notes:
32 //
33 // A "frame" consists of all the samples for a given time step. If you have
34 // stereo audio, then frames 1 and 2 will contain:
35 // frame 0 [left_sample_0, right_sample0]
36 // frame 1 [left_sample_1, right_sample1]
37 // ...
38 //
39 // The data above might contain 4 samples, but only 2 frames, or 2 samples in
40 // time.
41 //
42 //-----------------------------------------------------------------------------
43 
44 
45 #include <Nsound/Nsound.h>
47 #include <Nsound/AudioPlayback.h>
48 
49 #include <algorithm>
50 #include <iostream>
51 #include <string>
52 
53 #include <time.h>
54 
55 #ifdef NSOUND_LIBPORTAUDIO
56  #include <portaudio.h>
57 #endif
58 
59 using namespace Nsound;
60 
61 using std::cerr;
62 using std::cout;
63 using std::endl;
64 
65 #ifndef NSOUND_PLATFORM_OS_WINDOWS
66  #pragma GCC diagnostic ignored "-Wold-style-cast"
67 #endif
68 
69 #ifdef NSOUND_PLATFORM_OS_WINDOWS
70  extern "C"
71  {
72  void ___chkstk_ms(){}
73  void __chkstk_ms(){}
74  }
75 #endif
76 
77 //-----------------------------------------------------------------------------
80  uint32 sample_rate,
81  uint32 channels,
82  uint32 bits_per_sample)
83  :
84  AudioBackend(sample_rate, channels, bits_per_sample),
85  options_(),
86  error_buffer_(""),
87  out_params_(NULL),
88  stream_(NULL),
89 //~ pos_(0),
90  n_frames_per_buffer_(64),
91  driver_id_(0)
92 {
93  initialize();
94 };
95 
96 //-----------------------------------------------------------------------------
99 {
100  if(state_ == BACKEND_READY)
101  {
102  shutdown();
103  }
104 }
105 
109 {
111 }
112 
113 //-----------------------------------------------------------------------------
114 std::string
117 {
118  return error_buffer_.str();
119 }
120 
121 //-----------------------------------------------------------------------------
122 std::string
125 {
126  #ifndef NSOUND_LIBPORTAUDIO
128  M_THROW("Nsound::AudioBackendLibportaudio::getInfo():"
129  << ": Nsound was not compiled with libportaudio support.\n");
130  return;
131  #else
133  {
134  M_THROW("Nsound::AudioBackendLibportaudio::getInfo():"
135  << ": backend not initialized yet\n");
136  return "";
137  }
138  else if(state_ == BACKEND_ERROR)
139  {
140  return getError();
141  }
142 
143  const PaDeviceInfo * info = NULL;
144 
145  info = Pa_GetDeviceInfo(out_params_->device);
146 
147  if(info == NULL)
148  {
149  M_THROW("Nsound::AudioBackendLibportaudio::getInfo():"
150  << ": Pa_GetDeviceInfo() failed\n");
151 
152  return "";
153  }
154 
155  std::stringstream ss;
156 
157  ss << "libportaudio Default Driver Info:" << endl
158  << " deviceCount: " << Pa_GetDeviceCount() << endl
159  << " structVersion: " << info->structVersion << endl
160  << " name: " << info->name << endl
161  << " hostApi(index): " << info->hostApi << endl
162  << " maxInputChannels: " << info->maxInputChannels << endl
163  << " maxOutputChannels: " << info->maxOutputChannels << endl
164  << " defaultLowInputLatency: " << info->defaultLowInputLatency << endl
165  << " defaultHighInputLatency: " << info->defaultHighInputLatency << endl
166  << " defaultLowOutputLatency: " << info->defaultLowOutputLatency << endl
167  << " defaultHighOutputLatency: " << info->defaultHighOutputLatency << endl
168  << " defaultSampleRate: " << info->defaultSampleRate << endl;
169 
170  const PaHostApiInfo * api_info = NULL;
171 
172  api_info = Pa_GetHostApiInfo(info->hostApi);
173 
174  if(info == NULL)
175  {
176  M_THROW("Nsound::AudioBackendLibportaudio::getInfo():"
177  << ": Pa_GetHostApiInfo() failed\n");
178  return ss.str();
179  }
180 
181  ss << "libportaudio Default Host API Info:" << endl
182  << " structVersion: " << api_info->structVersion << endl
183  << " name: " << api_info->name << endl
184  << " deviceCount: " << api_info->deviceCount << endl
185  << " defaultInputDevice: " << api_info->defaultInputDevice << endl
186  << " defaultOutputDevice: " << api_info->defaultOutputDevice << endl;
187 
188  return ss.str();
189  #endif
190 }
191 
192 void
195 {
196  #ifndef NSOUND_LIBPORTAUDIO
198  M_THROW("Nsound::AudioBackendLibportaudio::getInfo():"
199  << ": Nsound was not compiled with libportaudio support.\n");
200  return;
201  #else
202 
204  {
205  return;
206  }
207 
208  std::stringstream ss;
209 
210  PaError ecode = Pa_Initialize();
211 
212  if(ecode != paNoError)
213  {
215  << "Nsound::AudioBackendLibportaudio::initialize():"
216  << __LINE__
217  << ": Pa_Initialize() failed"
218  << endl
219  << Pa_GetErrorText(ecode)
220  << endl;
221 
223  return;
224  }
225 
226  // Alocate params object
228 
229  if(out_params_ == NULL)
230  {
232  << "Nsound::AudioBackendLibportaudio::initialize():"
233  << __LINE__
234  << ": failed to allocate output params struct."
235  << endl;
236 
238 
239  Pa_Terminate();
240  return;
241  }
242 
243  // Get the default ouput device
244  out_params_->device = Pa_GetDefaultOutputDevice();
245 
246  if(out_params_->device == paNoDevice)
247  {
249  << "Nsound::AudioBackendLibportaudio::initialize():"
250  << __LINE__
251  << ": Pa_GetDefaultOutputDevice() failed"
252  << endl;
253 
255 
256  Pa_Terminate();
257 
258  delete out_params_;
259  out_params_ = NULL;
260 
261  return;
262  }
263 
264  out_params_->channelCount = channels_;
265 
266  switch(bits_per_sample_)
267  {
268  case 8:
269  out_params_->sampleFormat = paInt8;
270  break;
271 
272  case 16:
273  out_params_->sampleFormat = paInt16;
274  break;
275 
276  case 32:
277  out_params_->sampleFormat = paInt32;
278  break;
279 
280  default:
282  << "Nsound::AudioBackendLibportaudio::initialize():"
283  << __LINE__
284  << ": can't handle " << bits_per_sample_
285  << " bits per sample"
286  << endl;
287 
289 
290  Pa_Terminate();
291 
292  delete out_params_;
293  out_params_ = NULL;
294 
295  return;
296  }
297 
298  out_params_->suggestedLatency =
299  Pa_GetDeviceInfo(out_params_->device)->defaultHighOutputLatency;
300 
301  out_params_->hostApiSpecificStreamInfo = NULL;
302 
303  // Open the stream.
304  ecode = Pa_OpenStream(
305  & stream_,
306  NULL, // no input channel
307  out_params_,
308  static_cast<double>(sample_rate_),
310  paClipOff,
311  NULL, // no callback for now
312  NULL);
313 
314  if(ecode != paNoError)
315  {
317  << "Nsound::AudioBackendLibportaudio::initialize():"
318  << __LINE__
319  << ": Pa_OpenStream() failed"
320  << endl
321  << Pa_GetErrorText(ecode)
322  << endl;
323 
325 
326  Pa_Terminate();
327 
328  delete out_params_;
329  out_params_ = NULL;
330 
331  return;
332  }
333 
335  #endif
336 }
337 
338 //-----------------------------------------------------------------------------
339 // A function to perform the actual writting since can't perform pointer
340 // arithmetic on void *
341 PaError
343  PaStream * stream,
344  void * data,
345  uint32 position,
346  uint32 n_frames_per_buffer,
347  PaSampleFormat format)
348 {
349  switch(format)
350  {
351  case paInt8:
352  {
353  int8 * data_ptr = reinterpret_cast<int8 *>(data);
354  return Pa_WriteStream(
355  stream,
356  reinterpret_cast<void *>(data_ptr + position),
357  n_frames_per_buffer);
358  }
359 
360  case paInt16:
361  {
362  int16 * data_ptr = reinterpret_cast<int16 *>(data);
363  return Pa_WriteStream(
364  stream,
365  reinterpret_cast<void *>(data_ptr + position),
366  n_frames_per_buffer);
367  }
368 
369  case paInt32:
370  {
371  int32 * data_ptr = reinterpret_cast<int32 *>(data);
372  return Pa_WriteStream(
373  stream,
374  reinterpret_cast<void *>(data_ptr + position),
375  n_frames_per_buffer);
376  }
377  }
378 
379  M_THROW("Nsound::AudioBackendLibportaudio::initialize():"
380  << ": can't handle bits per sample type: "
381  << format
382  << "\n");
383 
384  return paSampleFormatNotSupported;
385 }
386 
387 void
389 play(void * data, uint32 n_bytes)
390 {
391  #ifndef NSOUND_LIBPORTAUDIO
393  M_THROW("Nsound::AudioBackendLibportaudio::getInfo():"
394  << ": Nsound was not compiled with libportaudio support.\n");
395  return;
396  #else
397 
398  if(state_ != BACKEND_READY)
399  {
400  return;
401  }
402 
403  if(n_bytes == 0)
404  {
405  return;
406  }
407 
408  if(data == NULL)
409  {
411  << "Nsound::AudioBackendLibportaudio::play():"
412  << __LINE__
413  << ": data is NULL"
414  << endl;
415 
417 
418  return;
419  }
420 
421  // Start the stream
422  PaError ecode = Pa_StartStream(stream_);
423 
424  if(ecode != paNoError)
425  {
427  << "Nsound::AudioBackendLibportaudio::play():"
428  << __LINE__
429  << ": Pa_StartStream() failed"
430  << endl
431  << Pa_GetErrorText(ecode)
432  << endl;
433 
435 
436  Pa_CloseStream(stream_);
437  Pa_Terminate();
438 
439  delete out_params_;
440  out_params_ = NULL;
441 
442  return;
443  }
444 
445  uint32 bits = 0;
446 
447  switch(out_params_->sampleFormat)
448  {
449  case paInt8:
450  bits = 8;
451  break;
452 
453  case paInt16:
454  bits = 16;
455  break;
456 
457  case paInt32:
458  bits = 32;
459  break;
460 
461  default:
463  << "Nsound::AudioBackendLibportaudio::initialize():"
464  << __LINE__
465  << ": can't handle bits per sample type: "
466  << out_params_->sampleFormat
467  << endl;
468 
470 
471  Pa_CloseStream(stream_);
472  Pa_Terminate();
473 
474  delete out_params_;
475  out_params_ = NULL;
476 
477  return;
478  }
479 
480  // Calculate the number of samples.
481  uint32 n_channels = out_params_->channelCount;
482  uint32 bytes_per_sample = bits / 8;
483  uint32 n_samples = n_bytes / bytes_per_sample / n_channels;
484 
485  uint32 i = 0;
486  for(
487  i = 0;
488  i < n_samples - n_frames_per_buffer_;
490  {
491  ecode = write_stream(
492  stream_,
493  data,
494  i * n_channels,
495  n_frames_per_buffer_,
496  out_params_->sampleFormat);
497 
498  if(ecode != paNoError)
499  {
501  << "Nsound::AudioBackendLibportaudio::play():"
502  << __LINE__
503  << ": Pa_WriteStream() failed"
504  << endl
505  << Pa_GetErrorText(ecode)
506  << endl;
507 
509 
510  Pa_StopStream(stream_);
511  Pa_CloseStream(stream_);
512  Pa_Terminate();
513 
514  delete out_params_;
515  out_params_ = NULL;
516 
517  return;
518  }
519  }
520 
521  // Write out remaining samples
522  ecode = write_stream(
523  stream_,
524  data,
525  i,
526  n_samples - i,
527  out_params_->sampleFormat);
528 
529  if(ecode != paNoError)
530  {
532  << "Nsound::AudioBackendLibportaudio::play():"
533  << __LINE__
534  << ": Pa_WriteStream() failed"
535  << endl
536  << Pa_GetErrorText(ecode)
537  << endl;
538 
540 
541  Pa_StopStream(stream_);
542  Pa_CloseStream(stream_);
543  Pa_Terminate();
544 
545  delete out_params_;
546  out_params_ = NULL;
547 
548  return;
549  }
550 
551  // Wait 500 ms to allow the stream to finish playing.
552  //
553  // At least on my box the playback is always cut off, especially when
554  // using Audacity, it's playback is cut off, perhaps this is releated
555  // to the audio driver. I'll leave this in for now.
556  //
557 
558  // POSIX only implementation
559  #if defined(NSOUND_PLATFORM_OS_LINUX)
560  timespec wait_time;
561  wait_time.tv_sec = 0;
562  wait_time.tv_nsec = 500000000;
563 
564  nanosleep(&wait_time, NULL);
565  #endif
566 
567  // Stop the stream
568  ecode = Pa_StopStream(stream_);
569 
570  if(ecode != paNoError)
571  {
573  << "Nsound::AudioBackendLibportaudio::play():"
574  << __LINE__
575  << ": Pa_StopStream() failed"
576  << endl
577  << Pa_GetErrorText(ecode)
578  << endl;
579 
581 
582  Pa_CloseStream(stream_);
583  Pa_Terminate();
584 
585  delete out_params_;
586  out_params_ = NULL;
587 
588  return;
589  }
590 
591  #endif
592 }
593 
594 //-----------------------------------------------------------------------------
595 std::string
596 static
597 lower(const std::string & x)
598 {
599  std::string y = x;
600  std::transform(y.begin(), y.end(), y.begin(), ::tolower);
601  return y;
602 }
603 
604 void
606 scanDevices(AudioPlayback & pb, const AudioStream & test_clip)
607 {
608  uint32 orig_driver_id = driver_id_;
609 
610  uint32 n_host_apis = Pa_GetHostApiCount();
611 
612  for(uint32 i = 0; i < n_host_apis; ++i)
613  {
614  const PaHostApiInfo * info = Pa_GetHostApiInfo(i);
615 
616  if(info != NULL)
617  {
618  std::string short_name = lower(std::string(info->name));
619 
620  cout
621  << "Libportaudio: found driver '"
622  << short_name
623  << "', id = "
624  << i
625  << ", deviceCount = "
626  << info->deviceCount
627  << ", defaultOutputDevice = "
628  << info->defaultOutputDevice
629  << "\nPLAYBACK STARTING ...";
630  cout.flush();
631 
632  shutdown();
633 
634  driver_id_ = i;
635 
636  initialize();
637 
638  pb.play(test_clip);
639 
640  cout << " STOPPED\n";
641  cout.flush();
642  }
643  }
644 
645  shutdown();
646  driver_id_ = orig_driver_id;
647 }
648 
649 //-----------------------------------------------------------------------------
650 int32
651 static
652 integer(const std::string & x, Nsound::AudioBackend::State * state)
653 {
654  std::stringstream ss(x);
655  int32 i = 0;
656 
657  ss >> i;
658 
659  if(ss.fail())
660  {
662  M_THROW("Nsound::AudioBackendLibportaudio::setOption():"
663  << ": could not convert '"
664  << x
665  << "' to an integer.\n");
666  return -1;
667  }
668 
669  return i;
670 }
671 
672 void
674 setOption(const std::string & key, const std::string & value)
675 {
676  #ifndef NSOUND_LIBPORTAUDIO
678  M_THROW("Nsound::AudioBackendLibportaudio::getInfo():"
679  << ": Nsound was not compiled with libportaudio support.\n");
680  return;
681  #else
682 
683  std::string k = lower(key);
684 
685  if(k == "frames_per_buffer")
686  {
687  int32 i = integer(value, &state_);
688 
689  if(i >= 1)
690  {
692  }
693  }
694  else
695  if(k == "driver")
696  {
697  std::string driver_name = lower(value);
698 
699  uint32 n_host_apis = Pa_GetHostApiCount();
700 
701  bool found_driver = false;
702 
703  for(uint32 i = 0; i < n_host_apis; ++i)
704  {
705  const PaHostApiInfo * info = Pa_GetHostApiInfo(i);
706 
707  if(info != NULL)
708  {
709  std::string short_name = lower(std::string(info->name));
710 
711  if(driver_name == short_name)
712  {
713  driver_id_ = i;
714  found_driver = true;
715  }
716  }
717  }
718 
719  if(!found_driver)
720  {
722  M_THROW("Nsound::AudioBackendLibportaudio::setOption():"
723  << ": failed to select driver '"
724  << key
725  << "'\n");
726  return;
727  }
728  }
729  else
730  {
732  M_THROW("Nsound::AudioBackendLibportaudio::setOption():"
733  << ": unrecognized key '"
734  << key
735  << "'\n");
736  return;
737  }
738 
739  #endif
740 }
741 
742 void
745 {
746  #ifndef NSOUND_LIBPORTAUDIO
748  M_THROW("Nsound::AudioBackendLibportaudio::getInfo():"
749  << ": Nsound was not compiled with libportaudio support.\n");
750  return;
751  #else
752 
753  if(stream_ != NULL)
754  {
755  Pa_CloseStream(stream_);
756  Pa_Terminate();
757 
758  stream_ = NULL;
759 
760  delete out_params_;
761  out_params_ = NULL;
762  }
763 
765 
766  #endif
767 }
unsigned int uint32
Definition: Nsound.h:153
static std::string lower(const std::string &x)
AudioBackendLibportaudio(uint32 sample_rate=44100, uint32 channels=1, uint32 bits_per_sample=16)
PaError write_stream(PaStream *stream, void *data, uint32 position, uint32 n_frames_per_buffer, PaSampleFormat format)
static int32 integer(const std::string &x, Nsound::AudioBackend::State *state)
signed char int8
Definition: Nsound.h:140
signed short int16
Definition: Nsound.h:141
void setOption(const std::string &key, const std::string &value)
Set Libportaudio options.
void scanDevices(AudioPlayback &pb, const AudioStream &test_clip)
#define M_THROW(message)
Definition: Macros.h:108
signed int int32
Definition: Nsound.h:142
void play(void *data, uint32 n_bytes)
void play(const AudioStream &a)
Plays the AudioStream throuh the backend.