Home | History | Annotate | Download | only in pulse
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "media/audio/pulse/pulse_util.h"
      6 
      7 #include "base/logging.h"
      8 #include "base/time/time.h"
      9 #include "media/audio/audio_manager_base.h"
     10 #include "media/audio/audio_parameters.h"
     11 
     12 namespace media {
     13 
     14 namespace pulse {
     15 
     16 namespace {
     17 
     18 pa_channel_position ChromiumToPAChannelPosition(Channels channel) {
     19   switch (channel) {
     20     // PulseAudio does not differentiate between left/right and
     21     // stereo-left/stereo-right, both translate to front-left/front-right.
     22     case LEFT:
     23       return PA_CHANNEL_POSITION_FRONT_LEFT;
     24     case RIGHT:
     25       return PA_CHANNEL_POSITION_FRONT_RIGHT;
     26     case CENTER:
     27       return PA_CHANNEL_POSITION_FRONT_CENTER;
     28     case LFE:
     29       return PA_CHANNEL_POSITION_LFE;
     30     case BACK_LEFT:
     31       return PA_CHANNEL_POSITION_REAR_LEFT;
     32     case BACK_RIGHT:
     33       return PA_CHANNEL_POSITION_REAR_RIGHT;
     34     case LEFT_OF_CENTER:
     35       return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
     36     case RIGHT_OF_CENTER:
     37       return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
     38     case BACK_CENTER:
     39       return PA_CHANNEL_POSITION_REAR_CENTER;
     40     case SIDE_LEFT:
     41       return PA_CHANNEL_POSITION_SIDE_LEFT;
     42     case SIDE_RIGHT:
     43       return PA_CHANNEL_POSITION_SIDE_RIGHT;
     44     default:
     45       NOTREACHED() << "Invalid channel: " << channel;
     46       return PA_CHANNEL_POSITION_INVALID;
     47   }
     48 }
     49 
     50 }  // namespace
     51 
     52 // static, pa_stream_success_cb_t
     53 void StreamSuccessCallback(pa_stream* s, int error, void* mainloop) {
     54   pa_threaded_mainloop* pa_mainloop =
     55       static_cast<pa_threaded_mainloop*>(mainloop);
     56   pa_threaded_mainloop_signal(pa_mainloop, 0);
     57 }
     58 
     59 // |pa_context| and |pa_stream| state changed cb.
     60 void ContextStateCallback(pa_context* context, void* mainloop) {
     61   pa_threaded_mainloop* pa_mainloop =
     62       static_cast<pa_threaded_mainloop*>(mainloop);
     63   pa_threaded_mainloop_signal(pa_mainloop, 0);
     64 }
     65 
     66 pa_sample_format_t BitsToPASampleFormat(int bits_per_sample) {
     67   switch (bits_per_sample) {
     68     case 8:
     69       return PA_SAMPLE_U8;
     70     case 16:
     71       return PA_SAMPLE_S16LE;
     72     case 24:
     73       return PA_SAMPLE_S24LE;
     74     case 32:
     75       return PA_SAMPLE_S32LE;
     76     default:
     77       NOTREACHED() << "Invalid bits per sample: " << bits_per_sample;
     78       return PA_SAMPLE_INVALID;
     79   }
     80 }
     81 
     82 pa_channel_map ChannelLayoutToPAChannelMap(ChannelLayout channel_layout) {
     83   pa_channel_map channel_map;
     84   pa_channel_map_init(&channel_map);
     85 
     86   channel_map.channels = ChannelLayoutToChannelCount(channel_layout);
     87   for (Channels ch = LEFT; ch <= CHANNELS_MAX;
     88        ch = static_cast<Channels>(ch + 1)) {
     89     int channel_index = ChannelOrder(channel_layout, ch);
     90     if (channel_index < 0)
     91       continue;
     92 
     93     channel_map.map[channel_index] = ChromiumToPAChannelPosition(ch);
     94   }
     95 
     96   return channel_map;
     97 }
     98 
     99 void WaitForOperationCompletion(pa_threaded_mainloop* pa_mainloop,
    100                                 pa_operation* operation) {
    101   if (!operation) {
    102     DLOG(WARNING) << "Operation is NULL";
    103     return;
    104   }
    105 
    106   while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
    107     pa_threaded_mainloop_wait(pa_mainloop);
    108 
    109   pa_operation_unref(operation);
    110 }
    111 
    112 int GetHardwareLatencyInBytes(pa_stream* stream,
    113                               int sample_rate,
    114                               int bytes_per_frame) {
    115   DCHECK(stream);
    116   int negative = 0;
    117   pa_usec_t latency_micros = 0;
    118   if (pa_stream_get_latency(stream, &latency_micros, &negative) != 0)
    119     return 0;
    120 
    121   if (negative)
    122     return 0;
    123 
    124   return latency_micros * sample_rate * bytes_per_frame /
    125       base::Time::kMicrosecondsPerSecond;
    126 }
    127 
    128 // Helper macro for CreateInput/OutputStream() to avoid code spam and
    129 // string bloat.
    130 #define RETURN_ON_FAILURE(expression, message) do { \
    131   if (!(expression)) { \
    132     DLOG(ERROR) << message; \
    133     return false; \
    134   } \
    135 } while(0)
    136 
    137 bool CreateInputStream(pa_threaded_mainloop* mainloop,
    138                        pa_context* context,
    139                        pa_stream** stream,
    140                        const AudioParameters& params,
    141                        const std::string& device_id,
    142                        pa_stream_notify_cb_t stream_callback,
    143                        void* user_data) {
    144   DCHECK(mainloop);
    145   DCHECK(context);
    146 
    147   // Set sample specifications.
    148   pa_sample_spec sample_specifications;
    149   sample_specifications.format = BitsToPASampleFormat(
    150       params.bits_per_sample());
    151   sample_specifications.rate = params.sample_rate();
    152   sample_specifications.channels = params.channels();
    153 
    154   // Get channel mapping and open recording stream.
    155   pa_channel_map source_channel_map = ChannelLayoutToPAChannelMap(
    156       params.channel_layout());
    157   pa_channel_map* map = (source_channel_map.channels != 0) ?
    158       &source_channel_map : NULL;
    159 
    160   // Create a new recording stream.
    161   *stream = pa_stream_new(context, "RecordStream", &sample_specifications, map);
    162   RETURN_ON_FAILURE(*stream, "failed to create PA recording stream");
    163 
    164   pa_stream_set_state_callback(*stream, stream_callback, user_data);
    165 
    166   // Set server-side capture buffer metrics. Detailed documentation on what
    167   // values should be chosen can be found at
    168   // freedesktop.org/software/pulseaudio/doxygen/structpa__buffer__attr.html.
    169   pa_buffer_attr buffer_attributes;
    170   const unsigned int buffer_size = params.GetBytesPerBuffer();
    171   buffer_attributes.maxlength = static_cast<uint32_t>(-1);
    172   buffer_attributes.tlength = buffer_size;
    173   buffer_attributes.minreq = buffer_size;
    174   buffer_attributes.prebuf = static_cast<uint32_t>(-1);
    175   buffer_attributes.fragsize = buffer_size;
    176   int flags = PA_STREAM_AUTO_TIMING_UPDATE |
    177               PA_STREAM_INTERPOLATE_TIMING |
    178               PA_STREAM_ADJUST_LATENCY |
    179               PA_STREAM_START_CORKED;
    180   RETURN_ON_FAILURE(
    181       pa_stream_connect_record(
    182           *stream,
    183           device_id == AudioManagerBase::kDefaultDeviceId ?
    184               NULL : device_id.c_str(),
    185           &buffer_attributes,
    186           static_cast<pa_stream_flags_t>(flags)) == 0,
    187       "pa_stream_connect_record FAILED ");
    188 
    189   // Wait for the stream to be ready.
    190   while (true) {
    191     pa_stream_state_t stream_state = pa_stream_get_state(*stream);
    192     RETURN_ON_FAILURE(
    193         PA_STREAM_IS_GOOD(stream_state), "Invalid PulseAudio stream state");
    194     if (stream_state == PA_STREAM_READY)
    195         break;
    196     pa_threaded_mainloop_wait(mainloop);
    197   }
    198 
    199   return true;
    200 }
    201 
    202 bool CreateOutputStream(pa_threaded_mainloop** mainloop,
    203                         pa_context** context,
    204                         pa_stream** stream,
    205                         const AudioParameters& params,
    206                         const std::string& device_id,
    207                         pa_stream_notify_cb_t stream_callback,
    208                         pa_stream_request_cb_t write_callback,
    209                         void* user_data) {
    210   DCHECK(!*mainloop);
    211   DCHECK(!*context);
    212 
    213   *mainloop = pa_threaded_mainloop_new();
    214   RETURN_ON_FAILURE(*mainloop, "Failed to create PulseAudio main loop.");
    215 
    216   pa_mainloop_api* pa_mainloop_api = pa_threaded_mainloop_get_api(*mainloop);
    217   *context = pa_context_new(pa_mainloop_api, "Chromium");
    218   RETURN_ON_FAILURE(*context, "Failed to create PulseAudio context.");
    219 
    220   // A state callback must be set before calling pa_threaded_mainloop_lock() or
    221   // pa_threaded_mainloop_wait() calls may lead to dead lock.
    222   pa_context_set_state_callback(*context, &ContextStateCallback, *mainloop);
    223 
    224   // Lock the main loop while setting up the context.  Failure to do so may lead
    225   // to crashes as the PulseAudio thread tries to run before things are ready.
    226   AutoPulseLock auto_lock(*mainloop);
    227 
    228   RETURN_ON_FAILURE(pa_threaded_mainloop_start(*mainloop) == 0,
    229                     "Failed to start PulseAudio main loop.");
    230   RETURN_ON_FAILURE(
    231       pa_context_connect(*context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) == 0,
    232       "Failed to connect PulseAudio context.");
    233 
    234   // Wait until |pa_context_| is ready.  pa_threaded_mainloop_wait() must be
    235   // called after pa_context_get_state() in case the context is already ready,
    236   // otherwise pa_threaded_mainloop_wait() will hang indefinitely.
    237   while (true) {
    238     pa_context_state_t context_state = pa_context_get_state(*context);
    239     RETURN_ON_FAILURE(
    240         PA_CONTEXT_IS_GOOD(context_state), "Invalid PulseAudio context state.");
    241     if (context_state == PA_CONTEXT_READY)
    242       break;
    243     pa_threaded_mainloop_wait(*mainloop);
    244   }
    245 
    246   // Set sample specifications.
    247   pa_sample_spec sample_specifications;
    248   sample_specifications.format = BitsToPASampleFormat(
    249       params.bits_per_sample());
    250   sample_specifications.rate = params.sample_rate();
    251   sample_specifications.channels = params.channels();
    252 
    253   // Get channel mapping and open playback stream.
    254   pa_channel_map* map = NULL;
    255   pa_channel_map source_channel_map = ChannelLayoutToPAChannelMap(
    256       params.channel_layout());
    257   if (source_channel_map.channels != 0) {
    258     // The source data uses a supported channel map so we will use it rather
    259     // than the default channel map (NULL).
    260     map = &source_channel_map;
    261   }
    262   *stream = pa_stream_new(*context, "Playback", &sample_specifications, map);
    263   RETURN_ON_FAILURE(*stream, "failed to create PA playback stream");
    264 
    265   pa_stream_set_state_callback(*stream, stream_callback, user_data);
    266 
    267   // Even though we start the stream corked above, PulseAudio will issue one
    268   // stream request after setup.  write_callback() must fulfill the write.
    269   pa_stream_set_write_callback(*stream, write_callback, user_data);
    270 
    271   // Pulse is very finicky with the small buffer sizes used by Chrome.  The
    272   // settings below are mostly found through trial and error.  Essentially we
    273   // want Pulse to auto size its internal buffers, but call us back nearly every
    274   // |minreq| bytes.  |tlength| should be a multiple of |minreq|; too low and
    275   // Pulse will issue callbacks way too fast, too high and we don't get
    276   // callbacks frequently enough.
    277   //
    278   // Setting |minreq| to the exact buffer size leads to more callbacks than
    279   // necessary, so we've clipped it to half the buffer size.  Regardless of the
    280   // requested amount, we'll always fill |params.GetBytesPerBuffer()| though.
    281   pa_buffer_attr pa_buffer_attributes;
    282   pa_buffer_attributes.maxlength = static_cast<uint32_t>(-1);
    283   pa_buffer_attributes.minreq = params.GetBytesPerBuffer() / 2;
    284   pa_buffer_attributes.prebuf = static_cast<uint32_t>(-1);
    285   pa_buffer_attributes.tlength = params.GetBytesPerBuffer() * 3;
    286   pa_buffer_attributes.fragsize = static_cast<uint32_t>(-1);
    287 
    288   // Connect playback stream.  Like pa_buffer_attr, the pa_stream_flags have a
    289   // huge impact on the performance of the stream and were chosen through trial
    290   // and error.
    291   RETURN_ON_FAILURE(
    292       pa_stream_connect_playback(
    293           *stream,
    294           device_id == AudioManagerBase::kDefaultDeviceId ?
    295               NULL : device_id.c_str(),
    296           &pa_buffer_attributes,
    297           static_cast<pa_stream_flags_t>(
    298               PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY |
    299               PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_NOT_MONOTONIC |
    300               PA_STREAM_START_CORKED),
    301           NULL,
    302           NULL) == 0,
    303       "pa_stream_connect_playback FAILED ");
    304 
    305   // Wait for the stream to be ready.
    306   while (true) {
    307     pa_stream_state_t stream_state = pa_stream_get_state(*stream);
    308     RETURN_ON_FAILURE(
    309         PA_STREAM_IS_GOOD(stream_state), "Invalid PulseAudio stream state");
    310     if (stream_state == PA_STREAM_READY)
    311       break;
    312     pa_threaded_mainloop_wait(*mainloop);
    313   }
    314 
    315   return true;
    316 }
    317 
    318 #undef RETURN_ON_FAILURE
    319 
    320 }  // namespace pulse
    321 
    322 }  // namespace media
    323