This commit is contained in:
2026-03-29 14:01:52 +03:00
commit 0611279128
210 changed files with 60454 additions and 0 deletions

920
op/aaudio.c Normal file
View File

@@ -0,0 +1,920 @@
/*
* Copyright (C) 2024 Patrick Gaskin <patrick@pgaskin.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
// for development, can cross-compile with $ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/clang -target aarch64-linux-android26 -shared -o aaudio.so -fPIC -D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ -Werror=unguarded-availability -Wall -std=gnu11 op/aaudio.c -laaudio
// also see https://github.com/google/oboe/blob/main/docs/AndroidAudioHistory.md
// also see https://android.googlesource.com/platform/frameworks/av/+/master/media/libaaudio/examples/utils/AAudioSimplePlayer.h
#ifndef __ANDROID__
// make ide autocomplete work without using a full ndk toolchain
#define __INTRODUCED_IN(api_level)
#endif
// https://developer.android.com/ndk/guides/using-newer-apis
#define REQUIRES_API(x) __attribute__((__availability__(android,introduced=x)))
#define API_AT_LEAST(x) __builtin_available(android x, *)
#define AAUDIO_MINIMUM_API 26
#include <aaudio/AAudio.h>
#include "../op.h"
#include "../mixer.h"
#include "../sf.h"
#include "../utils.h"
#include "../xmalloc.h"
// mapping from AAUDIO_CHANNEL_* enum values to cmus channel_position_t values
//
// cat "$(find ${ANDROID_NDK_HOME:-$ANDROID_HOME/ndk} -wholename '*/AAudio.h' | sort -n | tail -n1)" |
// grep AAUDIO_CHANNEL | tr -d ' \n' | tr '|,' ' \n' | grep -F '<<' | cut -d '_' -f3- |
// cut -d '=' -f1 | xargs printf '#define A2C__%s\tCHANNEL_POSITION_INVALID\n' |
// sed -E $(printf " -e s/(A2C__%s\\\t)CHANNEL_POSITION_INVALID/\\\1CHANNEL_POSITION_%s/" \
// FRONT_LEFT FRONT_LEFT \
// FRONT_RIGHT FRONT_RIGHT \
// FRONT_CENTER FRONT_CENTER \
// LOW_FREQUENCY LFE \
// BACK_LEFT REAR_LEFT \
// BACK_RIGHT REAR_RIGHT \
// FRONT_LEFT_OF_CENTER FRONT_LEFT_OF_CENTER \
// FRONT_RIGHT_OF_CENTER FRONT_RIGHT_OF_CENTER \
// BACK_CENTER REAR_CENTER \
// SIDE_LEFT SIDE_LEFT \
// SIDE_RIGHT SIDE_RIGHT \
// TOP_CENTER TOP_CENTER \
// TOP_FRONT_LEFT TOP_FRONT_LEFT \
// TOP_FRONT_CENTER TOP_FRONT_CENTER \
// TOP_FRONT_RIGHT TOP_FRONT_RIGHT \
// TOP_BACK_LEFT TOP_REAR_LEFT \
// TOP_BACK_CENTER TOP_REAR_CENTER \
// TOP_BACK_RIGHT TOP_REAR_RIGHT \
// ) |
// column -s $'\t' -t | tee /dev/stderr | cut -d ' ' -f2 | cut -d '_' -f3- |
// xargs printf ' X(%s)' | xargs -0 printf '#define A2C_CHANNELS%s\n'
#define A2C__FRONT_LEFT CHANNEL_POSITION_FRONT_LEFT
#define A2C__FRONT_RIGHT CHANNEL_POSITION_FRONT_RIGHT
#define A2C__FRONT_CENTER CHANNEL_POSITION_FRONT_CENTER
#define A2C__LOW_FREQUENCY CHANNEL_POSITION_LFE
#define A2C__BACK_LEFT CHANNEL_POSITION_REAR_LEFT
#define A2C__BACK_RIGHT CHANNEL_POSITION_REAR_RIGHT
#define A2C__FRONT_LEFT_OF_CENTER CHANNEL_POSITION_FRONT_LEFT_OF_CENTER
#define A2C__FRONT_RIGHT_OF_CENTER CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER
#define A2C__BACK_CENTER CHANNEL_POSITION_REAR_CENTER
#define A2C__SIDE_LEFT CHANNEL_POSITION_SIDE_LEFT
#define A2C__SIDE_RIGHT CHANNEL_POSITION_SIDE_RIGHT
#define A2C__TOP_CENTER CHANNEL_POSITION_TOP_CENTER
#define A2C__TOP_FRONT_LEFT CHANNEL_POSITION_TOP_FRONT_LEFT
#define A2C__TOP_FRONT_CENTER CHANNEL_POSITION_TOP_FRONT_CENTER
#define A2C__TOP_FRONT_RIGHT CHANNEL_POSITION_TOP_FRONT_RIGHT
#define A2C__TOP_BACK_LEFT CHANNEL_POSITION_TOP_REAR_LEFT
#define A2C__TOP_BACK_CENTER CHANNEL_POSITION_TOP_REAR_CENTER
#define A2C__TOP_BACK_RIGHT CHANNEL_POSITION_TOP_REAR_RIGHT
#define A2C__TOP_SIDE_LEFT CHANNEL_POSITION_INVALID
#define A2C__TOP_SIDE_RIGHT CHANNEL_POSITION_INVALID
#define A2C__BOTTOM_FRONT_LEFT CHANNEL_POSITION_INVALID
#define A2C__BOTTOM_FRONT_CENTER CHANNEL_POSITION_INVALID
#define A2C__BOTTOM_FRONT_RIGHT CHANNEL_POSITION_INVALID
#define A2C__LOW_FREQUENCY_2 CHANNEL_POSITION_INVALID
#define A2C__FRONT_WIDE_LEFT CHANNEL_POSITION_INVALID
#define A2C__FRONT_WIDE_RIGHT CHANNEL_POSITION_INVALID
#define A2C_CHANNELS X(FRONT_LEFT) X(FRONT_RIGHT) X(FRONT_CENTER) X(LOW_FREQUENCY) X(BACK_LEFT) X(BACK_RIGHT) X(FRONT_LEFT_OF_CENTER) X(FRONT_RIGHT_OF_CENTER) X(BACK_CENTER) X(SIDE_LEFT) X(SIDE_RIGHT) X(TOP_CENTER) X(TOP_FRONT_LEFT) X(TOP_FRONT_CENTER) X(TOP_FRONT_RIGHT) X(TOP_BACK_LEFT) X(TOP_BACK_CENTER) X(TOP_BACK_RIGHT) X(TOP_SIDE_LEFT) X(TOP_SIDE_RIGHT) X(BOTTOM_FRONT_LEFT) X(BOTTOM_FRONT_CENTER) X(BOTTOM_FRONT_RIGHT) X(LOW_FREQUENCY_2) X(FRONT_WIDE_LEFT) X(FRONT_WIDE_RIGHT)
// mapping from AAUDIO_CHANNEL_* masks to cmus channel_position_t lists
//
// cat "$(find ${ANDROID_NDK_HOME:-$ANDROID_HOME/ndk} -wholename '*/aaudio/AAudio.h' | sort -n | tail -n1)" |
// grep AAUDIO_CHANNEL | tr -d ' \n' | tr '|,' ',\n' | grep -Fve '<<' -e '-1' | cut -d '_' -f3- |
// xargs printf '#define A2C__%s\n' | tr '=' '\t' | sed -E 's/AAUDIO_CHANNEL_([A-Z0-9_]+)/A2C__\1/g' |
// column -s $'\t' -t | tee /dev/stderr | cut -d ' ' -f2 | cut -d '_' -f3- |
// xargs printf ' X(%s)' | xargs -0 printf '#define A2C_LAYOUTS%s\n'
#define A2C__MONO A2C__FRONT_LEFT
#define A2C__STEREO A2C__FRONT_LEFT,A2C__FRONT_RIGHT
#define A2C__2POINT1 A2C__FRONT_LEFT,A2C__FRONT_RIGHT,A2C__LOW_FREQUENCY
#define A2C__TRI A2C__FRONT_LEFT,A2C__FRONT_RIGHT,A2C__FRONT_CENTER
#define A2C__TRI_BACK A2C__FRONT_LEFT,A2C__FRONT_RIGHT,A2C__BACK_CENTER
#define A2C__3POINT1 A2C__FRONT_LEFT,A2C__FRONT_RIGHT,A2C__FRONT_CENTER,A2C__LOW_FREQUENCY
#define A2C__2POINT0POINT2 A2C__FRONT_LEFT,A2C__FRONT_RIGHT,A2C__TOP_SIDE_LEFT,A2C__TOP_SIDE_RIGHT
#define A2C__2POINT1POINT2 A2C__2POINT0POINT2,A2C__LOW_FREQUENCY
#define A2C__3POINT0POINT2 A2C__FRONT_LEFT,A2C__FRONT_RIGHT,A2C__FRONT_CENTER,A2C__TOP_SIDE_LEFT,A2C__TOP_SIDE_RIGHT
#define A2C__3POINT1POINT2 A2C__3POINT0POINT2,A2C__LOW_FREQUENCY
#define A2C__QUAD A2C__FRONT_LEFT,A2C__FRONT_RIGHT,A2C__BACK_LEFT,A2C__BACK_RIGHT
#define A2C__QUAD_SIDE A2C__FRONT_LEFT,A2C__FRONT_RIGHT,A2C__SIDE_LEFT,A2C__SIDE_RIGHT
#define A2C__SURROUND A2C__FRONT_LEFT,A2C__FRONT_RIGHT,A2C__FRONT_CENTER,A2C__BACK_CENTER
#define A2C__PENTA A2C__QUAD,A2C__FRONT_CENTER
#define A2C__5POINT1 A2C__FRONT_LEFT,A2C__FRONT_RIGHT,A2C__FRONT_CENTER,A2C__LOW_FREQUENCY,A2C__BACK_LEFT,A2C__BACK_RIGHT
#define A2C__5POINT1_SIDE A2C__FRONT_LEFT,A2C__FRONT_RIGHT,A2C__FRONT_CENTER,A2C__LOW_FREQUENCY,A2C__SIDE_LEFT,A2C__SIDE_RIGHT
#define A2C__6POINT1 A2C__FRONT_LEFT,A2C__FRONT_RIGHT,A2C__FRONT_CENTER,A2C__LOW_FREQUENCY,A2C__BACK_LEFT,A2C__BACK_RIGHT,A2C__BACK_CENTER
#define A2C__7POINT1 A2C__5POINT1,A2C__SIDE_LEFT,A2C__SIDE_RIGHT
#define A2C__5POINT1POINT2 A2C__5POINT1,A2C__TOP_SIDE_LEFT,A2C__TOP_SIDE_RIGHT
#define A2C__5POINT1POINT4 A2C__5POINT1,A2C__TOP_FRONT_LEFT,A2C__TOP_FRONT_RIGHT,A2C__TOP_BACK_LEFT,A2C__TOP_BACK_RIGHT
#define A2C__7POINT1POINT2 A2C__7POINT1,A2C__TOP_SIDE_LEFT,A2C__TOP_SIDE_RIGHT
#define A2C__7POINT1POINT4 A2C__7POINT1,A2C__TOP_FRONT_LEFT,A2C__TOP_FRONT_RIGHT,A2C__TOP_BACK_LEFT,A2C__TOP_BACK_RIGHT
#define A2C__9POINT1POINT4 A2C__7POINT1POINT4,A2C__FRONT_WIDE_LEFT,A2C__FRONT_WIDE_RIGHT
#define A2C__9POINT1POINT6 A2C__9POINT1POINT4,A2C__TOP_SIDE_LEFT,A2C__TOP_SIDE_RIGHT
#define A2C__FRONT_BACK A2C__FRONT_CENTER,A2C__BACK_CENTER
#define A2C_LAYOUTS X(MONO) X(STEREO) X(2POINT1) X(TRI) X(TRI_BACK) X(3POINT1) X(2POINT0POINT2) X(2POINT1POINT2) X(3POINT0POINT2) X(3POINT1POINT2) X(QUAD) X(QUAD_SIDE) X(SURROUND) X(PENTA) X(5POINT1) X(5POINT1_SIDE) X(6POINT1) X(7POINT1) X(5POINT1POINT2) X(5POINT1POINT4) X(7POINT1POINT2) X(7POINT1POINT4) X(9POINT1POINT4) X(9POINT1POINT6) X(FRONT_BACK)
// convert a cmus channel map to an equivalent aaudio channel mask (the returned
// value will either be invalid or have the same number of bits set as the
// number of channels)
static aaudio_channel_mask_t cmus_channel_map_to_aaudio_mask(int channels, const channel_position_t *channel_map)
{
aaudio_channel_mask_t mask = 0;
// we can only convert a valid channel map
if (channels >= CHANNELS_MAX || !channel_map || !channel_map_valid(channel_map)) {
return AAUDIO_CHANNEL_INVALID;
}
// special case for mono since cmus defines a separate channel position
// for it
if (channels == 1 && channel_map[0] == CHANNEL_POSITION_MONO) {
return AAUDIO_CHANNEL_FRONT_LEFT;
}
// fill the mask, returning invalid if it has duplicates or no mapping
for (int i = 0; i < channels; i++) {
#define X(aaudio) \
if (A2C__##aaudio != CHANNEL_POSITION_INVALID && channel_map[i] == A2C__##aaudio) { \
if (mask & AAUDIO_CHANNEL_##aaudio) \
return AAUDIO_CHANNEL_INVALID; \
mask |= AAUDIO_CHANNEL_##aaudio; \
}
A2C_CHANNELS
#undef X
}
return mask;
}
// get the expected cmus channel order for the specified aaudio channel mask
static bool channel_map_init_aaudio(aaudio_channel_mask_t mask, channel_position_t *map)
{
switch (mask) {
#define X(aaudio) \
case AAUDIO_CHANNEL_##aaudio: channel_map_copy(map, (channel_position_t[CHANNELS_MAX]){ A2C__##aaudio }); return true;
A2C_LAYOUTS
#undef X
}
return false;
}
// get the name of a known aaudio channel mask
static const char *aaudio_channel_to_string(aaudio_channel_mask_t mask)
{
switch (mask) {
#define X(aaudio) \
case AAUDIO_CHANNEL_##aaudio: return #aaudio;
A2C_CHANNELS
#undef X
}
switch (mask) {
#define X(aaudio) \
case AAUDIO_CHANNEL_##aaudio: return #aaudio;
A2C_LAYOUTS
#undef X
}
return NULL;
}
// allocate a map of output frame byte indexes to input frame byte indexes (or
// -1 to zero) to remap channels (map must be sf_get_frame_size elements)
static ssize_t *make_channel_remap(const channel_position_t *channel_map_out, const channel_position_t *channel_map_in, sample_format_t sf)
{
int byte, channel_out, channel_in;
ssize_t *map;
map = xnew(ssize_t, sf_get_frame_size(sf));
if (!channel_map_out || !channel_map_valid(channel_map_out) || !channel_map_in || !channel_map_valid(channel_map_in)) {
for (byte = 0; byte < sf_get_frame_size(sf); byte++) {
map[byte] = byte;
}
} else {
for (byte = 0; byte < sf_get_frame_size(sf); byte++) {
map[byte] = -1;
}
for (channel_out = 0; channel_out < sf_get_channels(sf); channel_out++) {
if (channel_map_out[channel_out] != CHANNEL_POSITION_INVALID) {
for (channel_in = 0; channel_in < sf_get_channels(sf); channel_in++) {
if (channel_map_in[channel_in] == channel_map_out[channel_out]) {
for (byte = 0; byte < sf_get_sample_size(sf); byte++) {
map[sf_get_sample_size(sf) * channel_out + byte] = (ssize_t) sf_get_sample_size(sf) * channel_in + byte;
}
break;
}
}
}
}
}
d_print("remap bytes");
for (byte = 0; byte < sf_get_frame_size(sf); byte++) {
d_print(" %03zd", map[byte]);
}
d_print("\n");
return map;
}
// if remap is non-null, applies it and returns dst, otherwise returns src
static const uint8_t *apply_channel_remap(uint8_t *dst, const uint8_t *src, size_t n, sample_format_t sf, const ssize_t *remap)
{
size_t off_frame, off;
BUG_ON(!src || dst == src);
if (remap) {
BUG_ON(!dst);
for (off_frame = 0; off_frame < n; off_frame += (size_t) sf_get_frame_size(sf)) {
for (off = 0; off < (size_t) sf_get_frame_size(sf); off++) {
if (remap[off] != -1) {
dst[off_frame+off] = src[off_frame+remap[off]];
} else {
dst[off_frame+off] = 0;
}
}
}
return dst;
}
return src;
}
// configure builder to use the specified sample format (and on a best-effort
// basis, channel_map, if provided)
//
// successful if the sample format is valid and was configured
//
// on success, out_remap will be set to NULL, or if a channel map was configured
// and requires remapping, an allocated map of target frame byte indexes from
// the source index
REQUIRES_API(AAUDIO_MINIMUM_API)
static aaudio_result_t configure_aaudio_sf(AAudioStreamBuilder *builder, sample_format_t sf, const channel_position_t *channel_map, ssize_t **out_remap)
{
aaudio_format_t format;
aaudio_channel_mask_t mask;
channel_position_t mask_expected_channels[CHANNELS_MAX];
// apply the sample format
if (!sf_get_signed(sf)) {
d_print("aaudio does not support unsigned samples\n");
return AAUDIO_ERROR_INVALID_FORMAT;
}
if (sf_get_bigendian(sf)) {
d_print("aaudio does not support big-endian samples\n");
return AAUDIO_ERROR_INVALID_FORMAT;
}
switch (sf_get_bits(sf)) {
case 16: format = AAUDIO_FORMAT_PCM_I16; break;
case 24: format = AAUDIO_FORMAT_PCM_I24_PACKED; break;
case 32: format = AAUDIO_FORMAT_PCM_I32; break;
default:
d_print("unsupported sample format bits\n");
return AAUDIO_ERROR_INVALID_FORMAT;
}
AAudioStreamBuilder_setFormat(builder, format);
// apply the sample rate
AAudioStreamBuilder_setSampleRate(builder, sf_get_rate(sf));
// set the channel count
//
// note: if no channel mask is set, aaudio will treat the first two
// channels as left/right (duplicating mono to stereo if required), and
// leave the rest up to the device, dropping them if the device doesn't
// have that many channels
AAudioStreamBuilder_setChannelCount(builder, sf_get_channels(sf));
// if we have a channel map, apply it on a best-effort basis
*out_remap = NULL;
if (channel_map && channel_map_valid(channel_map)) {
if (API_AT_LEAST(32)) {
mask = cmus_channel_map_to_aaudio_mask(sf_get_channels(sf), channel_map);
d_print("channel map aaudio mask %d (%s)\n", mask, aaudio_channel_to_string(mask) ? aaudio_channel_to_string(mask) : "(null)");
if (mask == AAUDIO_CHANNEL_INVALID) {
d_print("not applying channel map since it contains duplicates or not all channels have an aaudio equivalent\n");
} else {
if (!channel_map_init_aaudio(mask, mask_expected_channels)) {
d_print("not applying channel map since there isn't a valid cmus channel mapping for the aaudio mask\n");
} else {
if (!channel_map_equal(channel_map, mask_expected_channels, sf_get_channels(sf))) {
d_print("will remap channels since the input channel_map order doesn't match the order expected by aaudio\n");
*out_remap = make_channel_remap(mask_expected_channels, channel_map, sf);
if (!*out_remap) return AAUDIO_ERROR_NO_MEMORY;
}
d_print("applying channel mask\n");
AAudioStreamBuilder_setChannelMask(builder, mask);
}
}
}
}
return AAUDIO_OK;
}
// maps an res to a suitable error code
static int OP_ERROR_AAUDIO(aaudio_result_t res) {
// see https://android.googlesource.com/platform/bionic/+/refs/heads/main/libc/private/bionic_errdefs.h
switch (res) {
case AAUDIO_OK: return 0;
case AAUDIO_ERROR_INTERNAL: return OP_ERROR_INTERNAL;
case AAUDIO_ERROR_NO_SERVICE: return OP_ERROR_NOT_SUPPORTED;
case AAUDIO_ERROR_INVALID_FORMAT: return OP_ERROR_SAMPLE_FORMAT;
case AAUDIO_ERROR_INVALID_RATE: return OP_ERROR_SAMPLE_FORMAT;
case AAUDIO_ERROR_UNAVAILABLE: errno = ECONNREFUSED; return OP_ERROR_ERRNO; // Connection refused
case AAUDIO_ERROR_DISCONNECTED: errno = ECONNRESET; return OP_ERROR_ERRNO; // Connection reset by peer
case AAUDIO_ERROR_TIMEOUT: errno = ETIMEDOUT; return OP_ERROR_ERRNO; // Connection timed out
case AAUDIO_ERROR_WOULD_BLOCK: errno = ENOBUFS; return OP_ERROR_ERRNO; // No buffer space available
case AAUDIO_ERROR_UNIMPLEMENTED: errno = ENOSYS; return OP_ERROR_ERRNO; // Function not implemented
case AAUDIO_ERROR_NO_FREE_HANDLES: errno = EMFILE; return OP_ERROR_ERRNO; // Too many open files
case AAUDIO_ERROR_NO_MEMORY: errno = ENOMEM; return OP_ERROR_ERRNO; // Out of memory
case AAUDIO_ERROR_NULL: errno = EFAULT; return OP_ERROR_ERRNO; // Bad address
case AAUDIO_ERROR_OUT_OF_RANGE: errno = EINVAL; return OP_ERROR_ERRNO; // Invalid argument
case AAUDIO_ERROR_INVALID_HANDLE: errno = EBADF; return OP_ERROR_ERRNO; // Bad file descriptor
case AAUDIO_ERROR_INVALID_STATE: errno = EBADFD; return OP_ERROR_ERRNO; // File descriptor in bad state
case AAUDIO_ERROR_ILLEGAL_ARGUMENT: errno = EINVAL; return OP_ERROR_ERRNO; // Invalid argument
default: return OP_ERROR_INTERNAL;
}
}
// note: all options require restarting the output stream to apply
static aaudio_performance_mode_t op_aaudio_opt_performance_mode = AAUDIO_PERFORMANCE_MODE_POWER_SAVING;
static aaudio_allowed_capture_policy_t op_aaudio_opt_allowed_capture = AAUDIO_ALLOW_CAPTURE_BY_ALL;
static aaudio_sharing_mode_t op_aaudio_opt_sharing_mode = AAUDIO_SHARING_MODE_SHARED;
static bool op_aaudio_opt_disable_spatialization = false;
static int op_aaudio_opt_min_buffer_capacity_ms = 0;
// if we ever decide to support AAUDIO_PERFORMANCE_MODE_LOW_LATENCY streams,
// note that disconnection is broken for shared low-latency streams on RQ1A
// (this doesn't affect us right now since we don't use low-latency shared mmap
// streams)
//
// https://issuetracker.google.com/issues/173928197
static int op_aaudio_set_performance_mode(const char *val)
{
if (!strcmp(val, "none")) {
op_aaudio_opt_performance_mode = AAUDIO_PERFORMANCE_MODE_NONE;
return OP_ERROR_SUCCESS;
}
if (!strcmp(val, "power_saving")) {
op_aaudio_opt_performance_mode = AAUDIO_PERFORMANCE_MODE_POWER_SAVING;
return OP_ERROR_SUCCESS;
}
errno = EINVAL;
return -OP_ERROR_ERRNO;
}
static int op_aaudio_get_performance_mode(char **val)
{
switch (op_aaudio_opt_performance_mode) {
default:
__attribute__((fallthrough));
case AAUDIO_PERFORMANCE_MODE_NONE:
*val = xstrdup("none");
break;
case AAUDIO_PERFORMANCE_MODE_POWER_SAVING:
*val = xstrdup("power_saving");
break;
}
return OP_ERROR_SUCCESS;
}
static int op_aaudio_set_allowed_capture(const char *val)
{
if (!strcmp(val, "all")) {
op_aaudio_opt_allowed_capture = AAUDIO_ALLOW_CAPTURE_BY_ALL;
return OP_ERROR_SUCCESS;
}
if (!strcmp(val, "none")) {
op_aaudio_opt_allowed_capture = AAUDIO_ALLOW_CAPTURE_BY_NONE;
return OP_ERROR_SUCCESS;
}
if (!strcmp(val, "system")) {
op_aaudio_opt_allowed_capture = AAUDIO_ALLOW_CAPTURE_BY_SYSTEM;
return OP_ERROR_SUCCESS;
}
errno = EINVAL;
return -OP_ERROR_ERRNO;
}
static int op_aaudio_get_allowed_capture(char **val)
{
switch (op_aaudio_opt_allowed_capture) {
default:
__attribute__((fallthrough));
case AAUDIO_ALLOW_CAPTURE_BY_ALL:
*val = xstrdup("all");
break;
case AAUDIO_ALLOW_CAPTURE_BY_NONE:
*val = xstrdup("none");
break;
case AAUDIO_ALLOW_CAPTURE_BY_SYSTEM:
*val = xstrdup("system");
break;
}
return OP_ERROR_SUCCESS;
}
static int op_aaudio_set_sharing_mode(const char *val)
{
if (!strcmp(val, "shared")) {
op_aaudio_opt_sharing_mode = AAUDIO_SHARING_MODE_SHARED;
return OP_ERROR_SUCCESS;
}
if (!strcmp(val, "exclusive")) {
op_aaudio_opt_sharing_mode = AAUDIO_SHARING_MODE_EXCLUSIVE;
return OP_ERROR_SUCCESS;
}
errno = EINVAL;
return -OP_ERROR_ERRNO;
}
static int op_aaudio_get_sharing_mode(char **val)
{
switch (op_aaudio_opt_performance_mode) {
default:
__attribute__((fallthrough));
case AAUDIO_SHARING_MODE_SHARED:
*val = xstrdup("shared");
break;
case AAUDIO_SHARING_MODE_EXCLUSIVE:
*val = xstrdup("exclusive");
break;
}
return OP_ERROR_SUCCESS;
}
static int op_aaudio_set_disable_spatialization(const char *val)
{
op_aaudio_opt_disable_spatialization = strcmp(val, "true") ? false : true;
return OP_ERROR_SUCCESS;
}
static int op_aaudio_get_disable_spatialization(char **val)
{
*val = xstrdup(op_aaudio_opt_disable_spatialization ? "true" : "false");
return OP_ERROR_SUCCESS;
}
static int op_aaudio_set_min_buffer_capacity_ms(const char *val)
{
long tmp;
if (str_to_int(val, &tmp) == -1 || tmp < 0 || tmp > 1000) {
errno = EINVAL;
return -OP_ERROR_ERRNO;
}
op_aaudio_opt_min_buffer_capacity_ms = (int) tmp;
return OP_ERROR_SUCCESS;
}
static int op_aaudio_get_min_buffer_capacity_ms(char **val)
{
char tmp[5];
snprintf(tmp, sizeof(tmp), "%d", op_aaudio_opt_min_buffer_capacity_ms);
*val = xstrdup(tmp);
return OP_ERROR_SUCCESS;
}
static bool aaudio_supported() {
if (API_AT_LEAST(27)) {} else {
// don't use AAudio on API 26 due to bug causing crash on some
// devices when closing stream
//
// https://github.com/google/oboe/issues/40
return -OP_ERROR_NOT_SUPPORTED;
}
if (API_AT_LEAST(AAUDIO_MINIMUM_API)) {
return !!&AAudio_createStreamBuilder;
}
return false;
}
static struct {
AAudioStream *stream;
int32_t device;
aaudio_result_t error;
sample_format_t sf;
ssize_t *remap;
char *remap_buf;
} op;
int mixer_notify_output_in, mixer_notify_output_out;
static int op_aaudio_init(void)
{
if (!aaudio_supported()) {
// skip the output plugin (see op_select_any)
return -OP_ERROR_NOT_SUPPORTED;
}
init_pipes(&mixer_notify_output_out, &mixer_notify_output_in);
return OP_ERROR_SUCCESS;
}
REQUIRES_API(AAUDIO_MINIMUM_API)
static int op_aaudio_exit(void)
{
close(mixer_notify_output_out);
close(mixer_notify_output_in);
return OP_ERROR_SUCCESS;
}
REQUIRES_API(AAUDIO_MINIMUM_API)
static void handle_error(AAudioStream *stream, void *userData, aaudio_result_t error) {
if (error == AAUDIO_ERROR_DISCONNECTED) {
notify_via_pipe(mixer_notify_output_in);
}
d_print("stream errored (%d - %s)\n", error, AAudio_convertResultToText(error));
op.error = error;
}
REQUIRES_API(AAUDIO_MINIMUM_API)
static int op_aaudio_open(sample_format_t sf, const channel_position_t *channel_map)
{
aaudio_result_t rc;
AAudioStreamBuilder *bld;
// create the stream builder
rc = AAudio_createStreamBuilder(&bld);
if (rc) {
d_print("create stream builder failed (%d - %s)\n", rc, AAudio_convertResultToText(rc));
return -OP_ERROR_AAUDIO(rc);
}
// set the error callback
AAudioStreamBuilder_setErrorCallback(bld, handle_error, NULL);
// apply the options
AAudioStreamBuilder_setSharingMode(bld, op_aaudio_opt_sharing_mode);
AAudioStreamBuilder_setPerformanceMode(bld, op_aaudio_opt_performance_mode);
if (API_AT_LEAST(28)) AAudioStreamBuilder_setContentType(bld, AAUDIO_CONTENT_TYPE_MUSIC);
if (API_AT_LEAST(28)) AAudioStreamBuilder_setUsage(bld, AAUDIO_USAGE_MEDIA);
if (API_AT_LEAST(29)) AAudioStreamBuilder_setAllowedCapturePolicy(bld, op_aaudio_opt_allowed_capture);
if (API_AT_LEAST(31)) AAudioStreamBuilder_setAttributionTag(bld, "cmus");
if (API_AT_LEAST(32)) AAudioStreamBuilder_setSpatializationBehavior(bld, op_aaudio_opt_disable_spatialization ? AAUDIO_SPATIALIZATION_BEHAVIOR_NEVER : AAUDIO_SPATIALIZATION_BEHAVIOR_AUTO);
// ensure the buffer holds at least the requested amount of audio (default 80ms)
AAudioStreamBuilder_setBufferCapacityInFrames(bld, sf_get_rate(sf) / (1000 / (op_aaudio_opt_min_buffer_capacity_ms ? op_aaudio_opt_min_buffer_capacity_ms : 80)));
// configure the sample format and channel map
rc = configure_aaudio_sf(bld, sf, channel_map, &op.remap);
if (rc) {
d_print("configure format failed (%d - %s)\n", rc, AAudio_convertResultToText(rc));
return -OP_ERROR_AAUDIO(rc);
}
if (op.remap) {
d_print("allocating %zu bytes for remap buffer\n", (size_t) AAudioStream_getBufferCapacityInFrames(op.stream) * (size_t) sf_get_frame_size(sf));
op.remap_buf = xmalloc((size_t) AAudioStream_getBufferCapacityInFrames(op.stream) * (size_t) sf_get_frame_size(sf));
}
op.sf = sf;
// open the stream
op.error = 0;
rc = AAudioStreamBuilder_openStream(bld, &op.stream);
if (rc) {
d_print("open stream failed (%d - %s)\n", rc, AAudio_convertResultToText(rc));
if (op.remap_buf) {
free(op.remap_buf);
op.remap_buf = NULL;
}
if (op.remap) {
free(op.remap);
op.remap = NULL;
}
AAudioStreamBuilder_delete(bld);
return -OP_ERROR_AAUDIO(rc);
}
op.device = AAudioStream_getDeviceId(op.stream);
d_print("optimal buffer frames = %d\n", AAudioStream_getFramesPerBurst(op.stream));
d_print("buffer capacity frames = %d\n", AAudioStream_getBufferCapacityInFrames(op.stream));
// cleanup the stream builder
rc = AAudioStreamBuilder_delete(bld);
if (rc) {
d_print("delete stream builder failed (%d - %s)\n", rc, AAudio_convertResultToText(rc));
if (op.remap_buf) {
free(op.remap_buf);
op.remap_buf = NULL;
}
if (op.remap) {
free(op.remap);
op.remap = NULL;
}
AAudioStream_close(op.stream);
return -OP_ERROR_AAUDIO(rc);
}
// done (we don't actually start the stream until the first write)
return OP_ERROR_SUCCESS;
}
REQUIRES_API(AAUDIO_MINIMUM_API)
static int op_aaudio_close(void)
{
if (op.remap_buf) {
free(op.remap_buf);
op.remap_buf = NULL;
}
if (op.remap) {
free(op.remap);
op.remap = NULL;
}
if (op.stream) {
AAudioStream_close(op.stream);
op.stream = NULL;
}
return OP_ERROR_SUCCESS;
}
REQUIRES_API(AAUDIO_MINIMUM_API)
static aaudio_result_t do_state_change(aaudio_result_t (*request)(AAudioStream *strm), aaudio_stream_state_t state, aaudio_stream_state_t state2)
{
aaudio_result_t rc;
if (op.error) {
rc = op.error;
return rc;
}
if (request) {
d_print("request state change\n");
rc = request(op.stream);
if (rc) {
return rc;
}
}
d_print("wait state change (%d:%s || %d:%s)\n", state, AAudio_convertStreamStateToText(state), state2, AAudio_convertStreamStateToText(state2));
aaudio_stream_state_t currentState = AAUDIO_STREAM_STATE_UNKNOWN;
aaudio_stream_state_t inputState = currentState;
rc = AAUDIO_OK;
while (rc == AAUDIO_OK && currentState != state && (state2 == 0 || currentState != state2)) {
// this is required to prevent hanging during pause_on_output_change
if (op.error) {
rc = op.error;
break;
}
if (currentState == AAUDIO_STREAM_STATE_CLOSING || currentState == AAUDIO_STREAM_STATE_CLOSED || currentState == AAUDIO_STREAM_STATE_DISCONNECTED) {
rc = AAUDIO_ERROR_DISCONNECTED;
break;
}
d_print("current state change %d\r\n", currentState);
rc = AAudioStream_waitForStateChange(op.stream, inputState, &currentState, INT64_MAX);
inputState = currentState;
}
if (rc) {
d_print("failed state change (%d - %s) [current=%d:%s]\n", rc, AAudio_convertResultToText(rc), currentState, AAudio_convertStreamStateToText(currentState));
} else {
d_print("done state change [current=%d:%s]\n", currentState, AAudio_convertStreamStateToText(currentState));
}
return rc;
}
REQUIRES_API(AAUDIO_MINIMUM_API)
static int op_aaudio_drop(void)
{
aaudio_result_t rc;
aaudio_stream_state_t orig_state = AAudioStream_getState(op.stream);
// we can't flush if it's closing
if (orig_state == AAUDIO_STREAM_STATE_CLOSING || orig_state == AAUDIO_STREAM_STATE_CLOSED) {
return -OP_ERROR_NOT_OPEN;
}
// only flush if it isn't already flushed or closed
if (orig_state != AAUDIO_STREAM_STATE_FLUSHED) {
// the stream must be paused to be flushed
if (orig_state == AAUDIO_STREAM_STATE_STARTED || orig_state == AAUDIO_STREAM_STATE_STARTING) {
rc = do_state_change(AAudioStream_requestPause, AAUDIO_STREAM_STATE_PAUSED, 0);
if (rc) {
return -OP_ERROR_AAUDIO(rc);
}
// the stream will be started again on the first write
}
// flush the stream
rc = do_state_change(AAudioStream_requestFlush, AAUDIO_STREAM_STATE_FLUSHED, 0);
if (rc) {
return -OP_ERROR_AAUDIO(rc);
}
}
return OP_ERROR_SUCCESS;
}
REQUIRES_API(AAUDIO_MINIMUM_API)
static int op_aaudio_write(const char *buf, int count)
{
int32_t device;
aaudio_result_t rc;
aaudio_stream_state_t state;
// if the stream errored, return an error so cmus restarts the output
// plugin
//
// note that this isn't strictly required since AAudioStream_write will
// return an error on stream disconnection, which will cause cmus to
// reopen the output plugin
//
// https://github.com/google/oboe/wiki/TechNote_Disconnect
if (op.error) {
return -OP_ERROR_AAUDIO(op.error);
}
// note: this is cheap; it's just a field getter internally
device = AAudioStream_getDeviceId(op.stream);
if (op.device != device) {
if (op.device != -1) {
notify_via_pipe(mixer_notify_output_in);
}
op.device = device;
}
// start the stream on the first write (rather than after opening or
// flushing since cmus may not always use the stream and starting a
// stream is somewhat expensive)
//
// note: this is cheap; it's just a atomic field getter internally
state = AAudioStream_getState(op.stream);
if (state == AAUDIO_STREAM_STATE_CLOSING || state == AAUDIO_STREAM_STATE_CLOSED) {
return -OP_ERROR_NOT_OPEN;
}
if (state != AAUDIO_STREAM_STATE_STARTING && state != AAUDIO_STREAM_STATE_STARTED) {
rc = do_state_change(AAudioStream_requestStart, AAUDIO_STREAM_STATE_STARTED, AAUDIO_STREAM_STATE_STARTING);
if (rc) {
return -OP_ERROR_AAUDIO(rc);
}
}
// this should never happen since op_aaudio_buffer_space should always
// be less than AAudioStream_getBufferCapacityInFrames, and cmus
// determines how much to write using it
BUG_ON(count >= AAudioStream_getBufferCapacityInFrames(op.stream) * sf_get_frame_size(op.sf));
// remap if necessary
buf = (char *) apply_channel_remap((uint8_t *) op.remap_buf, (uint8_t *) buf, count, op.sf, op.remap);
// synchronously write the samples to the buffer
rc = AAudioStream_write(op.stream, buf, count / (int) sf_get_frame_size(op.sf), INT64_MAX);
if (rc < 0) {
d_print("write %d = error %d - %s [device=%d] [state=%d]\n", count / (int) sf_get_frame_size(op.sf), rc, AAudio_convertResultToText(rc), device, state);
return -OP_ERROR_AAUDIO(rc);
}
d_print("write %d = %d (* %d bytes) [device=%d] [state=%d]\n", count / (int) sf_get_frame_size(op.sf), rc, (int) sf_get_frame_size(op.sf), device, state);
// return the number of bytes we write
return (int) sf_get_frame_size(op.sf) * rc;
}
REQUIRES_API(AAUDIO_MINIMUM_API)
static int op_aaudio_pause(void)
{
// request stream pause, wait until it completes
return -OP_ERROR_AAUDIO(do_state_change(AAudioStream_requestPause, AAUDIO_STREAM_STATE_PAUSED, 0));
}
REQUIRES_API(AAUDIO_MINIMUM_API)
static int op_aaudio_unpause(void)
{
// request stream start, wait until it starts to start (i.e., will start
// consuming frames written to it)
return -OP_ERROR_AAUDIO(do_state_change(AAudioStream_requestStart, AAUDIO_STREAM_STATE_STARTED, AAUDIO_STREAM_STATE_STARTING));
}
REQUIRES_API(AAUDIO_MINIMUM_API)
static int op_aaudio_buffer_space(void)
{
int32_t optimal, nonblock;
// optimal buffer amount (anecdotally, this generally seems to be less
// than half the buffer capacity)
optimal = AAudioStream_getFramesPerBurst(op.stream) * (int32_t) sf_get_frame_size(op.sf);
// max buffer amount (without blocking)
nonblock = AAudioStream_getBufferSizeInFrames(op.stream) * (int32_t) sf_get_frame_size(op.sf);
// want to write the optimal amount (up to the nonblock amount)
return optimal < nonblock ? optimal : nonblock;
}
REQUIRES_API(AAUDIO_MINIMUM_API)
static int op_aaudio_mixer_init(void)
{
if (!aaudio_supported()) {
// skip the output plugin (see op_select_any)
return -OP_ERROR_NOT_SUPPORTED;
}
return OP_ERROR_SUCCESS;
}
REQUIRES_API(AAUDIO_MINIMUM_API)
static int op_aaudio_mixer_exit(void)
{
return OP_ERROR_SUCCESS;
}
REQUIRES_API(AAUDIO_MINIMUM_API)
static int op_aaudio_mixer_open(int *volume_max)
{
*volume_max = UINT16_MAX;
return OP_ERROR_SUCCESS;
}
REQUIRES_API(AAUDIO_MINIMUM_API)
static int op_aaudio_mixer_close(void)
{
return OP_ERROR_SUCCESS;
}
REQUIRES_API(AAUDIO_MINIMUM_API)
static int op_aaudio_mixer_get_fds(int what, int *fds)
{
switch (what) {
case MIXER_FDS_OUTPUT:
fds[0] = mixer_notify_output_out;
return 1;
default:
return 0;
}
}
REQUIRES_API(AAUDIO_MINIMUM_API)
static int op_aaudio_mixer_set_volume(int l, int r)
{
return -OP_ERROR_NOT_SUPPORTED;
}
REQUIRES_API(AAUDIO_MINIMUM_API)
static int op_aaudio_mixer_get_volume(int *l, int *r)
{
// aaudio doesn't support volume control, so say the volume is 100%
*l = *r = UINT16_MAX;
return OP_ERROR_SUCCESS;
}
REQUIRES_API(AAUDIO_MINIMUM_API)
const struct output_plugin_ops op_pcm_ops = {
.init = op_aaudio_init,
.exit = op_aaudio_exit,
.open = op_aaudio_open,
.close = op_aaudio_close,
.drop = op_aaudio_drop,
.write = op_aaudio_write,
.pause = op_aaudio_pause,
.unpause = op_aaudio_unpause,
.buffer_space = op_aaudio_buffer_space,
};
REQUIRES_API(AAUDIO_MINIMUM_API)
const struct mixer_plugin_ops op_mixer_ops = {
.init = op_aaudio_mixer_init,
.exit = op_aaudio_mixer_exit,
.open = op_aaudio_mixer_open,
.close = op_aaudio_mixer_close,
.get_fds.abi_2 = op_aaudio_mixer_get_fds,
.set_volume = op_aaudio_mixer_set_volume,
.get_volume = op_aaudio_mixer_get_volume,
};
const struct output_plugin_opt op_pcm_options[] = {
OPT(op_aaudio, performance_mode),
OPT(op_aaudio, allowed_capture),
OPT(op_aaudio, sharing_mode),
OPT(op_aaudio, disable_spatialization),
OPT(op_aaudio, min_buffer_capacity_ms),
{ NULL },
};
const struct mixer_plugin_opt op_mixer_options[] = {
{ NULL },
};
const int op_priority = -3; // higher priority than pulse (-2)
const unsigned op_abi_version = OP_ABI_VERSION;

368
op/alsa.c Normal file
View File

@@ -0,0 +1,368 @@
/*
* Copyright 2008-2013 Various Authors
* Copyright 2008 Jonathan Kleinehellefort
* Copyright 2004-2005 Timo Hirvonen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/*
* snd_pcm_state_t:
*
* Open
* SND_PCM_STATE_OPEN = 0,
*
* Setup installed
* SND_PCM_STATE_SETUP = 1,
*
* Ready to start
* SND_PCM_STATE_PREPARED = 2,
*
* Running
* SND_PCM_STATE_RUNNING = 3,
*
* Stopped: underrun (playback) or overrun (capture) detected
* SND_PCM_STATE_XRUN = 4,
*
* Draining: running (playback) or stopped (capture)
* SND_PCM_STATE_DRAINING = 5,
*
* Paused
* SND_PCM_STATE_PAUSED = 6,
*
* Hardware is suspended
* SND_PCM_STATE_SUSPENDED = 7,
*
* Hardware is disconnected
* SND_PCM_STATE_DISCONNECTED = 8,
*/
#include "../op.h"
#include "../utils.h"
#include "../xmalloc.h"
#include "../sf.h"
#include "../debug.h"
#define ALSA_PCM_NEW_HW_PARAMS_API
#define ALSA_PCM_NEW_SW_PARAMS_API
#include <alsa/asoundlib.h>
static sample_format_t alsa_sf;
static snd_pcm_t *alsa_handle;
static snd_pcm_format_t alsa_fmt;
static int alsa_can_pause;
static snd_pcm_status_t *status;
/* bytes (bits * channels / 8) */
static int alsa_frame_size;
/* configuration */
static char *alsa_dsp_device = NULL;
#if 0
#define debug_ret(func, ret) \
d_print("%s returned %d %s\n", func, ret, ret < 0 ? snd_strerror(ret) : "")
#else
#define debug_ret(func, ret) do { } while (0)
#endif
static int alsa_error_to_op_error(int err)
{
if (!err)
return OP_ERROR_SUCCESS;
err = -err;
if (err < SND_ERROR_BEGIN) {
errno = err;
return -OP_ERROR_ERRNO;
}
return -OP_ERROR_INTERNAL;
}
/* we don't want error messages to stderr */
static void error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...)
{
}
static int op_alsa_init(void)
{
int rc;
snd_lib_error_set_handler(error_handler);
if (alsa_dsp_device == NULL)
alsa_dsp_device = xstrdup("default");
rc = snd_pcm_status_malloc(&status);
if (rc < 0) {
free(alsa_dsp_device);
alsa_dsp_device = NULL;
errno = ENOMEM;
return -OP_ERROR_ERRNO;
}
return OP_ERROR_SUCCESS;
}
static int op_alsa_exit(void)
{
snd_pcm_status_free(status);
free(alsa_dsp_device);
alsa_dsp_device = NULL;
return OP_ERROR_SUCCESS;
}
/* randomize hw params */
static int alsa_set_hw_params(void)
{
snd_pcm_hw_params_t *hwparams = NULL;
unsigned int buffer_time_max = 300 * 1000; /* us */
const char *cmd;
unsigned int rate;
int rc, dir;
snd_pcm_hw_params_malloc(&hwparams);
cmd = "snd_pcm_hw_params_any";
rc = snd_pcm_hw_params_any(alsa_handle, hwparams);
if (rc < 0)
goto error;
cmd = "snd_pcm_hw_params_set_buffer_time_max";
rc = snd_pcm_hw_params_set_buffer_time_max(alsa_handle, hwparams,
&buffer_time_max, &dir);
if (rc < 0)
goto error;
alsa_can_pause = snd_pcm_hw_params_can_pause(hwparams);
d_print("can pause = %d\n", alsa_can_pause);
cmd = "snd_pcm_hw_params_set_access";
rc = snd_pcm_hw_params_set_access(alsa_handle, hwparams,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (rc < 0)
goto error;
alsa_fmt = snd_pcm_build_linear_format(sf_get_bits(alsa_sf), sf_get_bits(alsa_sf),
sf_get_signed(alsa_sf) ? 0 : 1,
sf_get_bigendian(alsa_sf));
cmd = "snd_pcm_hw_params_set_format";
rc = snd_pcm_hw_params_set_format(alsa_handle, hwparams, alsa_fmt);
if (rc < 0)
goto error;
cmd = "snd_pcm_hw_params_set_channels";
rc = snd_pcm_hw_params_set_channels(alsa_handle, hwparams, sf_get_channels(alsa_sf));
if (rc < 0)
goto error;
cmd = "snd_pcm_hw_params_set_rate";
rate = sf_get_rate(alsa_sf);
dir = 0;
rc = snd_pcm_hw_params_set_rate_near(alsa_handle, hwparams, &rate, &dir);
if (rc < 0)
goto error;
d_print("rate=%d\n", rate);
cmd = "snd_pcm_hw_params";
rc = snd_pcm_hw_params(alsa_handle, hwparams);
if (rc < 0)
goto error;
goto out;
error:
d_print("%s: error: %s\n", cmd, snd_strerror(rc));
out:
snd_pcm_hw_params_free(hwparams);
return rc;
}
static int op_alsa_open(sample_format_t sf, const channel_position_t *channel_map)
{
int rc;
alsa_sf = sf;
alsa_frame_size = sf_get_frame_size(alsa_sf);
rc = snd_pcm_open(&alsa_handle, alsa_dsp_device, SND_PCM_STREAM_PLAYBACK, 0);
if (rc < 0)
goto error;
rc = alsa_set_hw_params();
if (rc)
goto close_error;
rc = snd_pcm_prepare(alsa_handle);
if (rc < 0)
goto close_error;
return OP_ERROR_SUCCESS;
close_error:
snd_pcm_close(alsa_handle);
error:
return alsa_error_to_op_error(rc);
}
static int op_alsa_close(void)
{
int rc;
rc = snd_pcm_drain(alsa_handle);
debug_ret("snd_pcm_drain", rc);
rc = snd_pcm_close(alsa_handle);
debug_ret("snd_pcm_close", rc);
return alsa_error_to_op_error(rc);
}
static int op_alsa_drop(void)
{
int rc;
rc = snd_pcm_drop(alsa_handle);
debug_ret("snd_pcm_drop", rc);
rc = snd_pcm_prepare(alsa_handle);
debug_ret("snd_pcm_prepare", rc);
/* drop set state to SETUP
* prepare set state to PREPARED
*
* so if old state was PAUSED we can't UNPAUSE (see op_alsa_unpause)
*/
return alsa_error_to_op_error(rc);
}
static int op_alsa_write(const char *buffer, int count)
{
int rc, len;
int recovered = 0;
len = count / alsa_frame_size;
again:
rc = snd_pcm_writei(alsa_handle, buffer, len);
if (rc < 0) {
// rc _should_ be either -EBADFD, -EPIPE or -ESTRPIPE
if (!recovered && (rc == -EINTR || rc == -EPIPE || rc == -ESTRPIPE)) {
d_print("snd_pcm_writei failed: %s, trying to recover\n",
snd_strerror(rc));
recovered++;
// this handles -EINTR, -EPIPE and -ESTRPIPE
// for other errors it just returns the error code
rc = snd_pcm_recover(alsa_handle, rc, 1);
if (!rc)
goto again;
}
/* this handles EAGAIN too which is not critical error */
return alsa_error_to_op_error(rc);
}
rc *= alsa_frame_size;
return rc;
}
static int op_alsa_buffer_space(void)
{
int rc;
snd_pcm_sframes_t f;
f = snd_pcm_avail_update(alsa_handle);
while (f < 0) {
d_print("snd_pcm_avail_update failed: %s, trying to recover\n",
snd_strerror(f));
rc = snd_pcm_recover(alsa_handle, f, 1);
if (rc < 0) {
d_print("recovery failed: %s\n", snd_strerror(rc));
return alsa_error_to_op_error(rc);
}
f = snd_pcm_avail_update(alsa_handle);
}
return f * alsa_frame_size;
}
static int op_alsa_pause(void)
{
int rc = 0;
if (alsa_can_pause) {
snd_pcm_state_t state = snd_pcm_state(alsa_handle);
if (state == SND_PCM_STATE_PREPARED) {
// state is PREPARED -> no need to pause
} else if (state == SND_PCM_STATE_RUNNING) {
// state is RUNNING - > pause
rc = snd_pcm_pause(alsa_handle, 1);
debug_ret("snd_pcm_pause", rc);
} else {
d_print("error: state is not RUNNING or PREPARED\n");
rc = -OP_ERROR_INTERNAL;
}
} else {
rc = snd_pcm_drop(alsa_handle);
debug_ret("snd_pcm_drop", rc);
}
return alsa_error_to_op_error(rc);
}
static int op_alsa_unpause(void)
{
int rc = 0;
if (alsa_can_pause) {
snd_pcm_state_t state = snd_pcm_state(alsa_handle);
if (state == SND_PCM_STATE_PREPARED) {
// state is PREPARED -> no need to unpause
} else if (state == SND_PCM_STATE_PAUSED) {
// state is PAUSED -> unpause
rc = snd_pcm_pause(alsa_handle, 0);
debug_ret("snd_pcm_pause", rc);
} else {
d_print("error: state is not PAUSED nor PREPARED\n");
rc = -OP_ERROR_INTERNAL;
}
} else {
rc = snd_pcm_prepare(alsa_handle);
debug_ret("snd_pcm_prepare", rc);
}
return alsa_error_to_op_error(rc);
}
static int op_alsa_set_device(const char *val)
{
free(alsa_dsp_device);
alsa_dsp_device = xstrdup(val);
return OP_ERROR_SUCCESS;
}
static int op_alsa_get_device(char **val)
{
if (alsa_dsp_device)
*val = xstrdup(alsa_dsp_device);
return OP_ERROR_SUCCESS;
}
const struct output_plugin_ops op_pcm_ops = {
.init = op_alsa_init,
.exit = op_alsa_exit,
.open = op_alsa_open,
.close = op_alsa_close,
.drop = op_alsa_drop,
.write = op_alsa_write,
.buffer_space = op_alsa_buffer_space,
.pause = op_alsa_pause,
.unpause = op_alsa_unpause,
};
const struct output_plugin_opt op_pcm_options[] = {
OPT(op_alsa, device),
{ NULL },
};
const int op_priority = 0;
const unsigned op_abi_version = OP_ABI_VERSION;

308
op/ao.c Normal file
View File

@@ -0,0 +1,308 @@
/*
* Copyright 2008-2013 Various Authors
* Copyright 2006 Johannes Weißl
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "../op.h"
#include "../xmalloc.h"
#include "../utils.h"
#include "../misc.h"
#include "../debug.h"
/*
* <ao/ao.h> uses FILE but doesn't include stdio.h.
* Also we use snprintf().
*/
#include <stdio.h>
#include <strings.h>
#include <ao/ao.h>
#ifdef AO_EBADFORMAT
#define AO_API_1
#endif
static ao_device *libao_device;
static char *wav_dir = NULL;
static int wav_counter = 1;
static int is_wav = 0;
/* configuration */
static char *libao_driver = NULL;
static int libao_buffer_space = 16384;
static int libao_cur_buffer_space = 0;
static char *libao_device_interface = NULL;
static int op_ao_init(void)
{
/* ignore config value */
wav_counter = 1;
ao_initialize();
return 0;
}
static int op_ao_exit(void)
{
free(libao_driver);
ao_shutdown();
return 0;
}
/* http://www.xiph.org/ao/doc/ao_sample_format.html */
static const struct {
channel_position_t pos;
const char *str;
} ao_channel_mapping[] = {
{ CHANNEL_POSITION_LEFT, "L" },
{ CHANNEL_POSITION_RIGHT, "R" },
{ CHANNEL_POSITION_CENTER, "C" },
{ CHANNEL_POSITION_MONO, "M" },
{ CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, "CL" },
{ CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, "CR" },
{ CHANNEL_POSITION_REAR_LEFT, "BL" },
{ CHANNEL_POSITION_REAR_RIGHT, "BR" },
{ CHANNEL_POSITION_REAR_CENTER, "BC" },
{ CHANNEL_POSITION_SIDE_LEFT, "SL" },
{ CHANNEL_POSITION_SIDE_RIGHT, "SR" },
{ CHANNEL_POSITION_LFE, "LFE" },
{ CHANNEL_POSITION_INVALID, "X" },
};
#ifdef AO_API_1
static char *ao_channel_matrix(int channels, const channel_position_t *map)
{
int i, j;
char buf[256] = "";
if (!map || !channel_map_valid(map))
return NULL;
for (i = 0; i < channels; i++) {
const channel_position_t pos = map[i];
int found = 0;
for (j = 0; j < N_ELEMENTS(ao_channel_mapping); j++) {
if (pos == ao_channel_mapping[j].pos) {
strcat(buf, ao_channel_mapping[j].str);
strcat(buf, ",");
found = 1;
break;
}
}
if (!found)
strcat(buf, "M,");
}
buf[strlen(buf)-1] = '\0';
return xstrdup(buf);
}
#endif
static int op_ao_open(sample_format_t sf, const channel_position_t *channel_map)
{
ao_sample_format format = {
.bits = sf_get_bits(sf),
.rate = sf_get_rate(sf),
.channels = sf_get_channels(sf),
.byte_format = sf_get_bigendian(sf) ? AO_FMT_BIG : AO_FMT_LITTLE,
#ifdef AO_API_1
.matrix = ao_channel_matrix(sf_get_channels(sf), channel_map)
#endif
};
int driver;
if (libao_driver == NULL) {
driver = ao_default_driver_id();
} else {
driver = ao_driver_id(libao_driver);
is_wav = strcasecmp(libao_driver, "wav") == 0;
}
if (driver == -1) {
errno = ENODEV;
return -OP_ERROR_ERRNO;
}
if (is_wav) {
char file[512];
if (wav_dir == NULL)
wav_dir = xstrdup(home_dir);
snprintf(file, sizeof(file), "%s/%02d.wav", wav_dir, wav_counter);
libao_device = ao_open_file(driver, file, 0, &format, NULL);
} else {
ao_option *ao_options = NULL;
if (libao_device_interface) {
ao_append_option(&ao_options, "dev", libao_device_interface);
}
libao_device = ao_open_live(driver, &format, ao_options);
}
if (libao_device == NULL) {
switch (errno) {
case AO_ENODRIVER:
case AO_ENOTFILE:
case AO_ENOTLIVE:
case AO_EOPENDEVICE:
errno = ENODEV;
return -OP_ERROR_ERRNO;
case AO_EBADOPTION:
errno = EINVAL;
return -OP_ERROR_ERRNO;
case AO_EOPENFILE:
errno = EACCES;
return -OP_ERROR_ERRNO;
case AO_EFILEEXISTS:
errno = EEXIST;
return -OP_ERROR_ERRNO;
case AO_EFAIL:
default:
return -OP_ERROR_INTERNAL;
}
}
/* ensure that the buffer size is a multiple of the frame size */
libao_cur_buffer_space = is_wav ? 128 * 1024 : libao_buffer_space;
libao_cur_buffer_space -= libao_cur_buffer_space % sf_get_frame_size(sf);
#ifdef AO_API_1
d_print("channel matrix: %s\n", format.matrix ? format.matrix : "default");
#endif
return 0;
}
static int op_ao_close(void)
{
ao_close(libao_device);
if (is_wav)
wav_counter++;
return 0;
}
static int op_ao_write(const char *buffer, int count)
{
if (ao_play(libao_device, (void *)buffer, count) == 0)
return -1;
return count;
}
static int op_ao_buffer_space(void)
{
return libao_cur_buffer_space;
}
static int op_ao_set_buffer_size(const char *val)
{
long int ival;
if (str_to_int(val, &ival) || ival < 4096) {
errno = EINVAL;
return -OP_ERROR_ERRNO;
}
libao_buffer_space = ival;
return 0;
}
static int op_ao_get_buffer_size(char **val)
{
*val = xnew(char, 22);
snprintf(*val, 22, "%d", libao_buffer_space);
return 0;
}
static int op_ao_set_driver(const char *val)
{
free(libao_driver);
libao_driver = NULL;
if (val[0])
libao_driver = xstrdup(val);
return 0;
}
static int op_ao_get_driver(char **val)
{
if (libao_driver)
*val = xstrdup(libao_driver);
return 0;
}
static int op_ao_set_wav_counter(const char *val)
{
long int ival;
if (str_to_int(val, &ival)) {
errno = EINVAL;
return -OP_ERROR_ERRNO;
}
wav_counter = ival;
return 0;
}
static int op_ao_get_wav_counter(char **val)
{
*val = xnew(char, 22);
snprintf(*val, 22, "%d", wav_counter);
return 0;
}
static int op_ao_set_wav_dir(const char *val)
{
free(wav_dir);
wav_dir = xstrdup(val);
return 0;
}
static int op_ao_get_wav_dir(char **val)
{
if (wav_dir == NULL)
wav_dir = xstrdup(home_dir);
*val = expand_filename(wav_dir);
return 0;
}
static int op_ao_set_device_interface(const char *val)
{
free(libao_device_interface);
libao_device_interface = NULL;
if (val[0])
libao_device_interface = xstrdup(val);
return 0;
}
static int op_ao_get_device_interface(char **val)
{
if (libao_device_interface)
*val = xstrdup(libao_device_interface);
return 0;
}
const struct output_plugin_ops op_pcm_ops = {
.init = op_ao_init,
.exit = op_ao_exit,
.open = op_ao_open,
.close = op_ao_close,
.write = op_ao_write,
.buffer_space = op_ao_buffer_space,
};
const struct output_plugin_opt op_pcm_options[] = {
OPT(op_ao, buffer_size),
OPT(op_ao, driver),
OPT(op_ao, wav_counter),
OPT(op_ao, wav_dir),
OPT(op_ao, device_interface),
{ NULL },
};
const int op_priority = 3;
const unsigned op_abi_version = OP_ABI_VERSION;

123
op/arts.c Normal file
View File

@@ -0,0 +1,123 @@
/*
* Copyright 2008-2013 Various Authors
* Copyright 2004-2005 Timo Hirvonen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "../op.h"
#include "../xmalloc.h"
#include "../debug.h"
#include <artsc.h>
static arts_stream_t arts_stream;
static sample_format_t arts_sf;
static int arts_buffer_size;
static int op_arts_init(void)
{
int rc;
rc = arts_init();
if (rc < 0) {
return -1;
}
return 0;
}
static int op_arts_exit(void)
{
arts_free();
return 0;
}
static int op_arts_open(sample_format_t sf, const channel_position_t *channel_map)
{
int buffer_time, server_latency, total_latency;
int blocking;
arts_sf = sf;
arts_stream = arts_play_stream(sf_get_rate(arts_sf), sf_get_bits(arts_sf),
sf_get_channels(arts_sf), "cmus");
blocking = arts_stream_set(arts_stream, ARTS_P_BLOCKING, 0);
if (blocking) {
}
arts_buffer_size = arts_stream_get(arts_stream, ARTS_P_BUFFER_SIZE);
if (arts_buffer_size < 0) {
}
buffer_time = arts_stream_get(arts_stream, ARTS_P_BUFFER_TIME);
server_latency = arts_stream_get(arts_stream, ARTS_P_SERVER_LATENCY);
total_latency = arts_stream_get(arts_stream, ARTS_P_TOTAL_LATENCY);
d_print("buffer_time: %d\n", buffer_time);
d_print("server_latency: %d\n", server_latency);
d_print("total_latency: %d\n", total_latency);
return 0;
}
static int op_arts_close(void)
{
arts_close_stream(arts_stream);
return 0;
}
static int op_arts_write(const char *buffer, int count)
{
int rc;
rc = arts_write(arts_stream, buffer, count);
if (rc < 0) {
d_print("rc = %d, count = %d\n", rc, count);
return -1;
}
return rc;
}
static int op_arts_pause(void)
{
return 0;
}
static int op_arts_unpause(void)
{
return 0;
}
static int op_arts_buffer_space(void)
{
int space;
space = arts_stream_get(arts_stream, ARTS_P_BUFFER_SPACE);
if (space < 0)
return -1;
return space;
}
const struct output_plugin_ops op_pcm_ops = {
.init = op_arts_init,
.exit = op_arts_exit,
.open = op_arts_open,
.close = op_arts_close,
.write = op_arts_write,
.pause = op_arts_pause,
.unpause = op_arts_unpause,
.buffer_space = op_arts_buffer_space,
};
const struct output_plugin_opt op_pcm_options[] = {
{ NULL },
};
const int op_priority = 4;
const unsigned op_abi_version = OP_ABI_VERSION;

984
op/coreaudio.c Normal file
View File

@@ -0,0 +1,984 @@
/*
* Copyright (C) 2015 Yue Wang <yuleopen@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdatomic.h>
#include <AudioUnit/AudioUnit.h>
#include <CoreAudio/CoreAudio.h>
#include "../debug.h"
#include "../op.h"
#include "../mixer.h"
#include "../sf.h"
#include "../utils.h"
#include "../xmalloc.h"
#if !defined(MAC_OS_VERSION_12_0) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_VERSION_12_0
#define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster
#endif
/////////////////////////////////////////////////////////////////////////////
// Ring buffer utility from the PortAudio project.
// Original licence information is listed below.
/*
* Portable Audio I/O Library
* Ring Buffer utility.
*
* Author: Phil Burk, http://www.softsynth.com
* modified for SMP safety on Mac OS X by Bjorn Roche
* modified for SMP safety on Linux by Leland Lucius
* also, allowed for const where possible
* Note that this is safe only for a single-thread reader and a
* single-thread writer.
*
* This program uses the PortAudio Portable Audio Library.
* For more information see: http://www.portaudio.com
* Copyright (c) 1999-2000 Ross Bencina and Phil Burk
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/
typedef struct coreaudio_ring_buffer_t {
size_t buffer_size;
size_t write_index;
size_t read_index;
size_t big_mask;
size_t small_mask;
char *buffer;
} coreaudio_ring_buffer_t;
static int coreaudio_ring_buffer_init(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes);
static void coreaudio_ring_buffer_destroy(coreaudio_ring_buffer_t *rbuf);
static void coreaudio_ring_buffer_flush(coreaudio_ring_buffer_t *rbuf);
static size_t coreaudio_ring_buffer_write_available(coreaudio_ring_buffer_t *rbuf);
static size_t coreaudio_ring_buffer_read_available(coreaudio_ring_buffer_t *rbuf);
static size_t coreaudio_ring_buffer_write(coreaudio_ring_buffer_t *rbuf, const char *data, size_t num_of_bytes);
static size_t coreaudio_ring_buffer_read(coreaudio_ring_buffer_t *rbuf, char *data, size_t num_of_bytes);
static size_t coreaudio_ring_buffer_write_regions(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes, char **data_ptr1, size_t *size_ptr1, char **data_ptr2, size_t *size_ptr2);
static size_t coreaudio_ring_buffer_advance_write_index(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes);
static size_t coreaudio_ring_buffer_read_regions(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes, char **data_ptr1, size_t *size_ptr1, char **data_ptr2, size_t *size_ptr2);
static size_t coreaudio_ring_buffer_advance_read_index(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes);
static int coreaudio_ring_buffer_init(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes)
{
if (((num_of_bytes - 1) & num_of_bytes) != 0)
return -1; /*Not Power of two. */
rbuf->buffer_size = num_of_bytes;
rbuf->buffer = (char *)xmalloc(num_of_bytes);
coreaudio_ring_buffer_flush(rbuf);
rbuf->big_mask = (num_of_bytes *2) - 1;
rbuf->small_mask = (num_of_bytes) - 1;
return 0;
}
static void coreaudio_ring_buffer_destroy(coreaudio_ring_buffer_t *rbuf)
{
if (rbuf->buffer)
free(rbuf->buffer);
rbuf->buffer = NULL;
rbuf->buffer_size = 0;
rbuf->write_index = 0;
rbuf->read_index = 0;
rbuf->big_mask = 0;
rbuf->small_mask = 0;
}
static size_t coreaudio_ring_buffer_read_available(coreaudio_ring_buffer_t *rbuf)
{
atomic_thread_fence(memory_order_seq_cst);
return ((rbuf->write_index - rbuf->read_index) & rbuf->big_mask);
}
static size_t coreaudio_ring_buffer_write_available(coreaudio_ring_buffer_t *rbuf)
{
return (rbuf->buffer_size - coreaudio_ring_buffer_read_available(rbuf));
}
static void coreaudio_ring_buffer_flush(coreaudio_ring_buffer_t *rbuf)
{
rbuf->write_index = rbuf->read_index = 0;
}
static size_t coreaudio_ring_buffer_write_regions(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes, char **data_ptr1, size_t *size_ptr1, char **data_ptr2, size_t *size_ptr2)
{
size_t index;
size_t available = coreaudio_ring_buffer_write_available(rbuf);
if (num_of_bytes > available)
num_of_bytes = available;
index = rbuf->write_index & rbuf->small_mask;
if ((index + num_of_bytes) > rbuf->buffer_size) {
size_t first_half = rbuf->buffer_size - index;
*data_ptr1 = &rbuf->buffer[index];
*size_ptr1 = first_half;
*data_ptr2 = &rbuf->buffer[0];
*size_ptr2 = num_of_bytes - first_half;
} else {
*data_ptr1 = &rbuf->buffer[index];
*size_ptr1 = num_of_bytes;
*data_ptr2 = NULL;
*size_ptr2 = 0;
}
return num_of_bytes;
}
static size_t coreaudio_ring_buffer_advance_write_index(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes)
{
atomic_thread_fence(memory_order_seq_cst);
return rbuf->write_index = (rbuf->write_index + num_of_bytes) & rbuf->big_mask;
}
static size_t coreaudio_ring_buffer_read_regions(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes, char **data_ptr1, size_t *size_ptr1, char **data_ptr2, size_t *size_ptr2)
{
size_t index;
size_t available = coreaudio_ring_buffer_read_available(rbuf);
if (num_of_bytes > available)
num_of_bytes = available;
index = rbuf->read_index & rbuf->small_mask;
if ((index + num_of_bytes) > rbuf->buffer_size) {
size_t first_half = rbuf->buffer_size - index;
*data_ptr1 = &rbuf->buffer[index];
*size_ptr1 = first_half;
*data_ptr2 = &rbuf->buffer[0];
*size_ptr2 = num_of_bytes - first_half;
} else {
*data_ptr1 = &rbuf->buffer[index];
*size_ptr1 = num_of_bytes;
*data_ptr2 = NULL;
*size_ptr2 = 0;
}
return num_of_bytes;
}
static size_t coreaudio_ring_buffer_advance_read_index(coreaudio_ring_buffer_t *rbuf, size_t num_of_bytes)
{
atomic_thread_fence(memory_order_seq_cst);
return rbuf->read_index = (rbuf->read_index + num_of_bytes) & rbuf->big_mask;
}
static size_t coreaudio_ring_buffer_write(coreaudio_ring_buffer_t *rbuf, const char *data, size_t num_of_bytes)
{
size_t size1, size2, num_write;
char *data1, *data2;
num_write = coreaudio_ring_buffer_write_regions(rbuf, num_of_bytes, &data1, &size1, &data2, &size2);
if (size2 > 0) {
memcpy(data1, data, size1);
data = ((char *) data) + size1;
memcpy(data2, data, size2);
} else {
memcpy(data1, data, size1);
}
coreaudio_ring_buffer_advance_write_index(rbuf, num_write);
return num_write;
}
static size_t coreaudio_ring_buffer_read(coreaudio_ring_buffer_t *rbuf, char *data, size_t num_of_bytes)
{
size_t size1, size2, num_read;
char *data1, *data2;
num_read = coreaudio_ring_buffer_read_regions(rbuf, num_of_bytes, &data1, &size1, &data2, &size2);
if (size2 > 0) {
memcpy(data, data1, size1);
data = ((char *) data) + size1;
memcpy(data, data2, size2);
} else {
memcpy(data, data1, size1);
}
coreaudio_ring_buffer_advance_read_index(rbuf, num_read);
return num_read;
}
// End of ring buffer utility from the PortAudio project.
/////////////////////////////////////////////////////////////////////////////
static char *coreaudio_opt_device_name = NULL;
static bool coreaudio_opt_enable_hog_mode = false;
static bool coreaudio_opt_sync_rate = false;
static int coreaudio_max_volume = 100;
static AudioDeviceID coreaudio_device_id = kAudioDeviceUnknown;
static AudioStreamBasicDescription coreaudio_format_description;
static AudioUnit coreaudio_audio_unit = NULL;
static UInt32 coreaudio_buffer_frame_size = 1;
static coreaudio_ring_buffer_t coreaudio_ring_buffer = {0, 0, 0, 0, 0, NULL};
static UInt32 coreaudio_stereo_channels[2];
static int coreaudio_mixer_pipe_in = 0;
static int coreaudio_mixer_pipe_out = 0;
static OSStatus coreaudio_device_volume_change_listener(AudioObjectID inObjectID,
UInt32 inNumberAddresses,
const AudioObjectPropertyAddress inAddresses[],
void *inClientData)
{
notify_via_pipe(coreaudio_mixer_pipe_in);
return noErr;
}
static OSStatus coreaudio_play_callback(void *user_data,
AudioUnitRenderActionFlags *flags,
const AudioTimeStamp *ts,
UInt32 busnum,
UInt32 nframes,
AudioBufferList *buflist)
{
int count = nframes * coreaudio_format_description.mBytesPerFrame;
buflist->mBuffers[0].mDataByteSize = coreaudio_ring_buffer_read(&coreaudio_ring_buffer,
buflist->mBuffers[0].mData,
count);
return noErr;
}
static AudioDeviceID coreaudio_get_default_device()
{
AudioObjectPropertyAddress aopa = {
kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain
};
AudioDeviceID dev_id = kAudioDeviceUnknown;
UInt32 dev_id_size = sizeof(dev_id);
AudioObjectGetPropertyData(kAudioObjectSystemObject,
&aopa,
0,
NULL,
&dev_id_size,
&dev_id);
return dev_id;
}
static AudioDeviceID coreaudio_find_device(const char *dev_name)
{
AudioObjectPropertyAddress aopa = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain
};
UInt32 property_size = 0;
OSStatus err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
&aopa,
0,
NULL,
&property_size);
if (err != noErr)
return kAudioDeviceUnknown;
aopa.mSelector = kAudioHardwarePropertyDevices;
int device_count = property_size / sizeof(AudioDeviceID);
AudioDeviceID devices[device_count];
property_size = sizeof(devices);
err = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&aopa,
0,
NULL,
&property_size,
devices);
if (err != noErr)
return kAudioDeviceUnknown;
aopa.mSelector = kAudioDevicePropertyDeviceName;
for (int i = 0; i < device_count; i++) {
char name[256] = {0};
property_size = sizeof(name);
err = AudioObjectGetPropertyData(devices[i],
&aopa,
0,
NULL,
&property_size,
name);
if (err == noErr && strcmp(name, dev_name) == 0) {
return devices[i];
}
}
return kAudioDeviceUnknown;
}
static const struct {
channel_position_t pos;
const AudioChannelLabel label;
} coreaudio_channel_mapping[] = {
{ CHANNEL_POSITION_LEFT, kAudioChannelLabel_Left },
{ CHANNEL_POSITION_RIGHT, kAudioChannelLabel_Right },
{ CHANNEL_POSITION_CENTER, kAudioChannelLabel_Center },
{ CHANNEL_POSITION_LFE, kAudioChannelLabel_LFEScreen },
{ CHANNEL_POSITION_SIDE_LEFT, kAudioChannelLabel_LeftSurround },
{ CHANNEL_POSITION_SIDE_RIGHT, kAudioChannelLabel_RightSurround },
{ CHANNEL_POSITION_MONO, kAudioChannelLabel_Mono },
{ CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, kAudioChannelLabel_LeftCenter },
{ CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, kAudioChannelLabel_RightCenter },
{ CHANNEL_POSITION_REAR_LEFT, kAudioChannelLabel_LeftSurroundDirect },
{ CHANNEL_POSITION_REAR_RIGHT, kAudioChannelLabel_RightSurroundDirect },
{ CHANNEL_POSITION_REAR_CENTER, kAudioChannelLabel_CenterSurround },
{ CHANNEL_POSITION_INVALID, kAudioChannelLabel_Unknown },
};
static void coreaudio_set_channel_position(AudioDeviceID dev_id,
int channels,
const channel_position_t *map)
{
AudioObjectPropertyAddress aopa = {
kAudioDevicePropertyPreferredChannelLayout,
kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain
};
AudioChannelLayout *layout = NULL;
size_t layout_size = (size_t) &layout->mChannelDescriptions[channels];
layout = (AudioChannelLayout*)xmalloc(layout_size);
layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions ;
layout->mChannelBitmap = 0;
layout->mNumberChannelDescriptions = channels;
AudioChannelDescription *descriptions = layout->mChannelDescriptions;
for (int i = 0; i < channels; i++) {
const channel_position_t pos = map[i];
AudioChannelLabel label = kAudioChannelLabel_Mono;
for (int j = 0; j < N_ELEMENTS(coreaudio_channel_mapping); j++) {
if (pos == coreaudio_channel_mapping[j].pos) {
label = coreaudio_channel_mapping[j].label;
break;
}
}
descriptions[channels - 1 - i].mChannelLabel = label;
descriptions[i].mChannelFlags = kAudioChannelFlags_AllOff;
descriptions[i].mCoordinates[0] = 0;
descriptions[i].mCoordinates[1] = 0;
descriptions[i].mCoordinates[2] = 0;
}
OSStatus err =
AudioObjectSetPropertyData(dev_id,
&aopa,
0, NULL, layout_size, layout);
if (err != noErr)
d_print("Cannot set the channel layout successfully.\n");
free(layout);
}
static AudioStreamBasicDescription coreaudio_fill_format_description(sample_format_t sf)
{
AudioStreamBasicDescription desc = {
.mSampleRate = (Float64)sf_get_rate(sf),
.mFormatID = kAudioFormatLinearPCM,
.mFormatFlags = kAudioFormatFlagIsPacked,
.mBytesPerPacket = sf_get_frame_size(sf),
.mFramesPerPacket = 1,
.mChannelsPerFrame = sf_get_channels(sf),
.mBitsPerChannel = sf_get_bits(sf),
.mBytesPerFrame = sf_get_frame_size(sf),
};
d_print("Bits:%d\n", sf_get_bits(sf));
if (sf_get_bigendian(sf))
desc.mFormatFlags |= kAudioFormatFlagIsBigEndian;
if (sf_get_signed(sf))
desc.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
return desc;
}
static void coreaudio_sync_device_sample_rate(AudioDeviceID dev_id, AudioStreamBasicDescription desc)
{
AudioObjectPropertyAddress aopa = {
kAudioDevicePropertyAvailableNominalSampleRates,
kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain
};
UInt32 property_size;
OSStatus err = AudioObjectGetPropertyDataSize(dev_id,
&aopa,
0,
NULL,
&property_size);
int count = property_size/sizeof(AudioValueRange);
AudioValueRange ranges[count];
property_size = sizeof(ranges);
err = AudioObjectGetPropertyData(dev_id,
&aopa,
0,
NULL,
&property_size,
&ranges);
// Get the maximum sample rate as fallback.
Float64 sample_rate = .0;
for (int i = 0; i < count; i++) {
if (ranges[i].mMaximum > sample_rate)
sample_rate = ranges[i].mMaximum;
}
// Now try to see if the device support our format sample rate.
// For some high quality media samples, the frame rate may exceed
// device capability. In this case, we let CoreAudio downsample
// by decimation with an integer factor ranging from 1 to 4.
for (int f = 4; f > 0; f--) {
Float64 rate = desc.mSampleRate / f;
for (int i = 0; i < count; i++) {
if (ranges[i].mMinimum <= rate
&& rate <= ranges[i].mMaximum) {
sample_rate = rate;
break;
}
}
}
aopa.mSelector = kAudioDevicePropertyNominalSampleRate,
err = AudioObjectSetPropertyData(dev_id,
&aopa,
0,
NULL,
sizeof(&desc.mSampleRate),
&sample_rate);
if (err != noErr)
d_print("Failed to synchronize the sample rate: %d\n", err);
}
static void coreaudio_hog_device(AudioDeviceID dev_id, bool hog)
{
pid_t hog_pid;
AudioObjectPropertyAddress aopa = {
kAudioDevicePropertyHogMode,
kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain
};
UInt32 size = sizeof(hog_pid);
OSStatus err = AudioObjectGetPropertyData(dev_id,
&aopa,
0,
NULL,
&size,
&hog_pid);
if (err != noErr) {
d_print("Cannot get hog information: %d\n", err);
return;
}
if (hog) {
if (hog_pid != -1) {
d_print("Device is already hogged.\n");
return;
}
} else {
if (hog_pid != getpid()) {
d_print("Device is not owned by this process.\n");
return;
}
}
hog_pid = hog ? getpid() : -1;
size = sizeof(hog_pid);
err = AudioObjectSetPropertyData(dev_id,
&aopa,
0,
NULL,
size,
&hog_pid);
if (err != noErr)
d_print("Cannot hog the device: %d\n", err);
}
static OSStatus coreaudio_set_buffer_size(AudioUnit au, AudioStreamBasicDescription desc, int *frame_size)
{
AudioValueRange value_range = {0, 0};
UInt32 property_size = sizeof(AudioValueRange);
OSStatus err = AudioUnitGetProperty(au,
kAudioDevicePropertyBufferFrameSizeRange,
kAudioUnitScope_Global,
0,
&value_range,
&property_size);
if (err != noErr)
return err;
UInt32 buffer_frame_size = value_range.mMaximum;
err = AudioUnitSetProperty(au,
kAudioDevicePropertyBufferFrameSize,
kAudioUnitScope_Global,
0,
&buffer_frame_size,
sizeof(buffer_frame_size));
if (err != noErr)
d_print("Failed to set maximum buffer size: %d\n", err);
property_size = sizeof(buffer_frame_size);
err = AudioUnitGetProperty(au,
kAudioDevicePropertyBufferFrameSize,
kAudioUnitScope_Global,
0,
&buffer_frame_size,
&property_size);
if (err != noErr) {
d_print("Cannot get the buffer frame size: %d\n", err);
return err;
}
buffer_frame_size *= desc.mBytesPerFrame;
// We set the frame size to a power of two integer that
// is larger than buffer_frame_size.
while (*frame_size < buffer_frame_size + 1) {
*frame_size <<= 1;
}
return noErr;
}
static OSStatus coreaudio_init_audio_unit(AudioUnit *au,
OSType os_type,
AudioDeviceID dev_id)
{
OSStatus err;
AudioComponentDescription comp_desc = {
kAudioUnitType_Output,
os_type,
kAudioUnitManufacturer_Apple,
0,
0
};
AudioComponent comp = AudioComponentFindNext(0, &comp_desc);
if (!comp) {
return -1;
}
err = AudioComponentInstanceNew(comp, au);
if (err != noErr)
return err;
if (os_type == kAudioUnitSubType_HALOutput) {
err = AudioUnitSetProperty(*au,
kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global,
0,
&dev_id,
sizeof(dev_id));
if (err != noErr)
return err;
}
return err;
}
static OSStatus coreaudio_start_audio_unit(AudioUnit *au,
int *frame_size,
AudioStreamBasicDescription desc)
{
OSStatus err;
err = AudioUnitSetProperty(*au,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
0,
&desc,
sizeof(desc));
if (err != noErr)
return err;
AURenderCallbackStruct cb = {
.inputProc = coreaudio_play_callback,
.inputProcRefCon = NULL,
};
err = AudioUnitSetProperty(*au,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input,
0,
&cb,
sizeof(cb));
if (err != noErr)
return err;
err = AudioUnitInitialize(*au);
if (err != noErr)
return err;
err = coreaudio_set_buffer_size(*au, desc, frame_size);
if (err != noErr)
return err;
return AudioOutputUnitStart(*au);
}
static int coreaudio_init(void)
{
AudioDeviceID default_dev_id = coreaudio_get_default_device();
if (default_dev_id == kAudioDeviceUnknown) {
errno = ENODEV;
return -OP_ERROR_ERRNO;
}
AudioDeviceID named_dev_id = kAudioDeviceUnknown;
if (coreaudio_opt_device_name)
named_dev_id = coreaudio_find_device(coreaudio_opt_device_name);
coreaudio_device_id = named_dev_id != kAudioDeviceUnknown ? named_dev_id : default_dev_id;
if (named_dev_id != kAudioDeviceUnknown && coreaudio_opt_enable_hog_mode)
coreaudio_hog_device(coreaudio_device_id, true);
OSType unit_subtype = named_dev_id != kAudioDeviceUnknown ?
kAudioUnitSubType_HALOutput :
kAudioUnitSubType_DefaultOutput;
OSStatus err = coreaudio_init_audio_unit(&coreaudio_audio_unit,
unit_subtype,
coreaudio_device_id);
if (err != noErr) {
errno = ENODEV;
return -OP_ERROR_ERRNO;
}
return OP_ERROR_SUCCESS;
}
static int coreaudio_exit(void)
{
AudioComponentInstanceDispose(coreaudio_audio_unit);
coreaudio_audio_unit = NULL;
coreaudio_hog_device(coreaudio_device_id, false);
AudioHardwareUnload();
coreaudio_device_id = kAudioDeviceUnknown;
return OP_ERROR_SUCCESS;
}
static int coreaudio_open(sample_format_t sf, const channel_position_t *channel_map)
{
coreaudio_format_description = coreaudio_fill_format_description(sf);
if (coreaudio_opt_sync_rate)
coreaudio_sync_device_sample_rate(coreaudio_device_id, coreaudio_format_description);
if (channel_map)
coreaudio_set_channel_position(coreaudio_device_id,
coreaudio_format_description.mChannelsPerFrame,
channel_map);
OSStatus err = coreaudio_start_audio_unit(&coreaudio_audio_unit,
&coreaudio_buffer_frame_size,
coreaudio_format_description);
if (err)
return -OP_ERROR_SAMPLE_FORMAT;
coreaudio_ring_buffer_init(&coreaudio_ring_buffer, coreaudio_buffer_frame_size);
return OP_ERROR_SUCCESS;
}
static int coreaudio_close(void)
{
AudioOutputUnitStop(coreaudio_audio_unit);
AudioUnitUninitialize(coreaudio_audio_unit);
coreaudio_ring_buffer_destroy(&coreaudio_ring_buffer);
return OP_ERROR_SUCCESS;
}
static int coreaudio_drop(void)
{
coreaudio_ring_buffer_flush(&coreaudio_ring_buffer);
return OP_ERROR_SUCCESS;
}
static int coreaudio_write(const char *buf, int cnt)
{
return coreaudio_ring_buffer_write(&coreaudio_ring_buffer, buf, cnt);
}
static OSStatus coreaudio_get_device_stereo_channels(AudioDeviceID dev_id, UInt32 *channels) {
AudioObjectPropertyAddress aopa = {
kAudioDevicePropertyPreferredChannelsForStereo,
kAudioObjectPropertyScopeOutput,
kAudioObjectPropertyElementMain
};
UInt32 size = sizeof(UInt32[2]);
OSStatus err = AudioObjectGetPropertyData(dev_id,
&aopa,
0,
NULL,
&size,
channels);
return err;
}
static int coreaudio_mixer_set_volume(int l, int r)
{
Float32 vol[2];
OSStatus err = 0;
for (int i = 0; i < 2; i++) {
vol[i] = (i == 0 ? l : r) * 1.0f / coreaudio_max_volume;
if (vol[i] > 1.0f)
vol[i] = 1.0f;
if (vol[i] < 0.0f)
vol[i] = 0.0f;
AudioObjectPropertyAddress aopa = {
.mSelector = kAudioDevicePropertyVolumeScalar,
.mScope = kAudioObjectPropertyScopeOutput,
.mElement = coreaudio_stereo_channels[i]
};
UInt32 size = sizeof(vol[i]);
err |= AudioObjectSetPropertyData(coreaudio_device_id,
&aopa,
0,
NULL,
size,
vol + i);
}
if (err != noErr) {
errno = ENODEV;
return -OP_ERROR_ERRNO;
}
return OP_ERROR_SUCCESS;
}
static int coreaudio_mixer_get_volume(int *l, int *r)
{
clear_pipe(coreaudio_mixer_pipe_out, -1);
Float32 vol[2] = {.0, .0};
OSStatus err = 0;
for (int i = 0; i < 2; i++) {
AudioObjectPropertyAddress aopa = {
.mSelector = kAudioDevicePropertyVolumeScalar,
.mScope = kAudioObjectPropertyScopeOutput,
.mElement = coreaudio_stereo_channels[i]
};
UInt32 size = sizeof(vol[i]);
err |= AudioObjectGetPropertyData(coreaudio_device_id,
&aopa,
0,
NULL,
&size,
vol + i);
int volume = vol[i] * coreaudio_max_volume;
if (volume > coreaudio_max_volume)
volume = coreaudio_max_volume;
if (volume < 0)
volume = 0;
if (i == 0) {
*l = volume;
} else {
*r = volume;
}
}
if (err != noErr) {
errno = ENODEV;
return -OP_ERROR_ERRNO;
}
return OP_ERROR_SUCCESS;
}
static int coreaudio_mixer_open(int *volume_max)
{
*volume_max = coreaudio_max_volume;
OSStatus err = coreaudio_get_device_stereo_channels(coreaudio_device_id, coreaudio_stereo_channels);
if (err != noErr) {
d_print("Cannot get channel information: %d\n", err);
errno = ENODEV;
return -OP_ERROR_ERRNO;
}
for (int i = 0; i < 2; i++) {
AudioObjectPropertyAddress aopa = {
.mSelector = kAudioDevicePropertyVolumeScalar,
.mScope = kAudioObjectPropertyScopeOutput,
.mElement = coreaudio_stereo_channels[i]
};
err |= AudioObjectAddPropertyListener(coreaudio_device_id,
&aopa,
coreaudio_device_volume_change_listener,
NULL);
}
if (err != noErr) {
d_print("Cannot add property listener: %d\n", err);
errno = ENODEV;
return -OP_ERROR_ERRNO;
}
init_pipes(&coreaudio_mixer_pipe_out, &coreaudio_mixer_pipe_in);
return OP_ERROR_SUCCESS;
}
static int coreaudio_mixer_close(void)
{
OSStatus err = noErr;
for (int i = 0; i < 2; i++) {
AudioObjectPropertyAddress aopa = {
.mSelector = kAudioDevicePropertyVolumeScalar,
.mScope = kAudioObjectPropertyScopeOutput,
.mElement = coreaudio_stereo_channels[i]
};
err |= AudioObjectRemovePropertyListener(coreaudio_device_id,
&aopa,
coreaudio_device_volume_change_listener,
NULL);
}
if (err != noErr) {
d_print("Cannot remove property listener: %d\n", err);
errno = ENODEV;
return -OP_ERROR_ERRNO;
}
close(coreaudio_mixer_pipe_out);
close(coreaudio_mixer_pipe_in);
return OP_ERROR_SUCCESS;
}
static int coreaudio_mixer_dummy(void)
{
return OP_ERROR_SUCCESS;
}
static int coreaudio_mixer_get_fds(int what, int *fds)
{
switch (what) {
case MIXER_FDS_VOLUME:
fds[0] = coreaudio_mixer_pipe_out;
return 1;
default:
return 0;
}
}
static int coreaudio_pause(void)
{
OSStatus err = AudioOutputUnitStop(coreaudio_audio_unit);
if (err != noErr) {
errno = ENODEV;
return -OP_ERROR_ERRNO;
}
return OP_ERROR_SUCCESS;
}
static int coreaudio_unpause(void)
{
OSStatus err = AudioOutputUnitStart(coreaudio_audio_unit);
if (err != noErr) {
errno = ENODEV;
return -OP_ERROR_ERRNO;
}
return OP_ERROR_SUCCESS;
}
static int coreaudio_buffer_space(void)
{
return coreaudio_ring_buffer_write_available(&coreaudio_ring_buffer);
}
static int coreaudio_set_sync_sample_rate(const char *val)
{
coreaudio_opt_sync_rate = strcmp(val, "true") ? false : true;
if (coreaudio_opt_sync_rate)
coreaudio_sync_device_sample_rate(coreaudio_device_id, coreaudio_format_description);
return 0;
}
static int coreaudio_get_sync_sample_rate(char **val)
{
*val = xstrdup(coreaudio_opt_sync_rate ? "true" : "false");
return 0;
}
static int coreaudio_set_enable_hog_mode(const char *val)
{
coreaudio_opt_enable_hog_mode = strcmp(val, "true") ? false : true;
coreaudio_hog_device(coreaudio_device_id, coreaudio_opt_enable_hog_mode);
return 0;
}
static int coreaudio_get_enable_hog_mode(char **val)
{
*val = xstrdup(coreaudio_opt_enable_hog_mode ? "true" : "false");
return 0;
}
static int coreaudio_set_device(const char *val)
{
free(coreaudio_opt_device_name);
coreaudio_opt_device_name = NULL;
if (val[0])
coreaudio_opt_device_name = xstrdup(val);
return 0;
}
static int coreaudio_get_device(char **val)
{
if (coreaudio_opt_device_name)
*val = xstrdup(coreaudio_opt_device_name);
return 0;
}
const struct output_plugin_ops op_pcm_ops = {
.init = coreaudio_init,
.exit = coreaudio_exit,
.open = coreaudio_open,
.close = coreaudio_close,
.drop = coreaudio_drop,
.write = coreaudio_write,
.pause = coreaudio_pause,
.unpause = coreaudio_unpause,
.buffer_space = coreaudio_buffer_space,
};
const struct mixer_plugin_ops op_mixer_ops = {
.init = coreaudio_mixer_dummy,
.exit = coreaudio_mixer_dummy,
.open = coreaudio_mixer_open,
.close = coreaudio_mixer_close,
.get_fds.abi_2 = coreaudio_mixer_get_fds,
.set_volume = coreaudio_mixer_set_volume,
.get_volume = coreaudio_mixer_get_volume,
};
const struct output_plugin_opt op_pcm_options[] = {
OPT(coreaudio, device),
OPT(coreaudio, enable_hog_mode),
OPT(coreaudio, sync_sample_rate),
{ NULL },
};
const struct mixer_plugin_opt op_mixer_options[] = {
{ NULL },
};
const int op_priority = 1;
const unsigned op_abi_version = OP_ABI_VERSION;

