| // Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <pulse/pulseaudio.h> |
| |
| #include "audio_output.h" |
| #include "log.h" |
| #include "threading.h" |
| |
| namespace speech_synthesis { |
| |
| const int CHUNK_SIZE = 1024; |
| const int BUFFER_SIZE = 65536; |
| |
| class LinuxPulseAudioOutput : public AudioOutput { |
| public: |
| explicit LinuxPulseAudioOutput() |
| : provider_(NULL) { |
| } |
| |
| virtual ~LinuxPulseAudioOutput() { |
| pa_threaded_mainloop_stop(mainloop_); |
| pa_context_disconnect(context_); |
| pa_context_unref(context_); |
| pa_threaded_mainloop_free(mainloop_); |
| } |
| |
| bool Init(AudioProvider *provider) { |
| if (!provider) { |
| LOG(ERROR) << "An AudioProvider is required."; |
| return false; |
| } |
| |
| if (provider_) { |
| return true; |
| } |
| |
| // Create PulseAudio's main loop, which it will run in its own thread. |
| // Lock it so that it can't be modified during initialization. |
| mainloop_ = pa_threaded_mainloop_new(); |
| if (!mainloop_) { |
| LOG(ERROR) << "Unable to create pulseaudio threaded mainloop."; |
| return false; |
| } |
| pa_threaded_mainloop_lock(mainloop_); |
| |
| // Create the context, which we use to connect to the PulseAudio server. |
| context_ = pa_context_new( |
| pa_threaded_mainloop_get_api(mainloop_), |
| "tts_service"); |
| if (!context_) { |
| LOG(ERROR) << "Unable to create pulseaudio context."; |
| pa_threaded_mainloop_unlock(mainloop_); |
| pa_threaded_mainloop_free(mainloop_); |
| return false; |
| } |
| |
| // Register a callback when the state of the context changes. Our |
| // callback simply calls pa_threaded_mainloop_signal when it's called, |
| // which allows us to call pa_threaded_mainloop_wait to wait until |
| // the context changes. |
| pa_context_set_state_callback(context_, ContextStateCallback, this); |
| |
| // Request that the context connect to the server. |
| if (pa_context_connect(context_, NULL, (pa_context_flags_t)0, NULL) < 0) { |
| LOG(ERROR) << "Unable to connect pulseaudio context."; |
| pa_context_disconnect(context_); |
| pa_context_unref(context_); |
| pa_threaded_mainloop_unlock(mainloop_); |
| pa_threaded_mainloop_free(mainloop_); |
| return false; |
| } |
| |
| // Start the main loop running in the background. |
| if (pa_threaded_mainloop_start(mainloop_) < 0) { |
| LOG(ERROR) << "Unable to start pulseaudio mainloop."; |
| pa_context_disconnect(context_); |
| pa_context_unref(context_); |
| pa_threaded_mainloop_unlock(mainloop_); |
| pa_threaded_mainloop_free(mainloop_); |
| return false; |
| } |
| |
| uint32_t index = 0; |
| // Wait until the context has connected to the server. |
| LOG(INFO) << "Connecting to pulseaudio server..."; |
| pa_threaded_mainloop_wait(mainloop_); |
| if (pa_context_get_state(context_) != PA_CONTEXT_READY) { |
| LOG(ERROR) << "Unable to connect to pulseaudio server."; |
| goto error; |
| } |
| LOG(INFO) << "Connected."; |
| |
| // Get the server's sample rate. |
| sample_rate_ = -1; |
| pa_operation* op; |
| // Try getting sink info for the first 10 sink indices until a valid |
| // sink info is obtained. Its highly unlikely that a ChromeOS netbook |
| // has >10 audio devices. |
| for (; index < 10 && sample_rate_ == -1; index++) { |
| op = pa_context_get_sink_info_by_index( |
| context_, index, SinkInfoCallback, this); |
| if (!op) { |
| LOG(ERROR) << "Unable to get pulseaudio sink info."; |
| goto error; |
| } |
| while (pa_operation_get_state(op) != PA_OPERATION_DONE) { |
| pa_threaded_mainloop_wait(mainloop_); |
| } |
| } |
| if (sample_rate_ == -1) { |
| LOG(ERROR) << "Unable to obtain valid sink info."; |
| goto error; |
| } |
| LOG(INFO) << "Server sample rate: " << sample_rate_; |
| |
| // Construct a 1-channel, 16-bit audio stream at the server's |
| // sample rate. |
| pa_sample_spec ss; |
| ss.format = PA_SAMPLE_S16LE; |
| ss.rate = sample_rate_; |
| ss.channels = 1; |
| stream_ = pa_stream_new(context_, "unknown", &ss, NULL); |
| if (!stream_) { |
| LOG(ERROR) << "Unable to create pulseaudio stream."; |
| goto error; |
| } |
| |
| // Register our callback functions to be called with the stream |
| // state changes, and when it's time for us to output. |
| pa_stream_set_state_callback(stream_, StreamStateCallback, this); |
| pa_stream_set_write_callback(stream_, StreamWriteCallback, this); |
| |
| // Request low-latency buffer attributes. |
| pa_buffer_attr attr; |
| attr.tlength = CHUNK_SIZE * sizeof(int16_t); |
| attr.maxlength = BUFFER_SIZE * sizeof(int16_t); |
| attr.prebuf = CHUNK_SIZE * sizeof(int16_t); |
| attr.minreq = CHUNK_SIZE * sizeof(int16_t); |
| attr.fragsize = 0; |
| |
| // Initiate the request to connect the stream for playback, but in the |
| // "corked" state (paused). |
| if (pa_stream_connect_playback( |
| stream_, |
| NULL, |
| &attr, |
| static_cast<pa_stream_flags_t>(PA_STREAM_START_CORKED), |
| NULL, |
| NULL) < 0) { |
| LOG(ERROR) << "Unable to connect pulseaudio stream."; |
| goto error; |
| } |
| |
| // Wait until the stream is connected for playback. |
| pa_threaded_mainloop_wait(mainloop_); |
| if (pa_stream_get_state(stream_) != PA_STREAM_READY) { |
| LOG(ERROR) << "Unable to connect pulseaudio stream."; |
| goto error; |
| } |
| |
| // We're done initializing. Unlock the main loop and return. |
| pa_threaded_mainloop_unlock(mainloop_); |
| provider_ = provider; |
| return true; |
| |
| error: |
| LOG(ERROR) << "PulseAudio error: " << |
| pa_strerror(pa_context_errno(context_)); |
| pa_threaded_mainloop_unlock(mainloop_); |
| pa_threaded_mainloop_stop(mainloop_); |
| pa_context_disconnect(context_); |
| pa_context_unref(context_); |
| pa_threaded_mainloop_free(mainloop_); |
| return false; |
| } |
| |
| virtual void StartAudio() { |
| // Lock the mainloop and then uncork the stream. |
| pa_threaded_mainloop_lock(mainloop_); |
| pa_operation* op = pa_stream_cork(stream_, 0, SuccessCallback, this); |
| while (pa_operation_get_state(op) != PA_OPERATION_DONE) { |
| pa_threaded_mainloop_wait(mainloop_); |
| } |
| pa_threaded_mainloop_unlock(mainloop_); |
| } |
| |
| virtual void StopAudio() { |
| // Lock the mainloop and then cork the stream. |
| pa_threaded_mainloop_lock(mainloop_); |
| pa_operation* op = pa_stream_cork(stream_, 1, SuccessCallback, this); |
| while (pa_operation_get_state(op) != PA_OPERATION_DONE) { |
| pa_threaded_mainloop_wait(mainloop_); |
| } |
| pa_threaded_mainloop_unlock(mainloop_); |
| } |
| |
| virtual int GetSampleRate() { |
| return sample_rate_; |
| } |
| |
| virtual int GetChannelCount() { |
| return 1; |
| } |
| |
| virtual int GetChunkSize() { |
| return CHUNK_SIZE; |
| } |
| |
| virtual int GetTotalBufferSize() { |
| return BUFFER_SIZE; |
| } |
| |
| // Called when we should output audio samples to the stream. |
| static void StreamWriteCallback(pa_stream* stream, |
| size_t bytes, |
| void* userdata) { |
| LinuxPulseAudioOutput* This = |
| static_cast<LinuxPulseAudioOutput*>(userdata); |
| |
| if (This->provider_) { |
| This->provider_->FillAudioBuffer( |
| This->buffer_, bytes / sizeof(int16_t)); |
| } else { |
| memset(This->buffer_, 0, bytes); |
| } |
| |
| pa_stream_write(stream, |
| This->buffer_, |
| bytes, |
| NULL, |
| 0, |
| PA_SEEK_RELATIVE); |
| } |
| |
| // Called in response to our request for information about the |
| // audio sink (e.g., the output device). Extract the sink's sample |
| // rate and store it in our class member variable, then signal the |
| // main loop so that calls to wait() will return. |
| static void SinkInfoCallback(pa_context* context, |
| const pa_sink_info* info, |
| int eol, |
| void* userdata) { |
| LinuxPulseAudioOutput* This = |
| static_cast<LinuxPulseAudioOutput*>(userdata); |
| if (info) |
| This->sample_rate_ = info->sample_spec.rate; |
| LOG(INFO) << "Signalling main loop..."; |
| pa_threaded_mainloop_signal(This->mainloop_, 0); |
| } |
| |
| // Called when the stream state changes. For some states, signal the |
| // main loop so that calls to wait() will return. |
| static void StreamStateCallback(pa_stream *stream, void* userdata) { |
| LinuxPulseAudioOutput* This = |
| static_cast<LinuxPulseAudioOutput*>(userdata); |
| switch (pa_stream_get_state(stream)) { |
| case PA_STREAM_READY: |
| case PA_STREAM_FAILED: |
| case PA_STREAM_TERMINATED: |
| pa_threaded_mainloop_signal(This->mainloop_, 0); |
| break; |
| |
| case PA_STREAM_UNCONNECTED: |
| case PA_STREAM_CREATING: |
| break; |
| } |
| } |
| |
| // Called when the context state changes. For some states, signal the |
| // main loop so that calls to wait() will return. |
| static void ContextStateCallback(pa_context* context, void* userdata) { |
| LinuxPulseAudioOutput* This = |
| static_cast<LinuxPulseAudioOutput*>(userdata); |
| switch (pa_context_get_state(context)) { |
| case PA_CONTEXT_READY: |
| case PA_CONTEXT_TERMINATED: |
| case PA_CONTEXT_FAILED: |
| pa_threaded_mainloop_signal(This->mainloop_, 0); |
| break; |
| |
| case PA_CONTEXT_UNCONNECTED: |
| case PA_CONTEXT_CONNECTING: |
| case PA_CONTEXT_AUTHORIZING: |
| case PA_CONTEXT_SETTING_NAME: |
| break; |
| } |
| } |
| |
| // Called in response to our cork / uncork requests. Signal the |
| // main loop so that our call to wait() will return. |
| static void SuccessCallback(pa_stream *stream, int success, void* userdata) { |
| LinuxPulseAudioOutput* This = |
| static_cast<LinuxPulseAudioOutput*>(userdata); |
| pa_threaded_mainloop_signal(This->mainloop_, 0); |
| } |
| |
| private: |
| AudioProvider* provider_; |
| int16_t buffer_[BUFFER_SIZE]; |
| pa_context* context_; |
| pa_stream* stream_; |
| pa_threaded_mainloop* mainloop_; |
| int sample_rate_; |
| }; |
| |
| AudioOutput* AudioOutput::Create(Threading* threading) { |
| return new LinuxPulseAudioOutput(); |
| } |
| |
| } // namespace speech_synthesis |