push
This commit is contained in:
920
op/aaudio.c
Normal file
920
op/aaudio.c
Normal 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, ¤tState, 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
368
op/alsa.c
Normal 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
308
op/ao.c
Normal 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
123
op/arts.c
Normal 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
984
op/coreaudio.c
Normal 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
649
op/jack.c
Normal 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
292
op/mixer_alsa.c
Normal 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
244
op/mixer_oss.c
Normal 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
283
op/mixer_sun.c
Normal 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
285
op/oss.c
Normal 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
651
op/pulse.c
Normal 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
369
op/roar.c
Normal 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
251
op/sndio.c
Normal 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
251
op/sun.c
Normal 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
309
op/waveout.c
Normal 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;
|
||||
Reference in New Issue
Block a user