649
op/jack.c Normal file
View File

@@ -0,0 +1,649 @@
/*
* Copyright 2014 Niko Efthymiou <nefthy-cmus@nefthy.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/* TODO
*
* - configurable maping of channels to ports
*/
#include <jack/jack.h>
#include <jack/ringbuffer.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
#if HAVE_CONFIG
#include "../config/samplerate.h"
#ifdef HAVE_SAMPLERATE
#include <samplerate.h>
#endif
#endif
#include "../op.h"
#include "../utils.h"
#include "../channelmap.h"
#include "../xmalloc.h"
#include "../debug.h"
#define CHANNELS 2
#define BUFFER_MULTIPLYER (sizeof(jack_default_audio_sample_t) * 16)
#define BUFFER_SIZE_MIN 16384
static char *server_name;
static jack_client_t *client;
static jack_port_t *output_ports[CHANNELS];
static jack_ringbuffer_t *ringbuffer[CHANNELS];
static jack_nframes_t jack_sample_rate;
#ifdef HAVE_SAMPLERATE
static SRC_STATE* src_state[CHANNELS];
static int src_quality = SRC_SINC_BEST_QUALITY;
static float resample_ratio = 1.0f;
#endif
static size_t buffer_size;
static sample_format_t sample_format;
static unsigned int sample_bytes;
static const channel_position_t *channel_map;
static volatile bool paused = true;
static volatile bool drop = false;
static volatile bool drop_done = true;
/* fail on the next call */
static int fail;
/* function pointer to appropriate read function */
static float (*read_sample) (const char *buff);
static int op_jack_init(void);
static int op_jack_exit(void);
static int op_jack_open(sample_format_t sf, const channel_position_t* cm);
static int op_jack_close(void);
static int op_jack_write(const char *buffer, int count);
static int op_jack_drop(void);
static int op_jack_buffer_space(void);
static int op_jack_pause(void);
static int op_jack_unpause(void);
/* read functions for various sample formats */
static jack_default_audio_sample_t read_sample_le16(const char *buffer)
{
int16_t s = (int16_t)read_le16(buffer);
uint16_t upper_bound = (uint16_t)INT16_MAX + (s <= 0);
return (jack_default_audio_sample_t)s / (jack_default_audio_sample_t)upper_bound;
}
static jack_default_audio_sample_t read_sample_le24(const char *buffer)
{
int32_t s = read_le24i(buffer);
uint32_t upper_bound = 0x7FFFFF + (s < 0);
return (jack_default_audio_sample_t) s / (jack_default_audio_sample_t)upper_bound;
}
static jack_default_audio_sample_t read_sample_le32(const char *buffer)
{
int32_t s = (int32_t)read_le32(buffer);
uint32_t upper_bound = (uint32_t)INT32_MAX + (s <= 0);
return (jack_default_audio_sample_t)s / (jack_default_audio_sample_t)upper_bound;
}
static jack_default_audio_sample_t read_sample_le16u(const char *buffer)
{
uint32_t u = read_le16(buffer);
return (((jack_default_audio_sample_t) u)
/ ((jack_default_audio_sample_t) UINT16_MAX)) * 2.0 - 2.0;
}
static jack_default_audio_sample_t read_sample_le24u(const char *buffer)
{
uint32_t u = read_le24(buffer);
return (((jack_default_audio_sample_t) u)
/ ((jack_default_audio_sample_t) 0xFFFFFFU)) * 2.0 - 2.0;
}
static jack_default_audio_sample_t read_sample_le32u(const char *buffer)
{
uint32_t u = read_le32(buffer);
return (((jack_default_audio_sample_t) u)
/ ((jack_default_audio_sample_t) UINT32_MAX)) * 2.0 - 2.0;
}
#ifdef HAVE_SAMPLERATE
static void op_jack_reset_src(void) {
for (int c = 0; c < CHANNELS; c++) {
src_reset(src_state[c]);
}
}
#endif
/* jack callbacks */
static void op_jack_error_cb(const char *msg) {
d_print("jackd error: %s\n", msg);
fail = 1;
}
static int op_jack_cb(jack_nframes_t frames, void *arg)
{
size_t bytes_want = frames * sizeof(jack_default_audio_sample_t);
if (drop) {
for (int i = 0; i < CHANNELS; i++) {
jack_ringbuffer_reset(ringbuffer[i]);
}
drop = false;
drop_done = true;
}
size_t bytes_min = SIZE_MAX;
for (int i = 0; i < CHANNELS; i++) {
size_t bytes_available = jack_ringbuffer_read_space(ringbuffer[i]);
if (bytes_available < bytes_min) {
bytes_min = bytes_available;
}
}
/* if there is less than frames available play silence */
if (paused || bytes_min < bytes_want) {
for (int i = 0; i < CHANNELS; i++) {
jack_default_audio_sample_t* jack_buf = jack_port_get_buffer(output_ports[i], frames);
memset(jack_buf, 0, bytes_want);
}
return 0;
}
for (int i = 0; i < CHANNELS; i++) {
jack_default_audio_sample_t* jack_buf = jack_port_get_buffer(output_ports[i], frames);
size_t bytes_read = jack_ringbuffer_read(ringbuffer[i], (char*) jack_buf, bytes_want);
if (bytes_read < bytes_want) {
/* This should not happen[TM] - just in case set fail = 1 */
d_print("underrun! wanted %zu only got %zu bytes\n", bytes_want, bytes_read);
fail = 1;
}
}
return 0;
}
/* init or resize buffers if needed */
static int op_jack_buffer_init(jack_nframes_t samples, void *arg)
{
if (buffer_size > samples * BUFFER_MULTIPLYER) {
/* we just don't shrink buffers, since this could result
* in gaps and they won't get that big anyway
*/
return 0;
}
buffer_size = samples * BUFFER_MULTIPLYER;
if (buffer_size < BUFFER_SIZE_MIN) {
buffer_size = BUFFER_SIZE_MIN;
}
d_print("new buffer size %zu\n", buffer_size);
char *tmp = xmalloc(buffer_size);
for (int i = 0; i < CHANNELS; i++) {
jack_ringbuffer_t *new_buffer = jack_ringbuffer_create(buffer_size);
if (!new_buffer) {
d_print("ringbuffer alloc failed\n");
free(tmp);
fail = 1;
op_jack_exit();
return 1;
}
if (ringbuffer[i] != NULL) {
size_t length = jack_ringbuffer_read_space(ringbuffer[i]);
/* actually this could both read/write less than length.
* In that case, which should not happen[TM], there will
* be a gap in playback.
*/
jack_ringbuffer_read(ringbuffer[i], tmp, length);
jack_ringbuffer_write(new_buffer, tmp, length);
jack_ringbuffer_free(ringbuffer[i]);
}
ringbuffer[i] = new_buffer;
}
free(tmp);
return 0;
}
static int op_jack_sample_rate_cb(jack_nframes_t samples, void *arg)
{
#ifdef HAVE_SAMPLERATE
resample_ratio = (float) sf_get_rate(sample_format) / (float) samples;
#else
if (jack_sample_rate != samples) {
/* this cannot be handled */
fail = 1;
return 1;
}
#endif
return 0;
}
static void op_jack_shutdown_cb(void *arg)
{
d_print("jackd went away");
/* calling op_jack_exit() will cause a segfault if op_jack_write or
* anything else is in the middle of something...
* the fail flag is checked by op_jack_write and op_jack_buffer_space
*
* op_jack_open will try to reinitialize jack
*/
fail = 1;
}
/* cmus callbacks */
static int op_jack_init(void)
{
#ifdef HAVE_SAMPLERATE
for (int i = 0; i < CHANNELS; i++) {
src_state[i] = src_new(src_quality, 1, NULL);
if (src_state[i] == NULL) {
d_print("src_new failed");
for (i = i - 1; i >= 0; i--) {
src_delete(src_state[i]);
}
return -OP_ERROR_INTERNAL;
}
}
#endif
jack_set_error_function(op_jack_error_cb);
jack_options_t options = JackNullOption;
if (fail) {
/* since jackd failed, it will not be autostarted. Either jackd
* was killed intentionally or it died by heartattack.
* Until it is restarted, init will happily fail again
* and again and again..
*/
options |= JackNoStartServer;
}
jack_status_t status;
client = jack_client_open("cmus", options, &status, server_name);
if (client == NULL) {
d_print("jack_client_new failed\n");
return -OP_ERROR_INTERNAL;
}
if (status & JackServerStarted) {
d_print("jackd started\n");
}
jack_nframes_t jack_buffer_size = jack_get_buffer_size(client);
jack_sample_rate = jack_get_sample_rate(client);
op_jack_buffer_init(jack_buffer_size, NULL);
jack_set_process_callback(client, op_jack_cb, NULL);
jack_set_sample_rate_callback(client, op_jack_sample_rate_cb, NULL);
jack_set_buffer_size_callback(client, op_jack_buffer_init, NULL);
jack_on_shutdown(client, op_jack_shutdown_cb, NULL);
for (int i = 0; i < CHANNELS; i++) {
char port_name[20];
snprintf(port_name, sizeof(port_name)-1, "output %d", i);
output_ports[i] = jack_port_register(
client,
port_name,
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput,
0
);
if (output_ports[i] == NULL) {
d_print("no jack ports available\n");
return -OP_ERROR_INTERNAL;
}
}
if (jack_activate(client)) {
d_print("jack_client_activate failed\n");
return -OP_ERROR_INTERNAL;
}
const char **ports = jack_get_ports(client, NULL, NULL, JackPortIsPhysical | JackPortIsInput);
if (ports == NULL) {
d_print("cannot get playback ports\n");
return -OP_ERROR_INTERNAL;
}
for (int i = 0; i < CHANNELS; i++) {
if (ports[i] == NULL) {
d_print("could not connect output %d. too few ports.\n", i);
break;
}
if (jack_connect(client, jack_port_name(output_ports[i]), ports[i])) {
d_print("cannot connect port %s\n", ports[i]);
jack_free(ports);
return -OP_ERROR_INTERNAL;
}
}
jack_free(ports);
fail = 0;
return OP_ERROR_SUCCESS;
}
static int op_jack_exit(void)
{
if (client != NULL) {
jack_deactivate(client);
for (int i = 0; i < CHANNELS; i++) {
if (output_ports[i] != NULL) {
jack_port_unregister(client, output_ports[i]);
}
}
jack_client_close(client);
}
for (int i = 0; i < CHANNELS; i++) {
if (ringbuffer[i] != NULL) {
jack_ringbuffer_free(ringbuffer[i]);
}
ringbuffer[i] = NULL;
}
buffer_size = 0;
client = NULL;
return OP_ERROR_SUCCESS;
}
static int op_jack_open(sample_format_t sf, const channel_position_t *cm)
{
sample_format = sf;
if (fail) {
/* jack went away so lets see if we can recover */
if (client != NULL) {
op_jack_exit();
}
if (op_jack_init() != OP_ERROR_SUCCESS) {
return -OP_ERROR_INTERNAL;
}
}
if (cm == NULL) {
d_print("no channel_map\n");
return -OP_ERROR_NOT_SUPPORTED;
}
channel_map = cm;
#ifdef HAVE_SAMPLERATE
op_jack_reset_src();
resample_ratio = (float) jack_sample_rate / (float) sf_get_rate(sf);
#else
if (jack_sample_rate != sf_get_rate(sf)) {
d_print("jack sample rate of %d does not match %d\n", jack_get_sample_rate(client), sf_get_rate(sf));
return -OP_ERROR_SAMPLE_FORMAT;
}
#endif
if (sf_get_channels(sf) < CHANNELS) {
d_print("%d channels not supported\n", sf_get_channels(sf));
return -OP_ERROR_SAMPLE_FORMAT;
}
int bits = sf_get_bits(sf);
if (bits == 16) {
sample_bytes = 2;
read_sample = sf_get_signed(sf) ? &read_sample_le16 : &read_sample_le16u;
} else if (bits == 24) {
sample_bytes = 3;
read_sample = sf_get_signed(sf) ? &read_sample_le24 : &read_sample_le24u;
} else if (bits == 32) {
sample_bytes = 4;
read_sample = sf_get_signed(sf) ? &read_sample_le32 : &read_sample_le32u;
} else {
d_print("%d bits not supported\n", sf_get_bits(sf));
return -OP_ERROR_SAMPLE_FORMAT;
}
paused = false;
return OP_ERROR_SUCCESS;
}
static int op_jack_close(void)
{
paused = true;
return OP_ERROR_SUCCESS;
}
static int op_jack_drop(void)
{
if (!drop_done) {
return OP_ERROR_SUCCESS;
}
drop_done = false;
drop = true;
while (!drop_done) {
/* wait till op_jack_cb is done with dropping */
usleep(1000);
}
return OP_ERROR_SUCCESS;
}
static int op_jack_write(const char *buffer, int count)
{
if (fail) {
op_jack_exit();
return -OP_ERROR_INTERNAL;
}
if (!drop_done) {
return 0;
}
int frame_size = sf_get_frame_size(sample_format);
int channels = sf_get_channels(sample_format);
size_t frames = count / frame_size;
/* since this is the only place where the ringbuffers get
* written, available space will only grow, therefore frames_min
* is safe.
*/
size_t frames_min = SIZE_MAX;
for (int c = 0; c < CHANNELS; c++) {
size_t frames_available = jack_ringbuffer_write_space(ringbuffer[c]) / sizeof(jack_default_audio_sample_t);
if (frames_available < frames_min) {
frames_min = frames_available;
}
}
if (frames > frames_min) {
frames = frames_min;
}
jack_default_audio_sample_t buf[CHANNELS][buffer_size];
/* demux and convert to float */
for (int pos = 0; pos < count; ) {
int frame = pos / frame_size;
for (int c = 0; c < channels; c++) {
int idx = pos + c * sample_bytes;
/* for now, only 2 channels and mono are supported */
if (channel_map[c] == CHANNEL_POSITION_LEFT || channel_map[c] == CHANNEL_POSITION_MONO) {
buf[0][frame] = read_sample(&buffer[idx]);
} else if (channel_map[c] == CHANNEL_POSITION_RIGHT || channel_map[c] == CHANNEL_POSITION_MONO) {
buf[1][frame] = read_sample(&buffer[idx]);
}
}
pos += frame_size;
}
#ifdef HAVE_SAMPLERATE
if (resample_ratio > 1.01f || resample_ratio < 0.99) {
jack_default_audio_sample_t converted[buffer_size];
SRC_DATA src_data;
for (int c = 0; c < CHANNELS; c++) {
src_data.data_in = buf[c];
src_data.data_out = converted;
src_data.input_frames = frames;
src_data.output_frames = frames_min;
src_data.src_ratio = resample_ratio;
src_data.end_of_input = 0;
int err = src_process(src_state[c], &src_data);
if (err) {
d_print("libsamplerate err %s\n", src_strerror(err));
}
int byte_length = src_data.output_frames_gen * sizeof(jack_default_audio_sample_t);
jack_ringbuffer_write(ringbuffer[c], (const char*) converted, byte_length);
}
return src_data.input_frames_used * frame_size;
} else {
#endif
int byte_length = frames * sizeof(jack_default_audio_sample_t);
for (int c = 0; c < CHANNELS; c++) {
jack_ringbuffer_write(ringbuffer[c], (const char*) buf[c], byte_length);
}
return frames * frame_size;
#ifdef HAVE_SAMPLERATE
}
#endif
}
static int op_jack_buffer_space(void)
{
if (fail) {
op_jack_exit();
return -OP_ERROR_INTERNAL;
}
int bytes = jack_ringbuffer_write_space(ringbuffer[0]);
for (int c = 1; c < CHANNELS; c++) {
int tmp = jack_ringbuffer_write_space(ringbuffer[0]);
if (bytes > tmp) {
bytes = tmp;
}
}
int frames = bytes / sizeof(jack_default_audio_sample_t);
int frame_size = sf_get_frame_size(sample_format);
#ifdef HAVE_SAMPLERATE
return (int) ((float) (frames) / resample_ratio) * frame_size;
#else
return frames * frame_size;
#endif
}
static int op_jack_pause(void)
{
paused = true;
return OP_ERROR_SUCCESS;
}
static int op_jack_unpause(void)
{
#ifdef HAVE_SAMPLERATE
op_jack_reset_src();
#endif
paused = false;
return OP_ERROR_SUCCESS;
}
static int op_jack_set_server_name(const char *val)
{
free(server_name);
server_name = val[0] != '\0' ? xstrdup(val) : NULL;
return OP_ERROR_SUCCESS;
}
static int op_jack_get_server_name(char **val)
{
*val = xstrdup(server_name != NULL ? server_name : "");
return OP_ERROR_SUCCESS;
}
#ifdef HAVE_SAMPLERATE
static int op_jack_set_resampling_quality(const char *val)
{
if (strlen(val) != 1) {
return -OP_ERROR_NOT_SUPPORTED;
}
switch (val[0]) {
default:
case '2':
src_quality = SRC_SINC_BEST_QUALITY;
break;
case '1':
src_quality = SRC_SINC_MEDIUM_QUALITY;
break;
case '0':
src_quality = SRC_SINC_FASTEST;
break;
}
return OP_ERROR_SUCCESS;
}
static int op_jack_get_resampling_quality(char **val)
{
switch (src_quality) {
case SRC_SINC_BEST_QUALITY:
*val = xstrdup("2");
break;
case SRC_SINC_MEDIUM_QUALITY:
*val = xstrdup("1");
break;
case SRC_SINC_FASTEST:
*val = xstrdup("0");
break;
}
return OP_ERROR_SUCCESS;
}
#endif
const struct output_plugin_ops op_pcm_ops = {
.init = op_jack_init,
.exit = op_jack_exit,
.open = op_jack_open,
.close = op_jack_close,
.drop = op_jack_drop,
.write = op_jack_write,
.buffer_space = op_jack_buffer_space,
.pause = op_jack_pause,
.unpause = op_jack_unpause,
};
const struct output_plugin_opt op_pcm_options[] = {
OPT(op_jack, server_name),
#ifdef HAVE_SAMPLERATE
OPT(op_jack, resampling_quality),
#endif
{ NULL },
};
const int op_priority = 2;
const unsigned op_abi_version = OP_ABI_VERSION;

