blob: 3d1c04d8fbe5ca2da111702cc9390c1968553215 [file] [log] [blame]
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/shared/alsa/alsa_util.h"
#include <alsa/asoundlib.h>
#include "starboard/log.h"
#define ALSA_CHECK(error, alsa_function, failure_return) \
do { \
if (error < 0) { \
SB_LOG(ERROR) << __FUNCTION__ << ": " << #alsa_function \
<< "() failed with error " \
<< snd_strerror(error) \
<< " (" << error << ")"; \
return (failure_return); \
} \
} while (false)
namespace starboard {
namespace shared {
namespace alsa {
namespace {
const snd_pcm_uframes_t kSilenceThresholdInFrames = 256U;
const snd_pcm_uframes_t kStartThresholdInFrames = 1024U;
template <typename T, typename CloseFunc>
class AutoClose {
public:
explicit AutoClose(CloseFunc close_func)
: valid_(false), value_(NULL), close_func_(close_func) {}
~AutoClose() {
if (valid_) {
close_func_(value_);
}
}
operator T() {
SB_DCHECK(valid_);
return value_;
}
T* operator&() { // NOLINT(runtime/operator)
SB_DCHECK(!valid_);
return &value_;
}
bool is_valid() const { return valid_; }
void set_valid() {
SB_DCHECK(!valid_);
valid_ = true;
}
T Detach() {
SB_DCHECK(valid_);
valid_ = false;
return value_;
}
private:
bool valid_;
T value_;
CloseFunc close_func_;
};
class HWParams
: public AutoClose<snd_pcm_hw_params_t*, void (*)(snd_pcm_hw_params_t*)> {
public:
HWParams()
: AutoClose<snd_pcm_hw_params_t*, void (*)(snd_pcm_hw_params_t*)>(
snd_pcm_hw_params_free) {}
};
class SWParams
: public AutoClose<snd_pcm_sw_params_t*, void (*)(snd_pcm_sw_params_t*)> {
public:
SWParams()
: AutoClose<snd_pcm_sw_params_t*, void (*)(snd_pcm_sw_params_t*)>(
snd_pcm_sw_params_free) {}
};
class PcmHandle : public AutoClose<snd_pcm_t*, int (*)(snd_pcm_t*)> {
public:
PcmHandle() : AutoClose<snd_pcm_t*, int (*)(snd_pcm_t*)>(snd_pcm_close) {}
};
} // namespace
void* AlsaOpenPlaybackDevice(int channel,
int sample_rate,
int frames_per_request,
int buffer_size_in_frames,
snd_pcm_format_t sample_type) {
SB_DCHECK(sample_type == SND_PCM_FORMAT_FLOAT_LE ||
sample_type == SND_PCM_FORMAT_S16);
PcmHandle playback_handle;
int error =
snd_pcm_open(&playback_handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
ALSA_CHECK(error, snd_pcm_open, NULL);
playback_handle.set_valid();
HWParams hw_params;
error = snd_pcm_hw_params_malloc(&hw_params);
ALSA_CHECK(error, snd_pcm_hw_params_malloc, NULL);
hw_params.set_valid();
error = snd_pcm_hw_params_any(playback_handle, hw_params);
ALSA_CHECK(error, snd_pcm_hw_params_any, NULL);
error = snd_pcm_hw_params_set_access(playback_handle, hw_params,
SND_PCM_ACCESS_RW_INTERLEAVED);
ALSA_CHECK(error, snd_pcm_hw_params_set_access, NULL);
error = snd_pcm_hw_params_set_format(playback_handle, hw_params, sample_type);
ALSA_CHECK(error, snd_pcm_hw_params_set_format, NULL);
error =
snd_pcm_hw_params_set_rate(playback_handle, hw_params, sample_rate, 0);
ALSA_CHECK(error, snd_pcm_hw_params_set_rate, NULL);
error = snd_pcm_hw_params_set_channels(playback_handle, hw_params, channel);
ALSA_CHECK(error, snd_pcm_hw_params_set_channels, NULL);
snd_pcm_uframes_t buffer_size = buffer_size_in_frames;
error = snd_pcm_hw_params_set_buffer_size_near(playback_handle, hw_params,
&buffer_size);
ALSA_CHECK(error, snd_pcm_hw_params_set_buffer_size, NULL);
error = snd_pcm_hw_params(playback_handle, hw_params);
ALSA_CHECK(error, snd_pcm_hw_params, NULL);
SWParams sw_params;
error = snd_pcm_sw_params_malloc(&sw_params);
ALSA_CHECK(error, snd_pcm_sw_params_malloc, NULL);
sw_params.set_valid();
error = snd_pcm_sw_params_current(playback_handle, sw_params);
ALSA_CHECK(error, snd_pcm_sw_params_current, NULL);
error = snd_pcm_sw_params_set_avail_min(playback_handle, sw_params,
frames_per_request);
ALSA_CHECK(error, snd_pcm_sw_params_set_avail_min, NULL);
error = snd_pcm_sw_params_set_silence_threshold(playback_handle, sw_params,
kSilenceThresholdInFrames);
ALSA_CHECK(error, snd_pcm_sw_params_set_silence_threshold, NULL);
error = snd_pcm_sw_params_set_start_threshold(playback_handle, sw_params,
kStartThresholdInFrames);
ALSA_CHECK(error, snd_pcm_sw_params_set_start_threshold, NULL);
error = snd_pcm_sw_params(playback_handle, sw_params);
ALSA_CHECK(error, snd_pcm_sw_params, NULL);
error = snd_pcm_prepare(playback_handle);
ALSA_CHECK(error, snd_pcm_prepare, NULL);
return playback_handle.Detach();
}
int AlsaWriteFrames(void* playback_handle,
const void* buffer,
int frames_to_write) {
if (frames_to_write == 0) {
return 0;
}
int error;
snd_pcm_t* handle = reinterpret_cast<snd_pcm_t*>(playback_handle);
int frames = 0;
for (;;) {
frames = snd_pcm_writei(handle, buffer, frames_to_write);
if (frames > 0) {
return frames;
} else if (frames == -EPIPE) {
error = snd_pcm_prepare(handle);
ALSA_CHECK(error, snd_pcm_prepare, 0);
} else {
ALSA_CHECK(frames, snd_pcm_writei, 0);
// "frames == 0" means snd_pcm_writei() is interrupted, we'll retry.
}
}
SB_NOTREACHED();
return 0;
}
int AlsaGetBufferedFrames(void* playback_handle) {
int error;
snd_pcm_t* handle = reinterpret_cast<snd_pcm_t*>(playback_handle);
int state = snd_pcm_state(handle);
// snd_pcm_delay() isn't able to catch xrun or setup, so we explicitly check
// for them.
if (state == SND_PCM_STATE_XRUN || state == SND_PCM_STATE_SETUP) {
error = snd_pcm_prepare(handle);
ALSA_CHECK(error, snd_pcm_prepare, -1);
// The buffer has already been reset, so the delay is 0.
return 0;
}
snd_pcm_sframes_t delay;
error = snd_pcm_delay(handle, &delay);
if (error == 0) {
if (delay < 0) {
SB_LOG(ERROR) << __FUNCTION__
<< ": snd_pcm_delay() failed with negative delay " << delay;
return -1;
}
return delay;
}
ALSA_CHECK(error, snd_pcm_delay, -1);
return -1;
}
void AlsaCloseDevice(void* playback_handle) {
if (playback_handle) {
snd_pcm_drain(reinterpret_cast<snd_pcm_t*>(playback_handle));
snd_pcm_close(reinterpret_cast<snd_pcm_t*>(playback_handle));
}
}
void AlsaDrain(void* playback_handle) {
if (playback_handle) {
snd_pcm_t* handle = reinterpret_cast<snd_pcm_t*>(playback_handle);
int error;
error = snd_pcm_drain(handle);
SB_DCHECK(error >= 0);
}
}
} // namespace alsa
} // namespace shared
} // namespace starboard