blob: 88e976ef8b6f45e6608d7bdbed8709d1f757c610 [file] [log] [blame]
// 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