292
op/mixer_alsa.c Normal file
View File

@@ -0,0 +1,292 @@
/*
* Copyright 2008-2013 Various Authors
* Copyright 2004-2005 Timo Hirvonen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "../mixer.h"
#include "../op.h"
#include "../xmalloc.h"
#include "../debug.h"
#include <strings.h>
#include <math.h>
#define ALSA_PCM_NEW_HW_PARAMS_API
#define ALSA_PCM_NEW_SW_PARAMS_API
#include <alsa/asoundlib.h>
static snd_mixer_t *mixer;
static snd_mixer_elem_t *mixer_elem = NULL;
/* configuration */
static char *alsa_mixer_device = NULL;
static char *alsa_mixer_element = NULL;
static int alsa_mixer_mapped_volume = 1;
static int alsa_mixer_init(void)
{
if (alsa_mixer_device == NULL)
alsa_mixer_device = xstrdup("default");
if (alsa_mixer_element == NULL)
alsa_mixer_element = xstrdup("PCM");
return 0;
}
static int alsa_mixer_exit(void)
{
free(alsa_mixer_device);
alsa_mixer_device = NULL;
free(alsa_mixer_element);
alsa_mixer_element = NULL;
return 0;
}
static snd_mixer_elem_t *find_mixer_elem_by_name(const char *name)
{
snd_mixer_elem_t *elem;
snd_mixer_selem_id_t *sid = NULL;
snd_mixer_selem_id_alloca(&sid);
if (snd_mixer_selem_id_parse(sid, name))
return NULL;
elem = snd_mixer_find_selem(mixer, sid);
if (!elem) {
d_print("unable to find simple control '%s',%i\n",
snd_mixer_selem_id_get_name(sid),
snd_mixer_selem_id_get_index(sid));
return NULL;
}
if (!snd_mixer_selem_has_playback_volume(elem)) {
d_print("simple control '%s',%i does not have playback volume\n",
snd_mixer_selem_id_get_name(sid),
snd_mixer_selem_id_get_index(sid));
return NULL;
}
return elem;
}
static int alsa_mixer_open(int *volume_max)
{
snd_mixer_elem_t *elem;
int rc;
rc = snd_mixer_open(&mixer, 0);
if (rc < 0)
goto error;
rc = snd_mixer_attach(mixer, alsa_mixer_device);
if (rc < 0)
goto error;
rc = snd_mixer_selem_register(mixer, NULL, NULL);
if (rc < 0)
goto error;
rc = snd_mixer_load(mixer);
if (rc < 0)
goto error;
elem = find_mixer_elem_by_name(alsa_mixer_element);
if (!elem) {
d_print("mixer element '%s' not found, trying 'Master'\n",
alsa_mixer_element);
elem = find_mixer_elem_by_name("Master");
if (!elem) {
d_print("error: cannot find suitable mixer element\n");
return -2;
}
}
mixer_elem = elem;
*volume_max = 100;
return 0;
error:
d_print("error: %s\n", snd_strerror(rc));
return -1;
}
static int alsa_mixer_close(void)
{
snd_mixer_close(mixer);
return 0;
}
static int alsa_mixer_get_fds(int what, int *fds)
{
struct pollfd pfd[NR_MIXER_FDS];
int count, i;
switch (what) {
case MIXER_FDS_VOLUME:
count = snd_mixer_poll_descriptors(mixer, pfd, NR_MIXER_FDS);
for (i = 0; i < count; i++)
fds[i] = pfd[i].fd;
return count;
default:
return 0;
}
}
static void alsa_mixer_get_range(long *min, long *max, int *linear_mapping)
{
if (!*linear_mapping) {
int err = snd_mixer_selem_get_playback_dB_range(
mixer_elem, min, max);
*linear_mapping = err != 0 || *min >= *max;
/* Force linear mapping for small ranges of 24 dB */
enum { max_linear_db_scale = 24 };
*linear_mapping |= *max - *min <= max_linear_db_scale * 100;
}
if (*linear_mapping)
snd_mixer_selem_get_playback_volume_range(mixer_elem, min, max);
}
static void alsa_mixer_set_vol(snd_mixer_selem_channel_id_t channel,
long min, long max, int vol, int linear_mapping)
{
if (linear_mapping) {
long v = (long)(vol * (max - min) / 100.0 + 0.5) + min;
snd_mixer_selem_set_playback_volume(mixer_elem, channel, v);
} else {
/* alsamixer's volume mapping */
double norm = vol / 100.0;
if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
double min_norm = pow(10, (min - max) / 6000.0);
norm = norm * (1 - min_norm) + min_norm;
}
if (norm <= 0)
norm = 1e-36;
long v = (long)(6000.0 * log10(norm) + 0.5) + max;
snd_mixer_selem_set_playback_dB(mixer_elem, channel, v, 0);
}
}
static int alsa_mixer_set_volume(int l, int r)
{
int linear_mapping = !alsa_mixer_mapped_volume;
long min, max;
if (!mixer_elem)
return -1;
snd_mixer_handle_events(mixer);
alsa_mixer_get_range(&min, &max, &linear_mapping);
alsa_mixer_set_vol(SND_MIXER_SCHN_FRONT_LEFT,
min, max, l, linear_mapping);
alsa_mixer_set_vol(SND_MIXER_SCHN_FRONT_RIGHT,
min, max, r, linear_mapping);
return 0;
}
static int alsa_mixer_get_vol(snd_mixer_selem_channel_id_t channel,
long min, long max, int linear_mapping)
{
long val;
if (linear_mapping) {
snd_mixer_selem_get_playback_volume(mixer_elem, channel, &val);
return ((val - min) / (double)(max - min) * 100) + 0.5;
}
/* alsamixer's volume mapping */
snd_mixer_selem_get_playback_dB(mixer_elem, channel, &val);
double normalized = pow(10, (val - max) / 6000.0);
if (min != SND_CTL_TLV_DB_GAIN_MUTE) {
double min_norm = pow(10, (min - max) / 6000.0);
normalized = (normalized - min_norm) / (1 - min_norm);
}
return normalized * 100.0 + 0.5;
}
static int alsa_mixer_get_volume(int *l, int *r)
{
int linear_mapping = !alsa_mixer_mapped_volume;
long min, max;
if (!mixer_elem)
return -1;
snd_mixer_handle_events(mixer);
alsa_mixer_get_range(&min, &max, &linear_mapping);
*l = alsa_mixer_get_vol(SND_MIXER_SCHN_FRONT_LEFT,
min, max, linear_mapping);
*r = alsa_mixer_get_vol(SND_MIXER_SCHN_FRONT_RIGHT,
min, max, linear_mapping);
return 0;
}
static int alsa_mixer_set_channel(const char *val)
{
free(alsa_mixer_element);
alsa_mixer_element = xstrdup(val);
return 0;
}
static int alsa_mixer_get_channel(char **val)
{
if (alsa_mixer_element)
*val = xstrdup(alsa_mixer_element);
return 0;
}
static int alsa_mixer_set_device(const char *val)
{
free(alsa_mixer_device);
alsa_mixer_device = xstrdup(val);
return 0;
}
static int alsa_mixer_get_device(char **val)
{
if (alsa_mixer_device)
*val = xstrdup(alsa_mixer_device);
return 0;
}
static int alsa_mixer_set_mapped_volume(const char *val)
{
alsa_mixer_mapped_volume = atoi(val);
return 0;
}
static int alsa_mixer_get_mapped_volume(char **val)
{
*val = alsa_mixer_mapped_volume ? xstrdup("1") : xstrdup("0");
return 0;
}
const struct mixer_plugin_ops op_mixer_ops = {
.init = alsa_mixer_init,
.exit = alsa_mixer_exit,
.open = alsa_mixer_open,
.close = alsa_mixer_close,
.get_fds.abi_2 = alsa_mixer_get_fds,
.set_volume = alsa_mixer_set_volume,
.get_volume = alsa_mixer_get_volume,
};
const struct mixer_plugin_opt op_mixer_options[] = {
OPT(alsa_mixer, channel),
OPT(alsa_mixer, device),
OPT(alsa_mixer, mapped_volume),
{ NULL },
};

