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