244
op/mixer_oss.c Normal file
View File

@@ -0,0 +1,244 @@
/*
* Copyright 2008-2013 Various Authors
* Copyright 2004-2005 Timo Hirvonen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "../mixer.h"
#include "../op.h"
#include "../utils.h"
#include "../xmalloc.h"
#include "../debug.h"
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#if defined(__OpenBSD__)
#include <soundcard.h>
#else
#include <sys/soundcard.h>
#endif
enum {
OSS_MIXER_CHANNEL_VOLUME,
OSS_MIXER_CHANNEL_BASS,
OSS_MIXER_CHANNEL_TREBLE,
OSS_MIXER_CHANNEL_SYNTH,
OSS_MIXER_CHANNEL_PCM,
OSS_MIXER_CHANNEL_SPEAKER,
OSS_MIXER_CHANNEL_LINE,
OSS_MIXER_CHANNEL_MIC,
OSS_MIXER_CHANNEL_CD,
OSS_MIXER_CHANNEL_IMIX,
OSS_MIXER_CHANNEL_ALTPCM,
OSS_MIXER_CHANNEL_RECLEV,
OSS_MIXER_CHANNEL_IGAIN,
OSS_MIXER_CHANNEL_OGAIN,
OSS_MIXER_CHANNEL_LINE1,
OSS_MIXER_CHANNEL_LINE2,
OSS_MIXER_CHANNEL_LINE3,
OSS_MIXER_CHANNEL_DIGITAL1,
OSS_MIXER_CHANNEL_DIGITAL2,
OSS_MIXER_CHANNEL_DIGITAL3,
OSS_MIXER_CHANNEL_PHONEIN,
OSS_MIXER_CHANNEL_PHONEOUT,
OSS_MIXER_CHANNEL_VIDEO,
OSS_MIXER_CHANNEL_RADIO,
OSS_MIXER_CHANNEL_MONITOR,
OSS_MIXER_CHANNEL_MAX
};
static int mixer_fd = -1;
static int mixer_devmask;
/* static int mixer_recmask; */
/* static int mixer_recsrc; */
/* static int mixer_stereodevs; */
static char mixer_channels[OSS_MIXER_CHANNEL_MAX];
/* configuration */
static char *oss_mixer_device = NULL;
static int oss_volume_controls_pcm = 1;
static int mixer_open(const char *device)
{
int i;
mixer_fd = open(device, O_RDWR);
if (mixer_fd == -1)
return -1;
ioctl(mixer_fd, SOUND_MIXER_READ_DEVMASK, &mixer_devmask);
/* ioctl(mixer_fd, SOUND_MIXER_READ_RECMASK, &mixer_recmask); */
/* ioctl(mixer_fd, SOUND_MIXER_READ_RECSRC, &mixer_recsrc); */
/* ioctl(mixer_fd, SOUND_MIXER_READ_STEREODEVS, &mixer_stereodevs); */
i = 0;
while (i < min_i(SOUND_MIXER_NRDEVICES, OSS_MIXER_CHANNEL_MAX)) {
mixer_channels[i] = (mixer_devmask >> i) & 1;
i++;
}
while (i < OSS_MIXER_CHANNEL_MAX)
mixer_channels[i++] = 0;
return 0;
}
static int mixer_set_level(int channel, int l, int r)
{
int tmp;
tmp = (l & 0x7f) + ((r & 0x7f) << 8);
if (ioctl(mixer_fd, MIXER_WRITE(channel), &tmp) == -1)
return -1;
return 0;
}
static int mixer_get_level(int channel, int *l, int *r)
{
int tmp;
if (ioctl(mixer_fd, MIXER_READ(channel), &tmp) == -1)
return -1;
*l = tmp & 0x7f;
*r = (tmp >> 8) & 0x7f;
return 0;
}
static int oss_device_exists(const char *device)
{
struct stat s;
if (stat(device, &s) == 0) {
d_print("device %s exists\n", device);
return 1;
}
d_print("device %s does not exist\n", device);
return 0;
}
static int oss_mixer_init(void)
{
const char *new_mixer_dev = "/dev/sound/mixer";
const char *mixer_dev = "/dev/mixer";
if (oss_mixer_device) {
if (oss_device_exists(oss_mixer_device))
return 0;
free(oss_mixer_device);
oss_mixer_device = NULL;
return -1;
}
if (oss_device_exists(new_mixer_dev)) {
oss_mixer_device = xstrdup(new_mixer_dev);
return 0;
}
if (oss_device_exists(mixer_dev)) {
oss_mixer_device = xstrdup(mixer_dev);
return 0;
}
return -1;
}
static int oss_mixer_exit(void)
{
if (oss_mixer_device) {
free(oss_mixer_device);
oss_mixer_device = NULL;
}
return 0;
}
static int oss_mixer_open(int *volume_max)
{
*volume_max = 100;
if (mixer_open(oss_mixer_device) == 0)
return 0;
return -1;
}
static int oss_mixer_close(void)
{
close(mixer_fd);
mixer_fd = -1;
return 0;
}
static int oss_mixer_set_volume(int l, int r)
{
if (oss_volume_controls_pcm) {
return mixer_set_level(OSS_MIXER_CHANNEL_PCM, l, r);
} else {
return mixer_set_level(OSS_MIXER_CHANNEL_VOLUME, l, r);
}
}
static int oss_mixer_get_volume(int *l, int *r)
{
if (oss_volume_controls_pcm) {
return mixer_get_level(OSS_MIXER_CHANNEL_PCM, l, r);
} else {
return mixer_get_level(OSS_MIXER_CHANNEL_VOLUME, l, r);
}
}
static int oss_mixer_set_channel(const char *val)
{
if (strcasecmp(val, "pcm") == 0) {
oss_volume_controls_pcm = 1;
} else if (strcasecmp(val, "master") == 0) {
oss_volume_controls_pcm = 0;
} else {
errno = EINVAL;
return -OP_ERROR_ERRNO;
}
return 0;
}
static int oss_mixer_get_channel(char **val)
{
if (oss_volume_controls_pcm) {
*val = xstrdup("PCM");
} else {
*val = xstrdup("Master");
}
return 0;
}
static int oss_mixer_set_device(const char *val)
{
free(oss_mixer_device);
oss_mixer_device = xstrdup(val);
return 0;
}
static int oss_mixer_get_device(char **val)
{
if (oss_mixer_device)
*val = xstrdup(oss_mixer_device);
return 0;
}
const struct mixer_plugin_ops op_mixer_ops = {
.init = oss_mixer_init,
.exit = oss_mixer_exit,
.open = oss_mixer_open,
.close = oss_mixer_close,
.set_volume = oss_mixer_set_volume,
.get_volume = oss_mixer_get_volume,
};
const struct mixer_plugin_opt op_mixer_options[] = {
OPT(oss_mixer, channel),
OPT(oss_mixer, device),
{ NULL },
};

283
op/mixer_sun.c Normal file
View File

@@ -0,0 +1,283 @@
/*
* Copyright 2008-2013 Various Authors
* Copyright 2004-2005 Timo Hirvonen
*
* mixer_sun.c by alex <pukpuk@gmx.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/audioio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include "../debug.h"
#include "../mixer.h"
#include "../op.h"
#include "../sf.h"
#include "../xmalloc.h"
static int sun_mixer_device_id = -1;
static int sun_mixer_channels = -1;
static int sun_mixer_volume_delta = -1;
static int mixer_fd = -1;
static char *sun_mixer_device = NULL;
static char *sun_mixer_channel = NULL;
static int mixer_open(const char *);
static int min_delta(int, int, int);
static int sun_device_exists(const char *);
static int sun_mixer_init(void);
static int sun_mixer_exit(void);
static int sun_mixer_open(int *);
static int sun_mixer_close(void);
static int sun_mixer_set_volume(int, int);
static int sun_mixer_get_volume(int *, int *);
static int mixer_open(const char *dev)
{
struct mixer_devinfo minf;
int output_class;
mixer_fd = open(dev, O_RDWR);
if (mixer_fd == -1)
return -1;
output_class = -1;
sun_mixer_device_id = -1;
/* determine output class */
minf.index = 0;
while (ioctl(mixer_fd, AUDIO_MIXER_DEVINFO, &minf) != -1) {
if (minf.type == AUDIO_MIXER_CLASS) {
if (strcmp(minf.label.name, AudioCoutputs) == 0)
output_class = minf.index;
}
++minf.index;
}
/* no output class found?? something must be wrong */
if (output_class == -1)
return -1;
minf.index = 0;
/* query all mixer devices and try choose the correct one */
while (ioctl(mixer_fd, AUDIO_MIXER_DEVINFO, &minf) != -1) {
/* only scan output channels */
if (minf.type == AUDIO_MIXER_VALUE && minf.prev == AUDIO_MIXER_LAST &&
minf.mixer_class == output_class) {
if (strcasecmp(minf.label.name, sun_mixer_channel) == 0) {
sun_mixer_volume_delta = minf.un.v.delta;
sun_mixer_device_id = minf.index;
sun_mixer_channels = minf.un.v.num_channels;
}
}
++minf.index;
}
if (sun_mixer_device_id == -1)
return -1;
d_print("sun: found mixer-channel: %s, devid: %d, delta: %d, channels: %d\n", sun_mixer_channel,
sun_mixer_device_id, sun_mixer_volume_delta, sun_mixer_channels);
if (sun_mixer_volume_delta == 0)
sun_mixer_volume_delta = 1;
return 0;
}
static int min_delta(int oval, int nval, int delta)
{
if (oval > nval && oval - nval < delta)
nval -= delta;
else if (oval < nval && nval - oval < delta)
nval += delta;
nval = (nval < 0) ? 0 : nval;
nval = (nval > AUDIO_MAX_GAIN) ? AUDIO_MAX_GAIN : nval;
return nval;
}
static int sun_device_exists(const char *dev)
{
struct stat s;
if (stat(dev, &s) == 0) {
d_print("device %s exists\n", dev);
return 1;
}
d_print("device %s does not exist\n", dev);
return 0;
}
static int sun_mixer_init(void)
{
const char *mixer_dev = "/dev/mixer";
if (sun_mixer_device != NULL) {
if (sun_device_exists(sun_mixer_device))
return 0;
free(sun_mixer_device);
sun_mixer_device = NULL;
return -1;
}
if (sun_device_exists(mixer_dev)) {
sun_mixer_device = xstrdup(mixer_dev);
return 0;
}
return -1;
}
static int sun_mixer_exit(void)
{
if (sun_mixer_device != NULL) {
free(sun_mixer_device);
sun_mixer_device = NULL;
}
if (sun_mixer_channel != NULL) {
free(sun_mixer_channel);
sun_mixer_channel = NULL;
}
return 0;
}
static int sun_mixer_open(int *vol_max)
{
const char *mixer_channel = "master";
/* set default mixer channel */
if (sun_mixer_channel == NULL)
sun_mixer_channel = xstrdup(mixer_channel);
if (mixer_open(sun_mixer_device) == 0) {
*vol_max = AUDIO_MAX_GAIN;
return 0;
}
return -1;
}
static int sun_mixer_close(void)
{
if (mixer_fd != -1) {
close(mixer_fd);
mixer_fd = -1;
}
return 0;
}
static int sun_mixer_set_volume(int l, int r)
{
struct mixer_ctrl minf;
int ovall, ovalr;
if (sun_mixer_get_volume(&ovall, &ovalr) == -1)
return -1;
/* OpenBSD mixer values are `discrete' */
l = min_delta(ovall, l, sun_mixer_volume_delta);
r = min_delta(ovalr, r, sun_mixer_volume_delta);
minf.type = AUDIO_MIXER_VALUE;
minf.dev = sun_mixer_device_id;
if (sun_mixer_channels == 1)
minf.un.value.level[AUDIO_MIXER_LEVEL_MONO] = l;
else {
minf.un.value.level[AUDIO_MIXER_LEVEL_LEFT] = l;
minf.un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = r;
}
minf.un.value.num_channels = sun_mixer_channels;
if (ioctl(mixer_fd, AUDIO_MIXER_WRITE, &minf) == -1)
return -1;
return 0;
}
static int sun_mixer_get_volume(int *l, int *r)
{
struct mixer_ctrl minf;
minf.dev = sun_mixer_device_id;
minf.type = AUDIO_MIXER_VALUE;
minf.un.value.num_channels = sun_mixer_channels;
if (ioctl(mixer_fd, AUDIO_MIXER_READ, &minf) == -1)
return -1;
if (sun_mixer_channels == 1) {
*l = minf.un.value.level[AUDIO_MIXER_LEVEL_MONO];
*r = *l;
} else {
*l = minf.un.value.level[AUDIO_MIXER_LEVEL_LEFT];
*r = minf.un.value.level[AUDIO_MIXER_LEVEL_RIGHT];
}
return 0;
}
static int sun_mixer_set_channel(const char *val)
{
if (sun_mixer_channel != NULL)
free(sun_mixer_channel);
sun_mixer_channel = xstrdup(val);
return 0;
}
static int sun_mixer_get_channel(char **val)
{
if (sun_mixer_channel)
*val = xstrdup(sun_mixer_channel);
return 0;
}
static int sun_mixer_set_device(const char *val)
{
free(sun_mixer_device);
sun_mixer_device = xstrdup(val);
return 0;
}
static int sun_mixer_get_device(char **val)
{
if (sun_mixer_device)
*val = xstrdup(sun_mixer_device);
return 0;
}
const struct mixer_plugin_ops op_mixer_ops = {
.init = sun_mixer_init,
.exit = sun_mixer_exit,
.open = sun_mixer_open,
.close = sun_mixer_close,
.set_volume = sun_mixer_set_volume,
.get_volume = sun_mixer_get_volume,
};
const struct mixer_plugin_opt op_mixer_options[] = {
OPT(sun_mixer, channel),
OPT(sun_mixer, device),
{ NULL },
};

285
op/oss.c Normal file
View File

@@ -0,0 +1,285 @@
/*
* Copyright 2008-2013 Various Authors
* Copyright 2004-2005 Timo Hirvonen
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "../op.h"
#include "../sf.h"
#include "../xmalloc.h"
#include "../debug.h"
#include "../utils.h"
#if defined(__OpenBSD__)
#include <soundcard.h>
#else
#include <sys/soundcard.h>
#endif
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
static sample_format_t oss_sf;
static int oss_fd = -1;
/* configuration */
static char *oss_dsp_device = NULL;
static int oss_close(void);
static int oss_reset(void)
{
if (ioctl(oss_fd, SNDCTL_DSP_RESET, 0) == -1) {
return -1;
}
return 0;
}
#if defined(__linux__)
/* defined only in OSSv4, but seem to work in OSSv3 (Linux) */
#ifndef AFMT_S32_LE
#define AFMT_S32_LE 0x00001000
#endif
#ifndef AFMT_S32_BE
#define AFMT_S32_BE 0x00002000
#endif
#ifndef AFMT_S24_PACKED
#define AFMT_S24_PACKED 0x00040000
#endif
#endif
struct oss_fmt {
int fmt, bits, sig, be;
};
static struct oss_fmt oss_fmts[] = {
{ AFMT_S16_BE, 16, 1, 1 },
{ AFMT_S16_LE, 16, 1, 0 },
#ifdef AFMT_S24_PACKED
{ AFMT_S24_PACKED, 24, 1, 0 },
#endif
#ifdef AFMT_S24_BE
{ AFMT_S24_BE, 24, 1, 1 },
#endif
#ifdef AFMT_S24_LE
{ AFMT_S24_LE, 24, 1, 0 },
#endif
#ifdef AFMT_S32_BE
{ AFMT_S32_BE, 32, 1, 1 },
#endif
#ifdef AFMT_S32_LE
{ AFMT_S32_LE, 32, 1, 0 },
#endif
{ AFMT_U16_BE, 16, 0, 1 },
{ AFMT_U16_LE, 16, 0, 0 },
#ifdef AFMT_U24_BE
{ AFMT_U24_BE, 24, 0, 1 },
#endif
#ifdef AFMT_U24_LE
{ AFMT_U24_LE, 24, 0, 0 },
#endif
#ifdef AFMT_U32_BE
{ AFMT_U32_BE, 32, 0, 1 },
#endif
#ifdef AFMT_U32_LE
{ AFMT_U32_LE, 32, 0, 0 },
#endif
{ AFMT_S8, 8, 1, 0 },
{ AFMT_S8, 8, 1, 1 },
{ AFMT_U8, 8, 0, 0 },
{ AFMT_U8, 8, 0, 1 },
};
static int oss_set_sf(sample_format_t sf)
{
int found, tmp, log2_fragment_size, nr_fragments, bytes_per_second;
size_t i;
oss_reset();
oss_sf = sf;
#ifdef SNDCTL_DSP_CHANNELS
tmp = sf_get_channels(oss_sf);
if (ioctl(oss_fd, SNDCTL_DSP_CHANNELS, &tmp) == -1)
return -1;
#else
tmp = sf_get_channels(oss_sf) - 1;
if (ioctl(oss_fd, SNDCTL_DSP_STEREO, &tmp) == -1)
return -1;
#endif
found = 0;
for (i = 0; i < N_ELEMENTS(oss_fmts); i++) {
if (sf_get_bits(oss_sf) == oss_fmts[i].bits &&
sf_get_signed(oss_sf) == oss_fmts[i].sig &&
sf_get_bigendian(oss_sf) == oss_fmts[i].be) {
found = 1;
tmp = oss_fmts[i].fmt;
break;
}
}
if (!found) {
d_print("unsupported sample format: %c%u_%s\n",
sf_get_signed(oss_sf) ? 'S' : 'U', sf_get_bits(oss_sf),
sf_get_bigendian(oss_sf) ? "BE" : "LE");
return -1;
}
if (ioctl(oss_fd, SNDCTL_DSP_SAMPLESIZE, &tmp) == -1)
return -1;
tmp = sf_get_rate(oss_sf);
if (ioctl(oss_fd, SNDCTL_DSP_SPEED, &tmp) == -1)
return -1;
bytes_per_second = sf_get_second_size(oss_sf);
log2_fragment_size = 0;
while (1 << log2_fragment_size < bytes_per_second / 25)
log2_fragment_size++;
log2_fragment_size--;
nr_fragments = 32;
/* bits 0..15 = size of fragment, 16..31 = log2(number of fragments) */
tmp = (nr_fragments << 16) + log2_fragment_size;
if (ioctl(oss_fd, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
return -1;
return 0;
}
static int oss_device_exists(const char *device)
{
struct stat s;
if (stat(device, &s))
return 0;
return 1;
}
static int oss_init(void)
{
const char *new_dsp_dev = "/dev/sound/dsp";
const char *dsp_dev = "/dev/dsp";
if (oss_dsp_device) {
if (oss_device_exists(oss_dsp_device))
return 0;
free(oss_dsp_device);
oss_dsp_device = NULL;
return -1;
}
if (oss_device_exists(new_dsp_dev)) {
oss_dsp_device = xstrdup(new_dsp_dev);
return 0;
}
if (oss_device_exists(dsp_dev)) {
oss_dsp_device = xstrdup(dsp_dev);
return 0;
}
return -1;
}
static int oss_exit(void)
{
free(oss_dsp_device);
oss_dsp_device = NULL;
return 0;
}
static int oss_open(sample_format_t sf, const channel_position_t *channel_map)
{
int oss_version = 0;
oss_fd = open(oss_dsp_device, O_WRONLY);
if (oss_fd == -1)
return -1;
ioctl(oss_fd, OSS_GETVERSION, &oss_version);
d_print("oss version: %#08x\n", oss_version);
if (oss_set_sf(sf) == -1) {
oss_close();
return -1;
}
return 0;
}
static int oss_close(void)
{
close(oss_fd);
oss_fd = -1;
return 0;
}
static int oss_write(const char *buffer, int count)
{
int rc;
count -= count % sf_get_frame_size(oss_sf);
rc = write(oss_fd, buffer, count);
return rc;
}
static int oss_pause(void)
{
if (ioctl(oss_fd, SNDCTL_DSP_POST, NULL) == -1)
return -1;
return 0;
}
static int oss_unpause(void)
{
return 0;
}
static int oss_buffer_space(void)
{
audio_buf_info info;
int space;
if (ioctl(oss_fd, SNDCTL_DSP_GETOSPACE, &info) == -1)
return -1;
space = (info.fragments - 1) * info.fragsize;
return space;
}
static int op_oss_set_device(const char *val)
{
free(oss_dsp_device);
oss_dsp_device = xstrdup(val);
return 0;
}
static int op_oss_get_device(char **val)
{
if (oss_dsp_device)
*val = xstrdup(oss_dsp_device);
return 0;
}
const struct output_plugin_ops op_pcm_ops = {
.init = oss_init,
.exit = oss_exit,
.open = oss_open,
.close = oss_close,
.write = oss_write,
.pause = oss_pause,
.unpause = oss_unpause,
.buffer_space = oss_buffer_space,
};
const struct output_plugin_opt op_pcm_options[] = {
OPT(op_oss, device),
{ NULL },
};
const int op_priority = 1;
const unsigned op_abi_version = OP_ABI_VERSION;

651
op/pulse.c Normal file
View File

@@ -0,0 +1,651 @@
/*
* Copyright (C) 2008-2013 Various Authors
* Copyright (C) 2009 Gregory Petrosyan
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <pulse/pulseaudio.h>
#include "../op.h"
#include "../mixer.h"
#include "../debug.h"
#include "../utils.h"
#include "../xmalloc.h"
static pa_threaded_mainloop *mainloop;
static pa_context *context;
static pa_stream *stream;
static pa_channel_map channel_map;
static pa_cvolume volume;
static pa_sample_spec sample_spec;
static int mixer_notify_in;
static int mixer_notify_out;
static int mixer_notify_output_in;
static int mixer_notify_output_out;
static long last_output_idx;
/* configuration */
static int pa_restore_volume = 1;
#define RET_PA_ERROR(err) \
do { \
d_print("PulseAudio error: %s\n", pa_strerror(err)); \
return -OP_ERROR_INTERNAL; \
} while (0)
#define RET_PA_LAST_ERROR() RET_PA_ERROR(pa_context_errno(context))
static int pulse_wait_and_unlock(pa_operation *op)
{
pa_operation_state_t state;
if (!op) {
pa_threaded_mainloop_unlock(mainloop);
RET_PA_LAST_ERROR();
}
while ((state = pa_operation_get_state(op)) == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(mainloop);
pa_operation_unref(op);
pa_threaded_mainloop_unlock(mainloop);
if (state == PA_OPERATION_DONE)
return OP_ERROR_SUCCESS;
else
RET_PA_LAST_ERROR();
}
static int pulse_nowait_and_unlock(pa_operation *op)
{
if (!op) {
pa_threaded_mainloop_unlock(mainloop);
RET_PA_LAST_ERROR();
}
pa_operation_unref(op);
pa_threaded_mainloop_unlock(mainloop);
return OP_ERROR_SUCCESS;
}
static int pulse_wait(pa_operation *op)
{
pa_operation_state_t state;
if (!op)
RET_PA_LAST_ERROR();
while ((state = pa_operation_get_state(op)) == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(mainloop);
pa_operation_unref(op);
if (state == PA_OPERATION_DONE)
return OP_ERROR_SUCCESS;
else
RET_PA_LAST_ERROR();
}
static pa_sample_format_t convert_sample_format(sample_format_t sf)
{
const int _signed = sf_get_signed(sf);
const int big_endian = sf_get_bigendian(sf);
const int sample_size = sf_get_sample_size(sf) * 8;
if (!_signed && sample_size == 8)
return PA_SAMPLE_U8;
if (_signed) {
switch (sample_size) {
case 16:
return big_endian ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE;
case 24:
return big_endian ? PA_SAMPLE_S24BE : PA_SAMPLE_S24LE;
case 32:
return big_endian ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE;
}
}
return PA_SAMPLE_INVALID;
}
#define RET_IF(x) case CHANNEL_POSITION_ ## x: return PA_CHANNEL_POSITION_ ## x
static pa_channel_position_t convert_channel_position(channel_position_t p)
{
switch (p) {
RET_IF(MONO);
RET_IF(FRONT_LEFT); RET_IF(FRONT_RIGHT); RET_IF(FRONT_CENTER);
RET_IF(REAR_CENTER); RET_IF(REAR_LEFT); RET_IF(REAR_RIGHT);
RET_IF(LFE);
RET_IF(FRONT_LEFT_OF_CENTER); RET_IF(FRONT_RIGHT_OF_CENTER);
RET_IF(SIDE_LEFT); RET_IF(SIDE_RIGHT);
RET_IF(TOP_CENTER);
RET_IF(TOP_FRONT_LEFT); RET_IF(TOP_FRONT_RIGHT); RET_IF(TOP_FRONT_CENTER);
RET_IF(TOP_REAR_LEFT); RET_IF(TOP_REAR_RIGHT); RET_IF(TOP_REAR_CENTER);
default:
return PA_CHANNEL_POSITION_INVALID;
}
}
static pa_proplist *pulse_create_app_proplist(void)
{
pa_proplist *pl = pa_proplist_new();
BUG_ON(!pl);
int rc = pa_proplist_sets(pl, PA_PROP_APPLICATION_ID, "cmus");
BUG_ON(rc);
rc = pa_proplist_sets(pl, PA_PROP_APPLICATION_NAME, "C* Music Player");
BUG_ON(rc);
rc = pa_proplist_sets(pl, PA_PROP_APPLICATION_VERSION, VERSION);
BUG_ON(rc);
return pl;
}
static pa_proplist *pulse_create_stream_proplist(void)
{
pa_proplist *pl = pa_proplist_new();
BUG_ON(!pl);
int rc = pa_proplist_sets(pl, PA_PROP_MEDIA_ROLE, "music");
BUG_ON(rc);
rc = pa_proplist_sets(pl, PA_PROP_MEDIA_ICON_NAME, "audio-x-generic");
BUG_ON(rc);
return pl;
}
static const char *pa_context_state_str(pa_context_state_t s)
{
switch (s) {
case PA_CONTEXT_AUTHORIZING:
return "PA_CONTEXT_AUTHORIZING";
case PA_CONTEXT_CONNECTING:
return "PA_CONTEXT_CONNECTING";
case PA_CONTEXT_FAILED:
return "PA_CONTEXT_FAILED";
case PA_CONTEXT_READY:
return "PA_CONTEXT_READY";
case PA_CONTEXT_SETTING_NAME:
return "PA_CONTEXT_SETTING_NAME";
case PA_CONTEXT_TERMINATED:
return "PA_CONTEXT_TERMINATED";
case PA_CONTEXT_UNCONNECTED:
return "PA_CONTEXT_UNCONNECTED";
}
return "unknown";
}
static void pulse_context_state_cb(pa_context *c, void *data)
{
const pa_context_state_t cs = pa_context_get_state(c);
d_print("context state has changed to %s\n", pa_context_state_str(cs));
switch (cs) {
case PA_CONTEXT_READY:
case PA_CONTEXT_FAILED:
case PA_CONTEXT_TERMINATED:
pa_threaded_mainloop_signal(mainloop, 0);
default:
return;
}
}
static const char *pa_stream_state_str(pa_stream_state_t s)
{
switch (s) {
case PA_STREAM_CREATING:
return "PA_STREAM_CREATING";
case PA_STREAM_FAILED:
return "PA_STREAM_FAILED";
case PA_STREAM_READY:
return "PA_STREAM_READY";
case PA_STREAM_TERMINATED:
return "PA_STREAM_TERMINATED";
case PA_STREAM_UNCONNECTED:
return "PA_STREAM_UNCONNECTED";
}
return "unknown";
}
static void pulse_stream_state_cb(pa_stream *s, void *data)
{
const pa_stream_state_t ss = pa_stream_get_state(s);
d_print("stream state has changed to %s\n", pa_stream_state_str(ss));
switch (ss) {
case PA_STREAM_READY:
case PA_STREAM_FAILED:
case PA_STREAM_TERMINATED:
pa_threaded_mainloop_signal(mainloop, 0);
default:
return;
}
}
static void pulse_sink_input_info_cb(pa_context *c,
const pa_sink_input_info *i, int eol, void *data)
{
if (!i)
return;
memcpy(&volume, &i->volume, sizeof(volume));
notify_via_pipe(mixer_notify_in);
if (last_output_idx != i->sink) {
if (last_output_idx != -1)
notify_via_pipe(mixer_notify_output_in);
last_output_idx = i->sink;
}
}
static void pulse_context_subscription_cb(pa_context *ctx,
pa_subscription_event_type_t type, uint32_t idx, void *data)
{
type &= PA_SUBSCRIPTION_EVENT_TYPE_MASK;
if (type != PA_SUBSCRIPTION_EVENT_CHANGE)
return;
if (stream && idx == pa_stream_get_index(stream)) {
pa_context_get_sink_input_info(ctx, idx,
pulse_sink_input_info_cb, NULL);
}
}
static int pulse_create_context(void)
{
pa_mainloop_api *api = pa_threaded_mainloop_get_api(mainloop);
BUG_ON(!api);
pa_proplist *pl = pulse_create_app_proplist();
pa_threaded_mainloop_lock(mainloop);
context = pa_context_new_with_proplist(api, "C* Music Player", pl);
pa_proplist_free(pl);
BUG_ON(!context);
pa_context_set_state_callback(context, pulse_context_state_cb, NULL);
int rc = pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL);
if (rc)
goto err_free_ctx;
for (;;) {
pa_context_state_t s = pa_context_get_state(context);
if (s == PA_CONTEXT_READY)
break;
if (s == PA_CONTEXT_TERMINATED || s == PA_CONTEXT_FAILED)
goto err_disconnect_ctx;
pa_threaded_mainloop_wait(mainloop);
}
pa_context_set_subscribe_callback(context,
pulse_context_subscription_cb, NULL);
pa_operation *op = pa_context_subscribe(context,
PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL);
if (!op)
goto err_disconnect_ctx;
pa_operation_unref(op);
pa_threaded_mainloop_unlock(mainloop);
return OP_ERROR_SUCCESS;
err_disconnect_ctx:
pa_context_disconnect(context);
err_free_ctx:
pa_context_unref(context);
context = NULL;
pa_threaded_mainloop_unlock(mainloop);
RET_PA_LAST_ERROR();
}
static void pulse_stream_success_cb(pa_stream *s, int success, void *data)
{
pa_threaded_mainloop_signal(mainloop, 0);
}
static int op_pulse_init(void)
{
mainloop = pa_threaded_mainloop_new();
BUG_ON(!mainloop);
int rc = pa_threaded_mainloop_start(mainloop);
if (rc) {
pa_threaded_mainloop_free(mainloop);
RET_PA_ERROR(rc);
}
return OP_ERROR_SUCCESS;
}
static int op_pulse_exit(void)
{
if (mainloop) {
pa_threaded_mainloop_stop(mainloop);
pa_threaded_mainloop_free(mainloop);
mainloop = NULL;
}
return OP_ERROR_SUCCESS;
}
static int op_pulse_open(sample_format_t sf, const channel_position_t *cmap)
{
pa_proplist *pl;
int rc, i;
const pa_sample_spec ss = {
.format = convert_sample_format(sf),
.rate = sf_get_rate(sf),
.channels = sf_get_channels(sf)
};
if (!pa_sample_spec_valid(&ss))
return -OP_ERROR_SAMPLE_FORMAT;
sample_spec = ss;
if (cmap && channel_map_valid(cmap)) {
pa_channel_map_init(&channel_map);
channel_map.channels = ss.channels;
for (i = 0; i < channel_map.channels; i++)
channel_map.map[i] = convert_channel_position(cmap[i]);
} else {
pa_channel_map_init_auto(&channel_map, ss.channels,
PA_CHANNEL_MAP_ALSA);
}
last_output_idx = -1;
rc = pulse_create_context();
if (rc)
return rc;
pl = pulse_create_stream_proplist();
pa_threaded_mainloop_lock(mainloop);
stream = pa_stream_new_with_proplist(context, "playback",
&ss, &channel_map, pl);
pa_proplist_free(pl);
if (!stream)
goto err;
pa_stream_set_state_callback(stream, pulse_stream_state_cb, NULL);
rc = pa_stream_connect_playback(stream, NULL, NULL, PA_STREAM_NOFLAGS,
pa_restore_volume ? NULL : &volume, NULL);
if (rc)
goto err_free_stream;
for (;;) {
pa_stream_state_t s = pa_stream_get_state(stream);
if (s == PA_STREAM_READY)
break;
if (s == PA_STREAM_FAILED || s == PA_STREAM_TERMINATED)
goto err_free_stream;
pa_threaded_mainloop_wait(mainloop);
}
pa_context_get_sink_input_info(context, pa_stream_get_index(stream),
pulse_sink_input_info_cb, NULL);
pa_threaded_mainloop_unlock(mainloop);
return OP_ERROR_SUCCESS;
err_free_stream:
pa_stream_unref(stream);
err:
pa_threaded_mainloop_unlock(mainloop);
RET_PA_LAST_ERROR();
}
static void pulse_drain_if_playing(void)
{
if (!pa_stream_is_corked(stream)
&& !pa_stream_is_suspended(stream)
&& pa_stream_get_state(stream) == PA_STREAM_READY)
{
pa_operation *op = pa_stream_drain(stream,
pulse_stream_success_cb, NULL);
pulse_wait(op);
}
}
static int op_pulse_close(void)
{
pa_threaded_mainloop_lock(mainloop);
if (stream) {
pulse_drain_if_playing();
pa_stream_disconnect(stream);
pa_stream_unref(stream);
stream = NULL;
}
if (context) {
pa_context_disconnect(context);
pa_context_unref(context);
context = NULL;
}
pa_threaded_mainloop_unlock(mainloop);
return OP_ERROR_SUCCESS;
}
static int op_pulse_drop(void)
{
pa_threaded_mainloop_lock(mainloop);
pa_operation *op = pa_stream_flush(stream, pulse_stream_success_cb, NULL);
return pulse_wait_and_unlock(op);
}
static int op_pulse_write(const char *buf, int count)
{
pa_threaded_mainloop_lock(mainloop);
int rc = pa_stream_write(stream, buf, count, NULL, 0, PA_SEEK_RELATIVE);
pa_threaded_mainloop_unlock(mainloop);
if (rc)
RET_PA_ERROR(rc);
else
return count;
}
static int op_pulse_buffer_space(void)
{
pa_threaded_mainloop_lock(mainloop);
size_t s = pa_stream_writable_size(stream);
pa_threaded_mainloop_unlock(mainloop);
if (s == (size_t)-1)
RET_PA_LAST_ERROR();
else
return s;
}
static int pulse_stream_cork(int pause)
{
pa_threaded_mainloop_lock(mainloop);
pa_operation *op = pa_stream_cork(stream, pause,
pulse_stream_success_cb, NULL);
return pulse_wait_and_unlock(op);
}
static int op_pulse_pause(void)
{
return pulse_stream_cork(1);
}
static int op_pulse_unpause(void)
{
return pulse_stream_cork(0);
}
static int op_pulse_mixer_init(void)
{
if (!pa_channel_map_init_stereo(&channel_map))
RET_PA_LAST_ERROR();
pa_cvolume_reset(&volume, 2);
init_pipes(&mixer_notify_out, &mixer_notify_in);
init_pipes(&mixer_notify_output_out, &mixer_notify_output_in);
return OP_ERROR_SUCCESS;
}
static int op_pulse_mixer_exit(void)
{
close(mixer_notify_out);
close(mixer_notify_in);
close(mixer_notify_output_out);
close(mixer_notify_output_in);
return OP_ERROR_SUCCESS;
}
static int op_pulse_mixer_open(int *volume_max)
{
*volume_max = PA_VOLUME_NORM;
return OP_ERROR_SUCCESS;
}
static int op_pulse_mixer_close(void)
{
return OP_ERROR_SUCCESS;
}
static int op_pulse_mixer_get_fds(int what, int *fds)
{
switch (what) {
case MIXER_FDS_VOLUME:
fds[0] = mixer_notify_out;
return 1;
case MIXER_FDS_OUTPUT:
fds[0] = mixer_notify_output_out;
return 1;
default:
return 0;
}
}
static int op_pulse_mixer_set_volume(int l, int r)
{
if (!stream && pa_restore_volume)
return -OP_ERROR_NOT_OPEN;
pa_cvolume_set(&volume, sample_spec.channels, (pa_volume_t)((l + r) / 2));
pa_cvolume_set_position(&volume, &channel_map,
PA_CHANNEL_POSITION_FRONT_LEFT, (pa_volume_t)l);
pa_cvolume_set_position(&volume, &channel_map,
PA_CHANNEL_POSITION_FRONT_RIGHT, (pa_volume_t)r);
if (!stream) {
return OP_ERROR_SUCCESS;
} else {
pa_threaded_mainloop_lock(mainloop);
pa_operation *op = pa_context_set_sink_input_volume(
context, pa_stream_get_index(stream),
&volume, NULL, NULL);
return pulse_nowait_and_unlock(op);
}
}
static int op_pulse_mixer_get_volume(int *l, int *r)
{
clear_pipe(mixer_notify_out, -1);
if (!stream && pa_restore_volume)
return -OP_ERROR_NOT_OPEN;
*l = pa_cvolume_get_position(&volume, &channel_map,
PA_CHANNEL_POSITION_FRONT_LEFT);
*r = pa_cvolume_get_position(&volume, &channel_map,
PA_CHANNEL_POSITION_FRONT_RIGHT);
return OP_ERROR_SUCCESS;
}
static int op_pulse_set_restore_volume(const char *val)
{
pa_restore_volume = is_freeform_true(val);
return 0;
}
static int op_pulse_get_restore_volume(char **val)
{
*val = xstrdup(pa_restore_volume ? "1" : "0");
return 0;
}
const struct output_plugin_ops op_pcm_ops = {
.init = op_pulse_init,
.exit = op_pulse_exit,
.open = op_pulse_open,
.close = op_pulse_close,
.drop = op_pulse_drop,
.write = op_pulse_write,
.buffer_space = op_pulse_buffer_space,
.pause = op_pulse_pause,
.unpause = op_pulse_unpause,
};
const struct mixer_plugin_ops op_mixer_ops = {
.init = op_pulse_mixer_init,
.exit = op_pulse_mixer_exit,
.open = op_pulse_mixer_open,
.close = op_pulse_mixer_close,
.get_fds.abi_2 = op_pulse_mixer_get_fds,
.set_volume = op_pulse_mixer_set_volume,
.get_volume = op_pulse_mixer_get_volume,
};
const struct output_plugin_opt op_pcm_options[] = {
{ NULL },
};
const struct mixer_plugin_opt op_mixer_options[] = {
OPT(op_pulse, restore_volume),
{ NULL },
};
const int op_priority = -2;
const unsigned op_abi_version = OP_ABI_VERSION;

369
op/roar.c Normal file
View File

@@ -0,0 +1,369 @@
/*
* Copyright 2008-2013 Various Authors
* Copyright 2010-2011 Philipp 'ph3-der-loewe' Schafft
* Copyright 2006 Johannes Weißl
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <roaraudio.h>
#include "../op.h"
#include "../mixer.h"
#include "../xmalloc.h"
#include "../utils.h"
#include "../misc.h"
#include "../debug.h"
// we do not use native 2^16-1 here as they use signed ints with 16 bit
// so we use 2^(16-1)-1 here.
#define MIXER_BASE_VOLUME 32767
static struct roar_connection con;
static roar_vs_t *vss = NULL;
static int err;
static sample_format_t format;
/* configuration */
static char *host = NULL;
static char *role = NULL;
static inline void _err_to_errno(void)
{
roar_err_set(err);
roar_err_to_errno();
}
static int op_roar_dummy(void)
{
return 0;
}
#if DEBUG > 1
static ssize_t op_roar_debug_write(struct roar_vio_calls *vio, void *buf_, size_t count)
{
char *buf = (char *) buf_;
int len = count;
if (len > 0 && buf[len-1] == '\n')
len--;
if (len > 0)
d_print("%*s\n", len, buf);
return count;
}
static struct roar_vio_calls op_roar_debug_cbs = {
.write = op_roar_debug_write
};
#endif
static int op_roar_init(void)
{
#if DEBUG > 1
roar_debug_set_stderr_mode(ROAR_DEBUG_MODE_VIO);
roar_debug_set_stderr_vio(&op_roar_debug_cbs);
#else
roar_debug_set_stderr_mode(ROAR_DEBUG_MODE_SYSLOG);
#endif
return 0;
}
static int op_roar_exit(void)
{
free(host);
free(role);
return 0;
}
static int _set_role(void)
{
int roleid = ROAR_ROLE_UNKNOWN;
if (role == NULL)
return 0;
roleid = roar_str2role(role);
if (roleid == ROAR_ROLE_UNKNOWN) {
// TODO: warn if role is invalid.
return 0;
}
if (roar_vs_role(vss, roleid, &err) == -1) {
return -OP_ERROR_ERRNO;
}
return 0;
}
static int op_roar_open(sample_format_t sf, const channel_position_t *channel_map)
{
struct roar_audio_info info;
int ret;
memset(&info, 0, sizeof(info));
ROAR_DBG("op_roar_open(*) = ?");
format = sf;
info.rate = sf_get_rate(sf);
info.channels = sf_get_channels(sf);
info.bits = sf_get_bits(sf);
if (sf_get_bigendian(sf)) {
if (sf_get_signed(sf)) {
info.codec = ROAR_CODEC_PCM_S_BE;
} else {
info.codec = ROAR_CODEC_PCM_U_BE;
}
} else {
if (sf_get_signed(sf)) {
info.codec = ROAR_CODEC_PCM_S_LE;
} else {
info.codec = ROAR_CODEC_PCM_U_LE;
}
}
ROAR_DBG("op_roar_open(*) = ?");
if (roar_libroar_set_server(host) == -1) {
ROAR_DBG("op_roar_open(*) = ?");
roar_err_to_errno();
return -OP_ERROR_ERRNO;
}
if (roar_simple_connect2(&con, NULL, "C* Music Player (cmus)", ROAR_ENUM_FLAG_NONBLOCK, 0) == -1) {
ROAR_DBG("op_roar_open(*) = ?");
roar_err_to_errno();
return -OP_ERROR_ERRNO;
}
vss = roar_vs_new_from_con(&con, &err);
if (vss == NULL) {
ROAR_DBG("op_roar_open(*) = ?");
roar_disconnect(&con);
_err_to_errno();
return -OP_ERROR_ERRNO;
}
if (roar_vs_stream(vss, &info, ROAR_DIR_PLAY, &err) == -1) {
ROAR_DBG("op_roar_open(*) = ?");
roar_disconnect(&con);
_err_to_errno();
return -OP_ERROR_ERRNO;
}
ROAR_DBG("op_roar_open(*) = ?");
if (roar_vs_buffer(vss, 2048*8, &err) == -1) {
roar_vs_close(vss, ROAR_VS_TRUE, NULL);
roar_disconnect(&con);
_err_to_errno();
return -OP_ERROR_ERRNO;
}
ROAR_DBG("op_roar_open(*) = ?");
ret = _set_role();
if (ret != 0) {
roar_vs_close(vss, ROAR_VS_TRUE, NULL);
roar_disconnect(&con);
_err_to_errno();
return ret;
}
ROAR_DBG("op_roar_open(*) = ?");
if (roar_vs_blocking(vss, ROAR_VS_FALSE, &err) == -1) {
/* FIXME: handle this error */
}
ROAR_DBG("op_roar_open(*) = 0");
return 0;
}
static int op_roar_close(void)
{
roar_vs_close(vss, ROAR_VS_FALSE, &err);
roar_disconnect(&con);
return 0;
}
static int op_roar_drop(void)
{
if (roar_vs_reset_buffer(vss, ROAR_VS_TRUE, ROAR_VS_TRUE, &err) == -1) {
/* FIXME: handle this error
* FIXME: I'm deprecated */
}
return 0;
}
static int op_roar_write(const char *buffer, int count)
{
int ret;
int i;
ret = roar_vs_write(vss, buffer, count, &err);
for (i = 0; i < 8; i++)
roar_vs_iterate(vss, ROAR_VS_NOWAIT, NULL);
return ret;
}
static int op_roar_buffer_space(void)
{
ssize_t ret;
int i;
int fs = sf_get_frame_size(format);
for (i = 0; i < 8; i++)
roar_vs_iterate(vss, ROAR_VS_NOWAIT, NULL);
ret = roar_vs_get_avail_write(vss, &err);
ret -= ret % fs;
return ret;
}
static int op_roar_pause(void) {
if (roar_vs_pause(vss, ROAR_VS_TRUE, &err) == -1) {
_err_to_errno();
return -OP_ERROR_ERRNO;
}
return 0;
}
static int op_roar_unpause(void) {
if (roar_vs_pause(vss, ROAR_VS_FALSE, &err) == -1) {
_err_to_errno();
return -OP_ERROR_ERRNO;
}
return 0;
}
static int op_roar_mixer_open(int *volume_max)
{
*volume_max = MIXER_BASE_VOLUME;
return 0;
}
static int op_roar_mixer_set_volume(int l, int r)
{
float lf, rf;
if (vss == NULL)
return -OP_ERROR_NOT_OPEN;
lf = (float)l / (float)MIXER_BASE_VOLUME;
rf = (float)r / (float)MIXER_BASE_VOLUME;
if (roar_vs_volume_stereo(vss, lf, rf, &err) == -1) {
_err_to_errno();
return -OP_ERROR_ERRNO;
}
return 0;
}
static int op_roar_mixer_get_volume(int *l, int *r)
{
float lf, rf;
if (vss == NULL)
return -OP_ERROR_NOT_OPEN;
if (roar_vs_volume_get(vss, &lf, &rf, &err) == -1) {
_err_to_errno();
return -OP_ERROR_ERRNO;
}
lf *= (float)MIXER_BASE_VOLUME;
rf *= (float)MIXER_BASE_VOLUME;
*l = lf;
*r = rf;
return 0;
}
static int op_roar_set_server(const char *val)
{
free(host);
host = xstrdup(val);
return 0;
}
static int op_roar_get_server(char **val)
{
if (host != NULL)
*val = xstrdup(host);
return 0;
}
static int op_roar_set_role(const char *val)
{
free(host);
host = xstrdup(val);
return 0;
}
static int op_roar_get_role(char **val)
{
if (role != NULL)
*val = xstrdup(role);
return 0;
}
const struct output_plugin_ops op_pcm_ops = {
.init = op_roar_init,
.exit = op_roar_exit,
.open = op_roar_open,
.close = op_roar_close,
.drop = op_roar_drop,
.write = op_roar_write,
.buffer_space = op_roar_buffer_space,
.pause = op_roar_pause,
.unpause = op_roar_unpause,
};
const struct output_plugin_opt op_pcm_options[] = {
OPT(op_roar, server),
OPT(op_roar, role),
{ NULL },
};
const struct mixer_plugin_ops op_mixer_ops = {
.init = op_roar_dummy,
.exit = op_roar_dummy,
.open = op_roar_mixer_open,
.close = op_roar_dummy,
.get_fds = NULL,
.set_volume = op_roar_mixer_set_volume,
.get_volume = op_roar_mixer_get_volume,
};
const struct mixer_plugin_opt op_mixer_options[] = {
{ NULL },
};
const int op_priority = -1;
const unsigned op_abi_version = OP_ABI_VERSION;

251
op/sndio.c Normal file
View File

@@ -0,0 +1,251 @@
/*
* Copyright (C) 2011 Donovan "Tsomi" Watteau <tsoomi@gmail.com>
*
* Based on Thomas Pfaff's work for XMMS, and some suggestions from
* Alexandre Ratchov.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sndio.h>
#include "../op.h"
#include "../mixer.h"
#include "../sf.h"
#include "../xmalloc.h"
static sample_format_t sndio_sf;
static struct sio_par par;
static struct sio_hdl *hdl;
static int sndio_volume = SIO_MAXVOL;
static int sndio_paused;
static int sndio_mixer_set_volume(int l, int r)
{
sndio_volume = l > r ? l : r;
if (hdl == NULL)
return -OP_ERROR_NOT_INITIALIZED;
if (!sio_setvol(hdl, sndio_volume))
return -OP_ERROR_INTERNAL;
return OP_ERROR_SUCCESS;
}
static int sndio_mixer_get_volume(int *l, int *r)
{
*l = *r = sndio_volume;
return OP_ERROR_SUCCESS;
}
static int sndio_set_sf(sample_format_t sf)
{
struct sio_par apar;
sndio_sf = sf;
sio_initpar(&par);
par.pchan = sf_get_channels(sndio_sf);
par.rate = sf_get_rate(sndio_sf);
sndio_paused = 0;
if (sf_get_signed(sndio_sf))
par.sig = 1;
else
par.sig = 0;
if (sf_get_bigendian(sndio_sf))
par.le = 0;
else
par.le = 1;
switch (sf_get_bits(sndio_sf)) {
case 32:
par.bits = 32;
break;
case 24:
par.bits = 24;
par.bps = 3;
break;
case 16:
par.bits = 16;
break;
case 8:
par.bits = 8;
break;
default:
return -OP_ERROR_SAMPLE_FORMAT;
}
par.appbufsz = par.rate * 300 / 1000;
apar = par;
if (!sio_setpar(hdl, &par))
return -OP_ERROR_INTERNAL;
if (!sio_getpar(hdl, &par))
return -OP_ERROR_INTERNAL;
if (apar.rate != par.rate || apar.pchan != par.pchan ||
apar.bits != par.bits || (par.bits > 8 && apar.le != par.le) ||
apar.sig != par.sig)
return -OP_ERROR_INTERNAL;
sndio_mixer_set_volume(sndio_volume, sndio_volume);
if (!sio_start(hdl))
return -OP_ERROR_INTERNAL;
return OP_ERROR_SUCCESS;
}
static int sndio_init(void)
{
return OP_ERROR_SUCCESS;
}
static int sndio_exit(void)
{
return OP_ERROR_SUCCESS;
}
static int sndio_close(void)
{
if (hdl != NULL) {
sio_close(hdl);
hdl = NULL;
}
return OP_ERROR_SUCCESS;
}
static int sndio_open(sample_format_t sf, const channel_position_t *channel_map)
{
int ret = 0;
hdl = sio_open(NULL, SIO_PLAY, 0);
if (hdl == NULL)
return -OP_ERROR_INTERNAL;
if ((ret = sndio_set_sf(sf)) < 0) {
sndio_close();
return ret;
}
return OP_ERROR_SUCCESS;
}
static int sndio_write(const char *buf, int cnt)
{
size_t rc;
rc = sio_write(hdl, buf, cnt);
if (rc == 0)
return -OP_ERROR_INTERNAL;
return rc;
}
static int sndio_pause(void)
{
if (!sndio_paused) {
if (!sio_stop(hdl))
return -OP_ERROR_INTERNAL;
sndio_paused = 1;
}
return OP_ERROR_SUCCESS;
}
static int sndio_unpause(void)
{
if (sndio_paused) {
if (!sio_start(hdl))
return -OP_ERROR_INTERNAL;
sndio_paused = 0;
}
return OP_ERROR_SUCCESS;
}
static int sndio_buffer_space(void)
{
/*
* Do as if there's always some space and let sio_write() block.
*/
return par.bufsz * par.bps * par.pchan;
}
static int sndio_mixer_init(void)
{
return OP_ERROR_SUCCESS;
}
static int sndio_mixer_exit(void)
{
return OP_ERROR_SUCCESS;
}
static int sndio_mixer_open(int *volume_max)
{
*volume_max = SIO_MAXVOL;
return OP_ERROR_SUCCESS;
}
static int sndio_mixer_close(void)
{
return OP_ERROR_SUCCESS;
}
const struct output_plugin_ops op_pcm_ops = {
.init = sndio_init,
.exit = sndio_exit,
.open = sndio_open,
.close = sndio_close,
.write = sndio_write,
.pause = sndio_pause,
.unpause = sndio_unpause,
.buffer_space = sndio_buffer_space,
};
const struct mixer_plugin_ops op_mixer_ops = {
.init = sndio_mixer_init,
.exit = sndio_mixer_exit,
.open = sndio_mixer_open,
.close = sndio_mixer_close,
.set_volume = sndio_mixer_set_volume,
.get_volume = sndio_mixer_get_volume,
};
const struct output_plugin_opt op_pcm_options[] = {
{ NULL },
};
const struct mixer_plugin_opt op_mixer_options[] = {
{ NULL },
};
const int op_priority = 2;
const unsigned op_abi_version = OP_ABI_VERSION;

251
op/sun.c Normal file
View File

@@ -0,0 +1,251 @@
/*
* Copyright 2008-2013 Various Authors
* Copyright 2004-2005 Timo Hirvonen
*
* sun.c by alex <pukpuk@gmx.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/audioio.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include "../op.h"
#include "../sf.h"
#include "../xmalloc.h"
static sample_format_t sun_sf;
static int sun_fd = -1;
static char *sun_audio_device = NULL;
static int sun_reset(void)
{
if (ioctl(sun_fd, AUDIO_FLUSH, NULL) == -1)
return -1;
return 0;
}
static int sun_set_sf(sample_format_t sf)
{
struct audio_info ainf;
AUDIO_INITINFO(&ainf);
sun_reset();
sun_sf = sf;
ainf.play.channels = sf_get_channels(sun_sf);
ainf.play.sample_rate = sf_get_rate(sun_sf);
ainf.play.pause = 0;
ainf.mode = AUMODE_PLAY;
switch (sf_get_bits(sun_sf)) {
case 16:
ainf.play.precision = 16;
if (sf_get_signed(sun_sf)) {
if (sf_get_bigendian(sun_sf))
ainf.play.encoding = AUDIO_ENCODING_SLINEAR_BE;
else
ainf.play.encoding = AUDIO_ENCODING_SLINEAR_LE;
} else {
if (sf_get_bigendian(sun_sf))
ainf.play.encoding = AUDIO_ENCODING_ULINEAR_BE;
else
ainf.play.encoding = AUDIO_ENCODING_ULINEAR_LE;
}
break;
case 8:
ainf.play.precision = 8;
if (sf_get_signed(sun_sf))
ainf.play.encoding = AUDIO_ENCODING_SLINEAR;
else
ainf.play.encoding = AUDIO_ENCODING_ULINEAR;
break;
default:
return -1;
}
if (ioctl(sun_fd, AUDIO_SETINFO, &ainf) == -1)
return -1;
if (ioctl(sun_fd, AUDIO_GETINFO, &ainf) == -1)
return -1;
/* FIXME: check if sample rate is supported */
return 0;
}
static int sun_device_exists(const char *dev)
{
struct stat s;
if (stat(dev, &s))
return 0;
return 1;
}
static int sun_init(void)
{
const char *audio_dev = "/dev/audio";
if (sun_audio_device != NULL) {
if (sun_device_exists(sun_audio_device))
return 0;
free(sun_audio_device);
sun_audio_device = NULL;
return -1;
}
if (sun_device_exists(audio_dev)) {
sun_audio_device = xstrdup(audio_dev);
return 0;
}
return -1;
}
static int sun_exit(void)
{
if (sun_audio_device != NULL) {
free(sun_audio_device);
sun_audio_device = NULL;
}
return 0;
}
static int sun_close(void)
{
if (sun_fd != -1) {
close(sun_fd);
sun_fd = -1;
}
return 0;
}
static int sun_open(sample_format_t sf, const channel_position_t *channel_map)
{
sun_fd = open(sun_audio_device, O_WRONLY);
if (sun_fd == -1)
return -1;
if (sun_set_sf(sf) == -1) {
sun_close();
return -1;
}
return 0;
}
static int sun_write(const char *buf, int cnt)
{
const char *t;
cnt /= 4;
cnt *= 4;
t = buf;
while (cnt > 0) {
int rc = write(sun_fd, buf, cnt);
if (rc == -1) {
if (errno == EINTR)
continue;
else
return rc;
}
buf += rc;
cnt -= rc;
}
return (buf - t);
}
static int sun_pause(void)
{
struct audio_info ainf;
AUDIO_INITINFO(&ainf);
ainf.play.pause = 1;
if (ioctl(sun_fd, AUDIO_SETINFO, &ainf) == -1)
return -1;
return 0;
}
static int sun_unpause(void)
{
struct audio_info ainf;
AUDIO_INITINFO(&ainf);
ainf.play.pause = 0;
if (ioctl(sun_fd, AUDIO_SETINFO, &ainf) == -1)
return -1;
return 0;
}
static int sun_buffer_space(void)
{
struct audio_info ainf;
int sp;
AUDIO_INITINFO(&ainf);
if (ioctl(sun_fd, AUDIO_GETINFO, &ainf) == -1)
return -1;
sp = ainf.play.buffer_size;
return sp;
}
static int op_sun_set_device(const char *val)
{
free(sun_audio_device);
sun_audio_device = xstrdup(val);
return 0;
}
static int op_sun_get_device(char **val)
{
if (sun_audio_device)
*val = xstrdup(sun_audio_device);
return 0;
}
const struct output_plugin_ops op_pcm_ops = {
.init = sun_init,
.exit = sun_exit,
.open = sun_open,
.close = sun_close,
.write = sun_write,
.pause = sun_pause,
.unpause = sun_unpause,
.buffer_space = sun_buffer_space,
};
const struct output_plugin_opt op_pcm_options[] = {
OPT(op_sun, device),
{ NULL },
};
const int op_priority = 0;
const unsigned op_abi_version = OP_ABI_VERSION;

309
op/waveout.c Normal file
View File

@@ -0,0 +1,309 @@
/*
* Copyright 2008-2013 Various Authors
* Copyright 2007 dnk <dnk@bjum.net>
*
* Based on oss.c
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "../op.h"
#include "../sf.h"
#include "../utils.h"
#include "../xmalloc.h"
#include "../debug.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
static HWAVEOUT wave_out;
static sample_format_t waveout_sf;
static int buffer_size = 4096;
static int buffer_count = 12;
static WAVEHDR *buffers;
static int buffer_idx;
static int buffers_free;
#define FRAME_SIZE_ALIGN(x) \
(((x) / sf_get_frame_size(waveout_sf)) * sf_get_frame_size(waveout_sf))
static void waveout_error(const char *name, int rc)
{
const char *errstr = "UNKNOWN";
switch (rc) {
case MMSYSERR_ALLOCATED: errstr = "MMSYSERR_ALLOCATED"; break;
case MMSYSERR_INVALHANDLE: errstr = "MMSYSERR_INVALHANDLE"; break;
case MMSYSERR_NODRIVER: errstr = "MMSYSERR_NODRIVER"; break;
case MMSYSERR_BADDEVICEID: errstr = "MMSYSERR_BADDEVICEID"; break;
case MMSYSERR_NOMEM: errstr = "MMSYSERR_NOMEM"; break;
case MMSYSERR_NOTSUPPORTED: errstr = "MMSYSERR_NOTSUPPORTED"; break;
case WAVERR_STILLPLAYING: errstr = "WAVERR_STILLPLAYING"; break;
case WAVERR_UNPREPARED: errstr = "WAVERR_UNPREPARED"; break;
case WAVERR_BADFORMAT: errstr = "WAVERR_BADFORMAT"; break;
case WAVERR_SYNC: errstr = "WAVERR_SYNC"; break;
}
d_print("%s returned error %s (%d)\n", name, errstr, rc);
}
static void clean_buffers(void)
{
int i;
/* mark used buffers clean */
for (i = 0; i < buffer_count; i++) {
WAVEHDR *hdr = &buffers[(buffer_idx + i) % buffer_count];
if (!(hdr->dwFlags & WHDR_DONE))
break;
buffers_free++;
waveOutUnprepareHeader(wave_out, hdr, sizeof(WAVEHDR));
hdr->dwFlags = 0;
}
}
static int waveout_open(sample_format_t sf, const channel_position_t *channel_map)
{
WAVEFORMATEX format = {
.cbSize = sizeof(format),
.wFormatTag = WAVE_FORMAT_PCM,
.nChannels = sf_get_channels(sf),
.nSamplesPerSec = sf_get_rate(sf),
.wBitsPerSample = sf_get_bits(sf),
.nAvgBytesPerSec = sf_get_second_size(sf),
.nBlockAlign = sf_get_frame_size(sf)
};
int rc, i;
/* WAVEFORMATEX does not support channels > 2, waveOutWrite() wants little endian signed PCM */
if (sf_get_bigendian(sf) || !sf_get_signed(sf) || sf_get_channels(sf) > 2) {
return -OP_ERROR_SAMPLE_FORMAT;
}
rc = waveOutOpen(&wave_out, WAVE_MAPPER, &format, 0, 0, CALLBACK_NULL);
if (rc != MMSYSERR_NOERROR) {
switch (rc) {
case MMSYSERR_ALLOCATED:
errno = EBUSY;
return -OP_ERROR_ERRNO;
case MMSYSERR_BADDEVICEID:
case MMSYSERR_NODRIVER:
errno = ENODEV;
return -OP_ERROR_ERRNO;
case MMSYSERR_NOMEM:
errno = ENOMEM;
return -OP_ERROR_ERRNO;
case WAVERR_BADFORMAT:
return -OP_ERROR_SAMPLE_FORMAT;
}
return -OP_ERROR_INTERNAL;
}
/* reset buffers */
for (i = 0; i < buffer_count; i++) {
buffers[i].dwFlags = 0;
}
buffer_idx = 0;
buffers_free = buffer_count;
waveout_sf = sf;
return 0;
}
static int waveout_close(void)
{
int rc;
waveOutReset(wave_out);
clean_buffers();
rc = waveOutClose(wave_out);
if (rc != MMSYSERR_NOERROR) {
waveout_error("waveOutClose", rc);
return -1;
}
wave_out = NULL;
return 0;
}
static int waveout_init(void)
{
WAVEHDR *hdr;
int i;
/* create buffers */
buffers = xnew(WAVEHDR, buffer_count);
for (i = 0; i < buffer_count; i++) {
hdr = &buffers[i];
memset(hdr, 0, sizeof(WAVEHDR));
hdr->lpData = xmalloc(buffer_size);
}
return 0;
}
static int waveout_exit(void)
{
int i;
for (i = 0; i < buffer_count; i++) {
free(buffers[i].lpData);
}
free(buffers);
buffers = NULL;
return 0;
}
static int waveout_write(const char *buffer, int count)
{
int written = 0;
int len, rc;
count = FRAME_SIZE_ALIGN(count);
clean_buffers();
while (count > 0) {
WAVEHDR *hdr = &buffers[buffer_idx];
if (hdr->dwFlags != 0) {
/* no free buffers */
break;
}
len = FRAME_SIZE_ALIGN(min_i(count, buffer_size));
hdr->dwBufferLength = len;
memcpy(hdr->lpData, buffer + written, len);
rc = waveOutPrepareHeader(wave_out, hdr, sizeof(WAVEHDR));
if (rc != MMSYSERR_NOERROR) {
waveout_error("waveOutPrepareHeader", rc);
break;
}
rc = waveOutWrite(wave_out, hdr, sizeof(WAVEHDR));
if (rc != MMSYSERR_NOERROR) {
waveOutUnprepareHeader(wave_out, hdr, sizeof(WAVEHDR));
hdr->dwFlags = 0;
waveout_error("waveOutWrite", rc);
break;
}
written += len;
count -= len;
buffer_idx = (buffer_idx + 1) % buffer_count;
buffers_free--;
}
return written;
}
static int waveout_pause(void)
{
if (waveOutPause(wave_out) != MMSYSERR_NOERROR)
return -1;
return 0;
}
static int waveout_unpause(void)
{
if (waveOutRestart(wave_out) != MMSYSERR_NOERROR)
return -1;
return 0;
}
static int waveout_buffer_space(void)
{
clean_buffers();
return buffers_free * FRAME_SIZE_ALIGN(buffer_size);
}
static int waveout_set_buffer_common(long int val, long int *dst)
{
int reinit = 0;
if (buffers) {
waveout_exit();
reinit = 1;
}
*dst = val;
if (reinit) {
waveout_init();
}
return 0;
}
static int waveout_set_buffer_size(const char *val)
{
long int ival;
if (str_to_int(val, &ival) || ival < 4096 || ival > 65536) {
errno = EINVAL;
return -OP_ERROR_ERRNO;
}
return waveout_set_buffer_common(ival, &buffer_size);
}
static int waveout_set_buffer_count(const char *val)
{
long int ival;
if (str_to_int(val, &ival) || ival < 2 || ival > 64) {
errno = EINVAL;
return -OP_ERROR_ERRNO;
}
return waveout_set_buffer_common(ival, &buffer_count);
}
static int waveout_get_buffer_size(char **val)
{
*val = xnew(char, 22);
snprintf(*val, 22, "%d", buffer_size);
return 0;
}
static int waveout_get_buffer_count(char **val)
{
*val = xnew(char, 22);
snprintf(*val, 22, "%d", buffer_count);
return 0;
}
const struct output_plugin_ops op_pcm_ops = {
.init = waveout_init,
.exit = waveout_exit,
.open = waveout_open,
.close = waveout_close,
.write = waveout_write,
.pause = waveout_pause,
.unpause = waveout_unpause,
.buffer_space = waveout_buffer_space,
};
const struct output_plugin_opt op_pcm_options[] = {
OPT(waveout, buffer_size),
OPT(waveout, buffer_count),
{ NULL },
};
const int op_priority = 0;
const unsigned op_abi_version = OP_ABI_VERSION;