mirror of
https://github.com/openharmony/third_party_tinyalsa.git
synced 2026-07-01 20:14:06 -04:00
Merge pull request #137 from codeauroraforum/plugin-support
Plugin support
This commit is contained in:
@@ -4,7 +4,12 @@ cc_library {
|
||||
vendor_available: true,
|
||||
srcs: [
|
||||
"src/mixer.c",
|
||||
"src/mixer_hw.c",
|
||||
"src/mixer_plugin.c",
|
||||
"src/pcm.c",
|
||||
"src/pcm_hw.c",
|
||||
"src/pcm_plugin.c",
|
||||
"src/snd_card_plugin.c",
|
||||
],
|
||||
cflags: ["-Werror", "-Wno-macro-redefined"],
|
||||
export_include_dirs: ["include"],
|
||||
@@ -15,6 +20,8 @@ cc_library {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
|
||||
system_shared_libs: ["libc", "libdl"],
|
||||
}
|
||||
|
||||
cc_binary {
|
||||
|
||||
+16
-2
@@ -2,24 +2,37 @@ cmake_minimum_required(VERSION 3.0.2)
|
||||
|
||||
project("TinyALSA" C)
|
||||
|
||||
option(TINYALSA_USES_PLUGINS "Whether or not to build with plugin support" OFF)
|
||||
|
||||
if (TINYALSA_USES_PLUGINS)
|
||||
set (plugin_opt -DTINYALSA_USES_PLUGINS=1)
|
||||
endif (TINYALSA_USES_PLUGINS)
|
||||
|
||||
set (HDRS
|
||||
"include/tinyalsa/attributes.h"
|
||||
"include/tinyalsa/version.h"
|
||||
"include/tinyalsa/asoundlib.h"
|
||||
"include/tinyalsa/pcm.h"
|
||||
"include/tinyalsa/plugin.h"
|
||||
"include/tinyalsa/mixer.h")
|
||||
|
||||
set (SRCS
|
||||
"src/pcm.c"
|
||||
"src/mixer.c")
|
||||
"src/pcm_hw.c"
|
||||
"src/pcm_plugin.c"
|
||||
"src/snd_card_plugin.c"
|
||||
"src/mixer.c"
|
||||
"src/mixer_hw.c"
|
||||
"src/mixer_plugin.c")
|
||||
|
||||
add_library("tinyalsa" ${HDRS} ${SRCS})
|
||||
target_compile_options("tinyalsa" PRIVATE -Wall -Wextra -Werror -Wfatal-errors)
|
||||
target_compile_options("tinyalsa" PRIVATE -Wall -Wextra -Werror -Wfatal-errors ${plugin_opt})
|
||||
target_include_directories("tinyalsa" PRIVATE "include")
|
||||
|
||||
macro(ADD_EXAMPLE EXAMPLE)
|
||||
add_executable(${EXAMPLE} ${ARGN})
|
||||
target_link_libraries(${EXAMPLE} "tinyalsa")
|
||||
target_link_libraries(${EXAMPLE} "dl")
|
||||
target_include_directories(${EXAMPLE} PRIVATE "include")
|
||||
endmacro(ADD_EXAMPLE EXAMPLE)
|
||||
|
||||
@@ -29,6 +42,7 @@ add_example("pcm-writei" "examples/pcm-writei.c")
|
||||
macro(ADD_UTIL UTIL)
|
||||
add_executable(${UTIL} ${ARGN})
|
||||
target_link_libraries(${UTIL} "tinyalsa")
|
||||
target_link_libraries(${UTIL} "dl")
|
||||
target_compile_options(${UTIL} PRIVATE -Wall -Wextra -Werror -Wfatal-errors)
|
||||
target_include_directories(${UTIL} PRIVATE "include")
|
||||
endmacro(ADD_UTIL UTIL)
|
||||
|
||||
+2
-2
@@ -11,9 +11,9 @@ EXAMPLES += pcm-writei
|
||||
.PHONY: all
|
||||
all: $(EXAMPLES)
|
||||
|
||||
pcm-readi: pcm-readi.c -ltinyalsa
|
||||
pcm-readi: pcm-readi.c -ltinyalsa -ldl
|
||||
|
||||
pcm-writei: pcm-writei.c -ltinyalsa
|
||||
pcm-writei: pcm-writei.c -ltinyalsa -ldl
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
/* plugin.h
|
||||
** Copyright (c) 2019-2020, The Linux Foundation.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above
|
||||
** copyright notice, this list of conditions and the following
|
||||
** disclaimer in the documentation and/or other materials provided
|
||||
** with the distribution.
|
||||
** * Neither the name of The Linux Foundation nor the names of its
|
||||
** contributors may be used to endorse or promote products derived
|
||||
** from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
|
||||
** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||
** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**/
|
||||
|
||||
#ifndef TINYALSA_PLUGIN_H
|
||||
#define TINYALSA_PLUGIN_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <sound/asound.h>
|
||||
|
||||
/* static initializers */
|
||||
|
||||
#define SND_VALUE_ENUM(etexts, eitems) \
|
||||
{.texts = etexts, .items = eitems}
|
||||
|
||||
#define SND_VALUE_BYTES(csize) \
|
||||
{.size = csize }
|
||||
|
||||
#define SND_VALUE_INTEGER(icount, imin, imax, istep) \
|
||||
{.count = icount, .min = imin, .max = imax, .step = istep }
|
||||
|
||||
#define SND_VALUE_TLV_BYTES(csize, cget, cput) \
|
||||
{.size = csize, .get = cget, .put = cput }
|
||||
|
||||
/* pointer based initializers */
|
||||
#define INIT_SND_CONTROL_INTEGER(c, cname, cget, cput, cint, pval, pdata) \
|
||||
{ \
|
||||
c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \
|
||||
c->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; \
|
||||
c->type = SNDRV_CTL_ELEM_TYPE_INTEGER; \
|
||||
c->name = cname; c->value = &cint; c->get = cget; c->put = cput; \
|
||||
c->private_value = pval; c->private_data = pdata; \
|
||||
}
|
||||
|
||||
#define INIT_SND_CONTROL_BYTES(c, cname, cget, cput, cint, pval, pdata) \
|
||||
{ \
|
||||
c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \
|
||||
c->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; \
|
||||
c->type = SNDRV_CTL_ELEM_TYPE_BYTES; \
|
||||
c->name = cname; c->value = &cint; c->get = cget; c->put = cput; \
|
||||
c->private_value = pval; c->private_data = pdata; \
|
||||
}
|
||||
|
||||
#define INIT_SND_CONTROL_ENUM(c, cname, cget, cput, cenum, pval, pdata) \
|
||||
{ \
|
||||
c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \
|
||||
c->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; \
|
||||
c->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; \
|
||||
c->name = cname; c->value = cenum; c->get = cget; c->put = cput; \
|
||||
c->private_value = pval; c->private_data = pdata; \
|
||||
}
|
||||
|
||||
#define INIT_SND_CONTROL_TLV_BYTES(c, cname, cbytes, priv_val, priv_data) \
|
||||
{ \
|
||||
c->iface = SNDRV_CTL_ELEM_IFACE_MIXER; \
|
||||
c->access = SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE; \
|
||||
c->type = SNDRV_CTL_ELEM_TYPE_BYTES; \
|
||||
c->name = cname; c->value = &cbytes; \
|
||||
c->private_value = priv_val; c->private_data = priv_data; \
|
||||
}
|
||||
|
||||
struct mixer_plugin;
|
||||
struct pcm_plugin;
|
||||
struct snd_node;
|
||||
|
||||
/** Operations that are required to be registered by the plugin.
|
||||
* @ingroup libtinyalsa-pcm
|
||||
*/
|
||||
struct pcm_plugin_ops {
|
||||
/** open the pcm plugin */
|
||||
int (*open) (struct pcm_plugin **plugin, unsigned int card,
|
||||
unsigned int device, unsigned int flags);
|
||||
/** close the pcm plugin */
|
||||
int (*close) (struct pcm_plugin *plugin);
|
||||
/** Set the PCM hardware parameters to the plugin */
|
||||
int (*hw_params) (struct pcm_plugin *plugin,
|
||||
struct snd_pcm_hw_params *params);
|
||||
/** Set the PCM software parameters to the plugin */
|
||||
int (*sw_params) (struct pcm_plugin *plugin,
|
||||
struct snd_pcm_sw_params *params);
|
||||
/** Synchronize the pointer */
|
||||
int (*sync_ptr) (struct pcm_plugin *plugin,
|
||||
struct snd_pcm_sync_ptr *sync_ptr);
|
||||
/** Write frames to plugin to be rendered to output */
|
||||
int (*writei_frames) (struct pcm_plugin *plugin,
|
||||
struct snd_xferi *x);
|
||||
/** Read frames from plugin captured from input */
|
||||
int (*readi_frames) (struct pcm_plugin *plugin,
|
||||
struct snd_xferi *x);
|
||||
/** Obtain the timestamp for the PCM */
|
||||
int (*ttstamp) (struct pcm_plugin *plugin,
|
||||
int *tstamp);
|
||||
/** Prepare the plugin for data transfer */
|
||||
int (*prepare) (struct pcm_plugin *plugin);
|
||||
/** Start data transfer from/to the plugin */
|
||||
int (*start) (struct pcm_plugin *plugin);
|
||||
/** Drop pcm frames */
|
||||
int (*drop) (struct pcm_plugin *plugin);
|
||||
/** Any custom or alsa specific ioctl implementation */
|
||||
int (*ioctl) (struct pcm_plugin *plugin,
|
||||
int cmd, void *arg);
|
||||
};
|
||||
|
||||
/** Minimum and maximum values for hardware parameter constraints.
|
||||
* @ingroup libtinyalsa-pcm
|
||||
*/
|
||||
struct pcm_plugin_min_max {
|
||||
/** Minimum value for the hardware parameter */
|
||||
unsigned int min;
|
||||
/** Maximum value for the hardware parameter */
|
||||
unsigned int max;
|
||||
};
|
||||
|
||||
/** Encapsulate the hardware parameter constraints
|
||||
* @ingroup libtinyalsa-pcm
|
||||
*/
|
||||
struct pcm_plugin_hw_constraints {
|
||||
/** Value for SNDRV_PCM_HW_PARAM_ACCESS param */
|
||||
uint64_t access;
|
||||
/** Value for SNDRV_PCM_HW_PARAM_FORMAT param.
|
||||
* As of this implementation ALSA supports 52 formats */
|
||||
uint64_t format;
|
||||
/** Value for SNDRV_PCM_HW_PARAM_SAMPLE_BITS param */
|
||||
struct pcm_plugin_min_max bit_width;
|
||||
/** Value for SNDRV_PCM_HW_PARAM_CHANNELS param */
|
||||
struct pcm_plugin_min_max channels;
|
||||
/** Value for SNDRV_PCM_HW_PARAM_RATE param */
|
||||
struct pcm_plugin_min_max rate;
|
||||
/** Value for SNDRV_PCM_HW_PARAM_PERIODS param */
|
||||
struct pcm_plugin_min_max periods;
|
||||
/** Value for SNDRV_PCM_HW_PARAM_PERIOD_BYTES param */
|
||||
struct pcm_plugin_min_max period_bytes;
|
||||
};
|
||||
|
||||
struct pcm_plugin {
|
||||
/** Card number for the pcm device */
|
||||
unsigned int card;
|
||||
/** device number for the pcm device */
|
||||
unsigned int device;
|
||||
/** pointer to the contraints registered by the plugin */
|
||||
struct pcm_plugin_hw_constraints *constraints;
|
||||
/** Indicates read/write mode, etc.. */
|
||||
int mode;
|
||||
/* Pointer to hold the plugin's private data */
|
||||
void *priv;
|
||||
/* Tracks the plugin state */
|
||||
unsigned int state;
|
||||
};
|
||||
|
||||
typedef void (*mixer_event_callback)(struct mixer_plugin *);
|
||||
|
||||
struct mixer_plugin_ops {
|
||||
int (*open) (struct mixer_plugin **plugin, unsigned int card);
|
||||
void (*close) (struct mixer_plugin **plugin);
|
||||
int (*subscribe_events) (struct mixer_plugin *plugin,
|
||||
mixer_event_callback event_cb);
|
||||
ssize_t (*read_event) (struct mixer_plugin *plugin,
|
||||
struct snd_ctl_event *ev, size_t size);
|
||||
};
|
||||
|
||||
struct snd_control {
|
||||
snd_ctl_elem_iface_t iface;
|
||||
unsigned int access;
|
||||
const char *name;
|
||||
snd_ctl_elem_type_t type;
|
||||
void *value;
|
||||
int (*get) (struct mixer_plugin *plugin,
|
||||
struct snd_control *control,
|
||||
struct snd_ctl_elem_value *ev);
|
||||
int (*put) (struct mixer_plugin *plugin,
|
||||
struct snd_control *control,
|
||||
struct snd_ctl_elem_value *ev);
|
||||
uint32_t private_value;
|
||||
void *private_data;
|
||||
};
|
||||
|
||||
struct mixer_plugin {
|
||||
unsigned int card;
|
||||
void *priv;
|
||||
|
||||
int eventfd;
|
||||
int subscribed;
|
||||
int event_cnt;
|
||||
|
||||
struct snd_control *controls;
|
||||
unsigned int num_controls;
|
||||
};
|
||||
|
||||
struct snd_value_enum {
|
||||
unsigned int items;
|
||||
char **texts;
|
||||
};
|
||||
|
||||
struct snd_value_bytes {
|
||||
unsigned int size;
|
||||
};
|
||||
|
||||
struct snd_value_tlv_bytes {
|
||||
unsigned int size;
|
||||
int (*get) (struct mixer_plugin *plugin,
|
||||
struct snd_control *control,
|
||||
struct snd_ctl_tlv *tlv);
|
||||
int (*put) (struct mixer_plugin *plugin,
|
||||
struct snd_control *control,
|
||||
struct snd_ctl_tlv *tlv);
|
||||
};
|
||||
|
||||
struct snd_value_int {
|
||||
unsigned int count;
|
||||
int min;
|
||||
int max;
|
||||
int step;
|
||||
};
|
||||
|
||||
/** Operations defined by the plugin.
|
||||
* */
|
||||
struct snd_node_ops {
|
||||
/** Function pointer to get card definition */
|
||||
void* (*open_card)(unsigned int card);
|
||||
/** Function pointer to release card definition */
|
||||
void (*close_card)(void *card);
|
||||
/** Get interger type properties from device definition */
|
||||
int (*get_int)(void *node, const char *prop, int *val);
|
||||
/** Get string type properties from device definition */
|
||||
int (*get_str)(void *node, const char *prop, char **val);
|
||||
/** Function pointer to get mixer definition */
|
||||
void* (*get_mixer)(void *card);
|
||||
/** Function pointer to get PCM definition */
|
||||
void* (*get_pcm)(void *card, unsigned int id);
|
||||
/** Reserved for other nodes such as compress */
|
||||
void* reserved[4];
|
||||
};
|
||||
|
||||
#endif /* end of TINYALSA_PLUGIN_H */
|
||||
+8
-2
@@ -4,11 +4,17 @@ project ('tinyalsa', 'c',
|
||||
|
||||
tinyalsa_includes = include_directories('.', 'include')
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
|
||||
# Dependency on libdl
|
||||
dl_dep = cc.find_library('dl')
|
||||
|
||||
tinyalsa = library('tinyalsa',
|
||||
'src/mixer.c', 'src/pcm.c',
|
||||
'src/mixer.c', 'src/pcm.c', 'src/pcm_hw.c', 'src/pcm_plugin.c', 'src/snd_card_plugin.c', 'src/mixer_hw.c', 'src/mixer_plugin.c',
|
||||
include_directories: tinyalsa_includes,
|
||||
version: meson.project_version(),
|
||||
install: true)
|
||||
install: true,
|
||||
dependencies: dl_dep)
|
||||
|
||||
# For use as a Meson subproject
|
||||
tinyalsa_dep = declare_dependency(link_with: tinyalsa,
|
||||
|
||||
+11
-1
@@ -15,7 +15,7 @@ INCLUDE_DIRS = -I ../include
|
||||
override CFLAGS := $(WARNINGS) $(INCLUDE_DIRS) -fPIC $(CFLAGS)
|
||||
|
||||
VPATH = ../include/tinyalsa
|
||||
OBJECTS = limits.o mixer.o pcm.o
|
||||
OBJECTS = limits.o mixer.o pcm.o pcm_plugin.o pcm_hw.o snd_card_plugin.o mixer_plugin.o mixer_hw.o
|
||||
|
||||
LIBVERSION_MAJOR = $(TINYALSA_VERSION_MAJOR)
|
||||
LIBVERSION = $(TINYALSA_VERSION)
|
||||
@@ -25,10 +25,20 @@ all: libtinyalsa.a libtinyalsa.so
|
||||
|
||||
pcm.o: pcm.c pcm.h
|
||||
|
||||
pcm_plugin.o: pcm_plugin.c pcm_io.h
|
||||
|
||||
pcm_hw.o: pcm_hw.c pcm_io.h
|
||||
|
||||
limits.o: limits.c limits.h
|
||||
|
||||
mixer.o: mixer.c mixer.h
|
||||
|
||||
snd_card_plugin.o: snd_card_plugin.c snd_card_plugin.h
|
||||
|
||||
mixer_plugin.o: mixer_plugin.c mixer_io.h
|
||||
|
||||
mixer_hw.o: mixer_hw.c mixer_io.h
|
||||
|
||||
libtinyalsa.a: $(OBJECTS)
|
||||
$(AR) $(ARFLAGS) $@ $^
|
||||
|
||||
|
||||
+318
-74
@@ -29,6 +29,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
@@ -57,6 +58,9 @@
|
||||
#include <sound/asound.h>
|
||||
|
||||
#include <tinyalsa/mixer.h>
|
||||
#include <tinyalsa/plugin.h>
|
||||
|
||||
#include "mixer_io.h"
|
||||
|
||||
/** A mixer control.
|
||||
* @ingroup libtinyalsa-mixer
|
||||
@@ -68,6 +72,21 @@ struct mixer_ctl {
|
||||
struct snd_ctl_elem_info info;
|
||||
/** A list of string representations of enumerated values (only valid for enumerated controls) */
|
||||
char **ename;
|
||||
/** Pointer to the group that the control belongs to */
|
||||
struct mixer_ctl_group *grp;
|
||||
};
|
||||
|
||||
struct mixer_ctl_group {
|
||||
/** A continuous array of mixer controls */
|
||||
struct mixer_ctl *ctl;
|
||||
/** The number of mixer controls */
|
||||
unsigned int count;
|
||||
/** The number of events associated with this group */
|
||||
unsigned int event_cnt;
|
||||
/** The operations corresponding to this group */
|
||||
const struct mixer_ops *ops;
|
||||
/** Private data for storing group specific data */
|
||||
void *data;
|
||||
};
|
||||
|
||||
/** A mixer handle.
|
||||
@@ -78,10 +97,15 @@ struct mixer {
|
||||
int fd;
|
||||
/** Card information */
|
||||
struct snd_ctl_card_info card_info;
|
||||
/** A continuous array of mixer controls */
|
||||
struct mixer_ctl *ctl;
|
||||
/** The number of mixer controls */
|
||||
unsigned int count;
|
||||
/* Hardware (kernel interface) mixer control group */
|
||||
struct mixer_ctl_group *h_grp;
|
||||
/* Virtual (Plugin interface) mixer control group */
|
||||
struct mixer_ctl_group *v_grp;
|
||||
/* Total count of mixer controls from both groups */
|
||||
unsigned int total_count;
|
||||
/* Flag to track if card information is already retrieved */
|
||||
bool is_card_info_retrieved;
|
||||
|
||||
};
|
||||
|
||||
static void mixer_cleanup_control(struct mixer_ctl *ctl)
|
||||
@@ -96,25 +120,40 @@ static void mixer_cleanup_control(struct mixer_ctl *ctl)
|
||||
}
|
||||
}
|
||||
|
||||
static void mixer_grp_close(struct mixer *mixer, struct mixer_ctl_group *grp)
|
||||
{
|
||||
unsigned int n;
|
||||
|
||||
if (!grp)
|
||||
return;
|
||||
|
||||
if (grp->ctl) {
|
||||
for (n = 0; n < grp->count; n++)
|
||||
mixer_cleanup_control(&grp->ctl[n]);
|
||||
free(grp->ctl);
|
||||
}
|
||||
|
||||
mixer->is_card_info_retrieved = false;
|
||||
}
|
||||
|
||||
/** Closes a mixer returned by @ref mixer_open.
|
||||
* @param mixer A mixer handle.
|
||||
* @ingroup libtinyalsa-mixer
|
||||
*/
|
||||
void mixer_close(struct mixer *mixer)
|
||||
{
|
||||
unsigned int n;
|
||||
|
||||
if (!mixer)
|
||||
return;
|
||||
|
||||
if (mixer->fd >= 0)
|
||||
close(mixer->fd);
|
||||
if (mixer->fd >= 0 && mixer->h_grp)
|
||||
mixer->h_grp->ops->close(mixer->h_grp->data);
|
||||
mixer_grp_close(mixer, mixer->h_grp);
|
||||
|
||||
if (mixer->ctl) {
|
||||
for (n = 0; n < mixer->count; n++)
|
||||
mixer_cleanup_control(&mixer->ctl[n]);
|
||||
free(mixer->ctl);
|
||||
}
|
||||
#ifdef TINYALSA_USES_PLUGINS
|
||||
if (mixer->v_grp)
|
||||
mixer->v_grp->ops->close(mixer->v_grp->data);
|
||||
mixer_grp_close(mixer, mixer->v_grp);
|
||||
#endif
|
||||
|
||||
free(mixer);
|
||||
|
||||
@@ -133,18 +172,17 @@ static void *mixer_realloc_z(void *ptr, size_t curnum, size_t newnum, size_t siz
|
||||
return newp;
|
||||
}
|
||||
|
||||
static int add_controls(struct mixer *mixer)
|
||||
static int add_controls(struct mixer *mixer, struct mixer_ctl_group *grp)
|
||||
{
|
||||
struct snd_ctl_elem_list elist;
|
||||
struct snd_ctl_elem_id *eid = NULL;
|
||||
struct mixer_ctl *ctl;
|
||||
int fd = mixer->fd;
|
||||
const unsigned int old_count = mixer->count;
|
||||
const unsigned int old_count = grp->count;
|
||||
unsigned int new_count;
|
||||
unsigned int n;
|
||||
|
||||
memset(&elist, 0, sizeof(elist));
|
||||
if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
|
||||
if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
|
||||
goto fail;
|
||||
|
||||
if (old_count == elist.count)
|
||||
@@ -153,12 +191,12 @@ static int add_controls(struct mixer *mixer)
|
||||
if (old_count > elist.count)
|
||||
return -1; /* driver has removed controls - this is bad */
|
||||
|
||||
ctl = mixer_realloc_z(mixer->ctl, old_count, elist.count,
|
||||
ctl = mixer_realloc_z(grp->ctl, old_count, elist.count,
|
||||
sizeof(struct mixer_ctl));
|
||||
if (!ctl)
|
||||
goto fail;
|
||||
|
||||
mixer->ctl = ctl;
|
||||
grp->ctl = ctl;
|
||||
|
||||
/* ALSA drivers are not supposed to remove or re-order controls that
|
||||
* have already been created so we know that any new controls must
|
||||
@@ -174,18 +212,21 @@ static int add_controls(struct mixer *mixer)
|
||||
|
||||
elist.pids = eid;
|
||||
|
||||
if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
|
||||
if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
|
||||
goto fail;
|
||||
|
||||
for (n = old_count; n < new_count; n++) {
|
||||
struct snd_ctl_elem_info *ei = &mixer->ctl[n].info;
|
||||
struct snd_ctl_elem_info *ei = &grp->ctl[n].info;
|
||||
ei->id.numid = eid[n - old_count].numid;
|
||||
if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0)
|
||||
if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0)
|
||||
goto fail_extend;
|
||||
ctl[n].mixer = mixer;
|
||||
ctl[n].grp = grp;
|
||||
}
|
||||
|
||||
mixer->count = new_count;
|
||||
grp->count = new_count;
|
||||
mixer->total_count += (new_count - old_count);
|
||||
|
||||
free(eid);
|
||||
return 0;
|
||||
|
||||
@@ -196,13 +237,69 @@ fail_extend:
|
||||
*/
|
||||
mixer_cleanup_control(&ctl[n]);
|
||||
|
||||
mixer->count = n; /* keep controls we successfully added */
|
||||
grp->count = n; /* keep controls we successfully added */
|
||||
mixer->total_count += (n - old_count);
|
||||
/* fall through... */
|
||||
fail:
|
||||
free(eid);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int mixer_grp_open(struct mixer *mixer, unsigned int card, bool is_hw)
|
||||
{
|
||||
struct mixer_ctl_group *grp = NULL;
|
||||
const struct mixer_ops *ops = NULL;
|
||||
void *data = NULL;
|
||||
int fd, ret;
|
||||
|
||||
grp = calloc(1, sizeof(*grp));
|
||||
if (!grp)
|
||||
return -ENOMEM;
|
||||
|
||||
if (is_hw) {
|
||||
mixer->fd = -1;
|
||||
fd = mixer_hw_open(card, &data, &ops);
|
||||
if (fd < 0) {
|
||||
ret = fd;
|
||||
goto err_open;
|
||||
}
|
||||
mixer->fd = fd;
|
||||
mixer->h_grp = grp;
|
||||
}
|
||||
#ifdef TINYALSA_USES_PLUGINS
|
||||
else {
|
||||
ret = mixer_plugin_open(card, &data, &ops);
|
||||
if (ret < 0)
|
||||
goto err_open;
|
||||
mixer->v_grp = grp;
|
||||
}
|
||||
#endif
|
||||
grp->ops = ops;
|
||||
grp->data = data;
|
||||
|
||||
if (!mixer->is_card_info_retrieved) {
|
||||
ret = grp->ops->ioctl(data, SNDRV_CTL_IOCTL_CARD_INFO,
|
||||
&mixer->card_info);
|
||||
if (ret < 0)
|
||||
goto err_card_info;
|
||||
mixer->is_card_info_retrieved = true;
|
||||
}
|
||||
|
||||
ret = add_controls(mixer, grp);
|
||||
if (ret < 0)
|
||||
goto err_card_info;
|
||||
|
||||
return 0;
|
||||
|
||||
err_card_info:
|
||||
grp->ops->close(grp->data);
|
||||
|
||||
err_open:
|
||||
free(grp);
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
/** Opens a mixer for a given card.
|
||||
* @param card The card to open the mixer for.
|
||||
* @returns An initialized mixer handle.
|
||||
@@ -211,24 +308,20 @@ fail:
|
||||
struct mixer *mixer_open(unsigned int card)
|
||||
{
|
||||
struct mixer *mixer = NULL;
|
||||
int fd;
|
||||
char fn[256];
|
||||
|
||||
snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card);
|
||||
fd = open(fn, O_RDWR);
|
||||
if (fd < 0)
|
||||
return 0;
|
||||
int h_status, v_status = -1;
|
||||
|
||||
mixer = calloc(1, sizeof(*mixer));
|
||||
if (!mixer)
|
||||
goto fail;
|
||||
|
||||
if (ioctl(fd, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info) < 0)
|
||||
goto fail;
|
||||
h_status = mixer_grp_open(mixer, card, true);
|
||||
|
||||
mixer->fd = fd;
|
||||
#ifdef TINYALSA_USES_PLUGINS
|
||||
v_status = mixer_grp_open(mixer, card, false);
|
||||
#endif
|
||||
|
||||
if (add_controls(mixer) != 0)
|
||||
/* both hw and virtual should fail for mixer_open to fail */
|
||||
if (h_status < 0 && v_status < 0)
|
||||
goto fail;
|
||||
|
||||
return mixer;
|
||||
@@ -236,8 +329,7 @@ struct mixer *mixer_open(unsigned int card)
|
||||
fail:
|
||||
if (mixer)
|
||||
mixer_close(mixer);
|
||||
else if (fd >= 0)
|
||||
close(fd);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -260,10 +352,27 @@ fail:
|
||||
*/
|
||||
int mixer_add_new_ctls(struct mixer *mixer)
|
||||
{
|
||||
int rc1 = 0, rc2 = 0;
|
||||
|
||||
if (!mixer)
|
||||
return 0;
|
||||
|
||||
return add_controls(mixer);
|
||||
/* add the h_grp controls */
|
||||
if (mixer->h_grp)
|
||||
rc1 = add_controls(mixer, mixer->h_grp);
|
||||
|
||||
#ifdef TINYALSA_USES_PLUGINS
|
||||
/* add the v_grp controls */
|
||||
if (mixer->v_grp)
|
||||
rc2 = add_controls(mixer, mixer->v_grp);
|
||||
#endif
|
||||
|
||||
if (rc1 < 0)
|
||||
return rc1;
|
||||
if (rc2 < 0)
|
||||
return rc2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Gets the name of the mixer's card.
|
||||
@@ -286,7 +395,7 @@ unsigned int mixer_get_num_ctls(const struct mixer *mixer)
|
||||
if (!mixer)
|
||||
return 0;
|
||||
|
||||
return mixer->count;
|
||||
return mixer->total_count;
|
||||
}
|
||||
|
||||
/** Gets the number of mixer controls, that go by a specified name, for a given mixer.
|
||||
@@ -297,6 +406,7 @@ unsigned int mixer_get_num_ctls(const struct mixer *mixer)
|
||||
*/
|
||||
unsigned int mixer_get_num_ctls_by_name(const struct mixer *mixer, const char *name)
|
||||
{
|
||||
struct mixer_ctl_group *grp;
|
||||
unsigned int n;
|
||||
unsigned int count = 0;
|
||||
struct mixer_ctl *ctl;
|
||||
@@ -304,11 +414,24 @@ unsigned int mixer_get_num_ctls_by_name(const struct mixer *mixer, const char *n
|
||||
if (!mixer)
|
||||
return 0;
|
||||
|
||||
ctl = mixer->ctl;
|
||||
if (mixer->h_grp) {
|
||||
grp = mixer->h_grp;
|
||||
ctl = grp->ctl;
|
||||
|
||||
for (n = 0; n < mixer->count; n++)
|
||||
if (!strcmp(name, (char*) ctl[n].info.id.name))
|
||||
count++;
|
||||
for (n = 0; n < grp->count; n++)
|
||||
if (!strcmp(name, (char*) ctl[n].info.id.name))
|
||||
count++;
|
||||
}
|
||||
#ifdef TINYALSA_USES_PLUGINS
|
||||
if (mixer->v_grp) {
|
||||
grp = mixer->v_grp;
|
||||
ctl = grp->ctl;
|
||||
|
||||
for (n = 0; n < grp->count; n++)
|
||||
if (!strcmp(name, (char*) ctl[n].info.id.name))
|
||||
count++;
|
||||
}
|
||||
#endif
|
||||
|
||||
return count;
|
||||
}
|
||||
@@ -322,9 +445,21 @@ unsigned int mixer_get_num_ctls_by_name(const struct mixer *mixer, const char *n
|
||||
*/
|
||||
int mixer_subscribe_events(struct mixer *mixer, int subscribe)
|
||||
{
|
||||
if (ioctl(mixer->fd, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0) {
|
||||
return -1;
|
||||
struct mixer_ctl_group *grp;
|
||||
|
||||
if (mixer->h_grp) {
|
||||
grp = mixer->h_grp;
|
||||
if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef TINYALSA_USES_PLUGINS
|
||||
if (mixer->v_grp) {
|
||||
grp = mixer->v_grp;
|
||||
if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0)
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -338,25 +473,76 @@ int mixer_subscribe_events(struct mixer *mixer, int subscribe)
|
||||
*/
|
||||
int mixer_wait_event(struct mixer *mixer, int timeout)
|
||||
{
|
||||
struct pollfd pfd;
|
||||
struct pollfd *pfd;
|
||||
struct mixer_ctl_group *grp;
|
||||
int count = 0, num_fds = 0, i;
|
||||
|
||||
pfd.fd = mixer->fd;
|
||||
pfd.events = POLLIN | POLLOUT | POLLERR | POLLNVAL;
|
||||
if (mixer->fd >= 0)
|
||||
num_fds++;
|
||||
|
||||
#ifdef TINYALSA_USES_PLUGINS
|
||||
if (mixer->v_grp)
|
||||
num_fds++;
|
||||
#endif
|
||||
|
||||
pfd = (struct pollfd *)calloc(sizeof(struct pollfd), num_fds);
|
||||
if (!pfd)
|
||||
return -ENOMEM;
|
||||
|
||||
if (mixer->fd >= 0) {
|
||||
pfd[count].fd = mixer->fd;
|
||||
pfd[count].events = POLLIN | POLLOUT | POLLERR | POLLNVAL;
|
||||
count++;
|
||||
}
|
||||
|
||||
#ifdef TINYALSA_USES_PLUGINS
|
||||
if (mixer->v_grp) {
|
||||
grp = mixer->v_grp;
|
||||
if (!grp->ops->get_poll_fd(grp->data, pfd, count)) {
|
||||
pfd[count].events = POLLIN | POLLERR | POLLNVAL;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!count)
|
||||
return 0;
|
||||
|
||||
for (;;) {
|
||||
int err;
|
||||
err = poll(&pfd, 1, timeout);
|
||||
err = poll(pfd, count, timeout);
|
||||
if (err < 0)
|
||||
return -errno;
|
||||
if (!err)
|
||||
return 0;
|
||||
if (pfd.revents & (POLLERR | POLLNVAL))
|
||||
return -EIO;
|
||||
if (pfd.revents & (POLLIN | POLLOUT))
|
||||
return 1;
|
||||
for (i = 0; i < count; i++) {
|
||||
if (pfd[i].revents & (POLLERR | POLLNVAL))
|
||||
return -EIO;
|
||||
if (pfd[i].revents & (POLLIN | POLLOUT)) {
|
||||
if ((i == 0) && mixer->fd >= 0) {
|
||||
grp = mixer->h_grp;
|
||||
grp->event_cnt++;
|
||||
}
|
||||
#ifdef TINYALSA_USES_PLUGINS
|
||||
else {
|
||||
grp = mixer->v_grp;
|
||||
grp->event_cnt++;
|
||||
}
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int mixer_grp_get_count(struct mixer_ctl_group *grp)
|
||||
{
|
||||
if (!grp)
|
||||
return 0;
|
||||
|
||||
return grp->count;
|
||||
}
|
||||
|
||||
/** Gets a mixer control handle, by the mixer control's id.
|
||||
* For non-const access, see @ref mixer_get_ctl
|
||||
* @param mixer An initialized mixer handle.
|
||||
@@ -366,8 +552,22 @@ int mixer_wait_event(struct mixer *mixer, int timeout)
|
||||
*/
|
||||
const struct mixer_ctl *mixer_get_ctl_const(const struct mixer *mixer, unsigned int id)
|
||||
{
|
||||
if (mixer && (id < mixer->count))
|
||||
return mixer->ctl + id;
|
||||
unsigned int h_count;
|
||||
|
||||
if (!mixer || (id >= mixer->total_count))
|
||||
return NULL;
|
||||
|
||||
h_count = mixer_grp_get_count(mixer->h_grp);
|
||||
|
||||
if (id < h_count)
|
||||
return mixer->h_grp->ctl + id;
|
||||
#ifdef TINYALSA_USES_PLUGINS
|
||||
else {
|
||||
unsigned int v_count = mixer_grp_get_count(mixer->v_grp);
|
||||
if ((id - h_count) < v_count)
|
||||
return mixer->v_grp->ctl + (id - h_count);
|
||||
}
|
||||
#endif
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -381,9 +581,22 @@ const struct mixer_ctl *mixer_get_ctl_const(const struct mixer *mixer, unsigned
|
||||
*/
|
||||
struct mixer_ctl *mixer_get_ctl(struct mixer *mixer, unsigned int id)
|
||||
{
|
||||
if (mixer && (id < mixer->count))
|
||||
return mixer->ctl + id;
|
||||
unsigned int h_count;
|
||||
|
||||
if (!mixer || (id >= mixer->total_count))
|
||||
return NULL;
|
||||
|
||||
h_count = mixer_grp_get_count(mixer->h_grp);
|
||||
|
||||
if (id < h_count)
|
||||
return mixer->h_grp->ctl + id;
|
||||
#ifdef TINYALSA_USES_PLUGINS
|
||||
else {
|
||||
unsigned int v_count = mixer_grp_get_count(mixer->v_grp);
|
||||
if ((id - h_count) < v_count)
|
||||
return mixer->v_grp->ctl + (id - h_count);
|
||||
}
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -410,19 +623,34 @@ struct mixer_ctl *mixer_get_ctl_by_name_and_index(struct mixer *mixer,
|
||||
const char *name,
|
||||
unsigned int index)
|
||||
{
|
||||
struct mixer_ctl_group *grp;
|
||||
unsigned int n;
|
||||
struct mixer_ctl *ctl;
|
||||
|
||||
if (!mixer)
|
||||
return NULL;
|
||||
|
||||
ctl = mixer->ctl;
|
||||
if (mixer->h_grp) {
|
||||
grp = mixer->h_grp;
|
||||
ctl = grp->ctl;
|
||||
|
||||
for (n = 0; n < mixer->count; n++)
|
||||
if (!strcmp(name, (char*) ctl[n].info.id.name))
|
||||
if (index-- == 0)
|
||||
return mixer->ctl + n;
|
||||
for (n = 0; n < grp->count; n++)
|
||||
if (!strcmp(name, (char*) ctl[n].info.id.name))
|
||||
if (index-- == 0)
|
||||
return ctl + n;
|
||||
}
|
||||
|
||||
#ifdef TINYALSA_USES_PLUGINS
|
||||
if (mixer->v_grp) {
|
||||
grp = mixer->v_grp;
|
||||
ctl = grp->ctl;
|
||||
|
||||
for (n = 0; n < grp->count; n++)
|
||||
if (!strcmp(name, (char*) ctl[n].info.id.name))
|
||||
if (index-- == 0)
|
||||
return ctl + n;
|
||||
}
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -433,7 +661,13 @@ struct mixer_ctl *mixer_get_ctl_by_name_and_index(struct mixer *mixer,
|
||||
*/
|
||||
void mixer_ctl_update(struct mixer_ctl *ctl)
|
||||
{
|
||||
ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_INFO, ctl->info);
|
||||
struct mixer_ctl_group *grp;
|
||||
|
||||
if (!ctl)
|
||||
return;
|
||||
|
||||
grp = ctl->grp;
|
||||
grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, &ctl->info);
|
||||
}
|
||||
|
||||
/** Checks the control for TLV Read/Write access.
|
||||
@@ -595,15 +829,17 @@ int mixer_ctl_set_percent(struct mixer_ctl *ctl, unsigned int id, int percent)
|
||||
*/
|
||||
int mixer_ctl_get_value(const struct mixer_ctl *ctl, unsigned int id)
|
||||
{
|
||||
struct mixer_ctl_group *grp;
|
||||
struct snd_ctl_elem_value ev;
|
||||
int ret;
|
||||
|
||||
if (!ctl || (id >= ctl->info.count))
|
||||
return -EINVAL;
|
||||
|
||||
grp = ctl->grp;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.id.numid = ctl->info.id.numid;
|
||||
ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
|
||||
ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
@@ -641,6 +877,7 @@ int mixer_ctl_get_value(const struct mixer_ctl *ctl, unsigned int id)
|
||||
*/
|
||||
int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count)
|
||||
{
|
||||
struct mixer_ctl_group *grp;
|
||||
struct snd_ctl_elem_value ev;
|
||||
int ret = 0;
|
||||
size_t size;
|
||||
@@ -650,6 +887,7 @@ int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count)
|
||||
if (!ctl || !count || !array)
|
||||
return -EINVAL;
|
||||
|
||||
grp = ctl->grp;
|
||||
total_count = ctl->info.count;
|
||||
|
||||
if ((ctl->info.type == SNDRV_CTL_ELEM_TYPE_BYTES) &&
|
||||
@@ -667,7 +905,7 @@ int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count)
|
||||
switch (ctl->info.type) {
|
||||
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
|
||||
case SNDRV_CTL_ELEM_TYPE_INTEGER:
|
||||
ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
|
||||
ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
size = sizeof(ev.value.integer.value[0]);
|
||||
@@ -687,7 +925,7 @@ int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count)
|
||||
return -ENOMEM;
|
||||
tlv->numid = ctl->info.id.numid;
|
||||
tlv->length = count;
|
||||
ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_TLV_READ, tlv);
|
||||
ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_TLV_READ, tlv);
|
||||
|
||||
source = tlv->tlv;
|
||||
memcpy(array, source, count);
|
||||
@@ -696,7 +934,7 @@ int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count)
|
||||
|
||||
return ret;
|
||||
} else {
|
||||
ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
|
||||
ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
size = sizeof(ev.value.bytes.data[0]);
|
||||
@@ -725,15 +963,17 @@ int mixer_ctl_get_array(const struct mixer_ctl *ctl, void *array, size_t count)
|
||||
*/
|
||||
int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value)
|
||||
{
|
||||
struct mixer_ctl_group *grp;
|
||||
struct snd_ctl_elem_value ev;
|
||||
int ret;
|
||||
|
||||
if (!ctl || (id >= ctl->info.count))
|
||||
return -EINVAL;
|
||||
|
||||
grp = ctl->grp;
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.id.numid = ctl->info.id.numid;
|
||||
ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
|
||||
ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
@@ -763,7 +1003,7 @@ int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
|
||||
return grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
|
||||
}
|
||||
|
||||
/** Sets the contents of a control's value array.
|
||||
@@ -778,6 +1018,7 @@ int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value)
|
||||
*/
|
||||
int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count)
|
||||
{
|
||||
struct mixer_ctl_group *grp;
|
||||
struct snd_ctl_elem_value ev;
|
||||
size_t size;
|
||||
void *dest;
|
||||
@@ -786,6 +1027,7 @@ int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count)
|
||||
if ((!ctl) || !count || !array)
|
||||
return -EINVAL;
|
||||
|
||||
grp = ctl->grp;
|
||||
total_count = ctl->info.count;
|
||||
|
||||
if ((ctl->info.type == SNDRV_CTL_ELEM_TYPE_BYTES) &&
|
||||
@@ -821,7 +1063,7 @@ int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count)
|
||||
tlv->length = count;
|
||||
memcpy(tlv->tlv, array, count);
|
||||
|
||||
ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_TLV_WRITE, tlv);
|
||||
ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_TLV_WRITE, tlv);
|
||||
free(tlv);
|
||||
|
||||
return ret;
|
||||
@@ -837,7 +1079,7 @@ int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count)
|
||||
|
||||
memcpy(dest, array, size * count);
|
||||
|
||||
return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
|
||||
return grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
|
||||
}
|
||||
|
||||
/** Gets the minimum value of an control.
|
||||
@@ -887,6 +1129,7 @@ unsigned int mixer_ctl_get_num_enums(const struct mixer_ctl *ctl)
|
||||
|
||||
int mixer_ctl_fill_enum_string(struct mixer_ctl *ctl)
|
||||
{
|
||||
struct mixer_ctl_group *grp = ctl->grp;
|
||||
struct snd_ctl_elem_info tmp;
|
||||
unsigned int m;
|
||||
char **enames;
|
||||
@@ -902,7 +1145,7 @@ int mixer_ctl_fill_enum_string(struct mixer_ctl *ctl)
|
||||
memset(&tmp, 0, sizeof(tmp));
|
||||
tmp.id.numid = ctl->info.id.numid;
|
||||
tmp.value.enumerated.item = m;
|
||||
if (ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_INFO, &tmp) < 0)
|
||||
if (grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_INFO, &tmp) < 0)
|
||||
goto fail;
|
||||
enames[m] = strdup(tmp.value.enumerated.name);
|
||||
if (!enames[m])
|
||||
@@ -949,6 +1192,7 @@ const char *mixer_ctl_get_enum_string(struct mixer_ctl *ctl,
|
||||
*/
|
||||
int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string)
|
||||
{
|
||||
struct mixer_ctl_group *grp;
|
||||
unsigned int i, num_enums;
|
||||
struct snd_ctl_elem_value ev;
|
||||
int ret;
|
||||
@@ -957,13 +1201,14 @@ int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string)
|
||||
mixer_ctl_fill_enum_string(ctl) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
grp = ctl->grp;
|
||||
num_enums = ctl->info.value.enumerated.items;
|
||||
for (i = 0; i < num_enums; i++) {
|
||||
if (!strcmp(string, ctl->ename[i])) {
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.value.enumerated.item[0] = i;
|
||||
ev.id.numid = ctl->info.id.numid;
|
||||
ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
|
||||
ret = grp->ops->ioctl(grp->data, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return 0;
|
||||
@@ -972,4 +1217,3 @@ int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string)
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/* mixer_hw.c
|
||||
** Copyright (c) 2019, The Linux Foundation.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above
|
||||
** copyright notice, this list of conditions and the following
|
||||
** disclaimer in the documentation and/or other materials provided
|
||||
** with the distribution.
|
||||
** * Neither the name of The Linux Foundation nor the names of its
|
||||
** contributors may be used to endorse or promote products derived
|
||||
** from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
|
||||
** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||
** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
#include <sound/asound.h>
|
||||
|
||||
#include "mixer_io.h"
|
||||
|
||||
/** Store the hardware (kernel interface) mixer data */
|
||||
struct mixer_hw_data {
|
||||
/* Card number for the mixer */
|
||||
unsigned int card;
|
||||
/* File descriptor of the mixer device node */
|
||||
int fd;
|
||||
};
|
||||
|
||||
static void mixer_hw_close(void *data)
|
||||
{
|
||||
struct mixer_hw_data *hw_data = data;
|
||||
|
||||
if (!hw_data)
|
||||
return;
|
||||
|
||||
if (hw_data->fd >= 0)
|
||||
close(hw_data->fd);
|
||||
|
||||
hw_data->fd = -1;
|
||||
free(hw_data);
|
||||
hw_data = NULL;
|
||||
}
|
||||
|
||||
static int mixer_hw_ioctl(void *data, unsigned int cmd, ...)
|
||||
{
|
||||
struct mixer_hw_data *hw_data = data;
|
||||
va_list ap;
|
||||
void *arg;
|
||||
|
||||
va_start(ap, cmd);
|
||||
arg = va_arg(ap, void *);
|
||||
va_end(ap);
|
||||
|
||||
return ioctl(hw_data->fd, cmd, arg);
|
||||
}
|
||||
|
||||
static const struct mixer_ops mixer_hw_ops = {
|
||||
.close = mixer_hw_close,
|
||||
.ioctl = mixer_hw_ioctl,
|
||||
};
|
||||
|
||||
int mixer_hw_open(unsigned int card, void **data,
|
||||
const struct mixer_ops **ops)
|
||||
{
|
||||
struct mixer_hw_data *hw_data;
|
||||
int fd;
|
||||
char fn[256];
|
||||
|
||||
snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card);
|
||||
fd = open(fn, O_RDWR);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
|
||||
hw_data = calloc(1, sizeof(*hw_data));
|
||||
if (!hw_data)
|
||||
return -1;
|
||||
|
||||
hw_data->card = card;
|
||||
hw_data->fd = fd;
|
||||
*data = hw_data;
|
||||
*ops = &mixer_hw_ops;
|
||||
|
||||
return fd;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/* mixer_io.h
|
||||
** Copyright (c) 2019, The Linux Foundation.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above
|
||||
** copyright notice, this list of conditions and the following
|
||||
** disclaimer in the documentation and/or other materials provided
|
||||
** with the distribution.
|
||||
** * Neither the name of The Linux Foundation nor the names of its
|
||||
** contributors may be used to endorse or promote products derived
|
||||
** from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
|
||||
** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||
** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**/
|
||||
|
||||
#ifndef TINYALSA_SRC_MIXER_H
|
||||
#define TINYALSA_SRC_MIXER_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sound/asound.h>
|
||||
|
||||
struct mixer_ops;
|
||||
|
||||
int mixer_hw_open(unsigned int card, void **data,
|
||||
const struct mixer_ops **ops);
|
||||
int mixer_plugin_open(unsigned int card, void **data,
|
||||
const struct mixer_ops **ops);
|
||||
|
||||
struct mixer_ops {
|
||||
void (*close) (void *data);
|
||||
int (*get_poll_fd) (void *data, struct pollfd *pfd, int count);
|
||||
ssize_t (*read_event) (void *data, struct snd_ctl_event *ev, size_t size);
|
||||
int (*ioctl) (void *data, unsigned int cmd, ...);
|
||||
};
|
||||
|
||||
#endif /* TINYALSA_SRC_MIXER_H */
|
||||
@@ -0,0 +1,480 @@
|
||||
/* mixer_plugin.c
|
||||
** Copyright (c) 2019, The Linux Foundation.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above
|
||||
** copyright notice, this list of conditions and the following
|
||||
** disclaimer in the documentation and/or other materials provided
|
||||
** with the distribution.
|
||||
** * Neither the name of The Linux Foundation nor the names of its
|
||||
** contributors may be used to endorse or promote products derived
|
||||
** from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
|
||||
** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||
** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**/
|
||||
|
||||
#include <tinyalsa/plugin.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <poll.h>
|
||||
#include <dlfcn.h>
|
||||
#include <string.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
#include <sound/asound.h>
|
||||
|
||||
#include "snd_card_plugin.h"
|
||||
#include "mixer_io.h"
|
||||
|
||||
/** Encapulates the mixer plugin specific data */
|
||||
struct mixer_plug_data {
|
||||
/** Card number associated with the plugin */
|
||||
int card;
|
||||
/** Device node for mixer */
|
||||
void *mixer_node;
|
||||
/** Pointer to the plugin's ops */
|
||||
const struct mixer_plugin_ops *ops;
|
||||
/** Pointer to plugin responsible to service the controls */
|
||||
struct mixer_plugin *plugin;
|
||||
/** Handle to the plugin library */
|
||||
void *dl_hdl;
|
||||
};
|
||||
|
||||
static int mixer_plug_get_elem_id(struct mixer_plug_data *plug_data,
|
||||
struct snd_ctl_elem_id *id, unsigned int offset)
|
||||
{
|
||||
struct mixer_plugin *plugin = plug_data->plugin;
|
||||
struct snd_control *ctl;
|
||||
|
||||
if (offset >= plugin->num_controls) {
|
||||
fprintf(stderr, "%s: invalid offset %u\n",
|
||||
__func__, offset);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ctl = plugin->controls + offset;
|
||||
id->numid = offset;
|
||||
id->iface = ctl->iface;
|
||||
|
||||
strncpy((char *)id->name, (char *)ctl->name,
|
||||
sizeof(id->name));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mixer_plug_info_enum(struct snd_control *ctl,
|
||||
struct snd_ctl_elem_info *einfo)
|
||||
{
|
||||
struct snd_value_enum *val = ctl->value;
|
||||
|
||||
einfo->count = 1;
|
||||
einfo->value.enumerated.items = val->items;
|
||||
|
||||
if (einfo->value.enumerated.item > val->items)
|
||||
return -EINVAL;
|
||||
|
||||
strncpy(einfo->value.enumerated.name,
|
||||
val->texts[einfo->value.enumerated.item],
|
||||
sizeof(einfo->value.enumerated.name));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mixer_plug_info_bytes(struct snd_control *ctl,
|
||||
struct snd_ctl_elem_info *einfo)
|
||||
{
|
||||
struct snd_value_bytes *val;
|
||||
struct snd_value_tlv_bytes *val_tlv;
|
||||
|
||||
if (ctl->access & SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE) {
|
||||
val_tlv = ctl->value;
|
||||
einfo->count = val_tlv->size;
|
||||
} else {
|
||||
val = ctl->value;
|
||||
einfo->count = val->size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mixer_plug_info_integer(struct snd_control *ctl,
|
||||
struct snd_ctl_elem_info *einfo)
|
||||
{
|
||||
struct snd_value_int *val = ctl->value;
|
||||
|
||||
einfo->count = val->count;
|
||||
einfo->value.integer.min = val->min;
|
||||
einfo->value.integer.max = val->max;
|
||||
einfo->value.integer.step = val->step;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mixer_plug_notifier_cb(struct mixer_plugin *plugin)
|
||||
{
|
||||
eventfd_write(plugin->eventfd, 1);
|
||||
plugin->event_cnt++;
|
||||
}
|
||||
|
||||
/* In consume_event/read, do not call eventfd_read until all events are read from list.
|
||||
This will make poll getting unblocked until all events are read */
|
||||
static ssize_t mixer_plug_read_event(void *data, struct snd_ctl_event *ev, size_t size)
|
||||
{
|
||||
struct mixer_plug_data *plug_data = data;
|
||||
struct mixer_plugin *plugin = plug_data->plugin;
|
||||
eventfd_t evfd;
|
||||
ssize_t result = 0;
|
||||
|
||||
result = plug_data->ops->read_event(plugin, ev, size);
|
||||
|
||||
if (result > 0) {
|
||||
plugin->event_cnt -= result / sizeof(struct snd_ctl_event);
|
||||
if (plugin->event_cnt == 0)
|
||||
eventfd_read(plugin->eventfd, &evfd);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int mixer_plug_subscribe_events(struct mixer_plug_data *plug_data,
|
||||
int *subscribe)
|
||||
{
|
||||
struct mixer_plugin *plugin = plug_data->plugin;
|
||||
eventfd_t evfd;
|
||||
|
||||
if (*subscribe < 0 || *subscribe > 1) {
|
||||
*subscribe = plugin->subscribed;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (*subscribe && !plugin->subscribed) {
|
||||
plug_data->ops->subscribe_events(plugin, &mixer_plug_notifier_cb);
|
||||
} else if (plugin->subscribed && !*subscribe) {
|
||||
plug_data->ops->subscribe_events(plugin, NULL);
|
||||
|
||||
if (plugin->event_cnt)
|
||||
eventfd_read(plugin->eventfd, &evfd);
|
||||
|
||||
plugin->event_cnt = 0;
|
||||
}
|
||||
|
||||
plugin->subscribed = *subscribe;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mixer_plug_get_poll_fd(void *data, struct pollfd *pfd, int count)
|
||||
{
|
||||
struct mixer_plug_data *plug_data = data;
|
||||
struct mixer_plugin *plugin = plug_data->plugin;
|
||||
|
||||
if (plugin->eventfd != -1) {
|
||||
pfd[count].fd = plugin->eventfd;
|
||||
return 0;
|
||||
}
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static int mixer_plug_tlv_write(struct mixer_plug_data *plug_data,
|
||||
struct snd_ctl_tlv *tlv)
|
||||
{
|
||||
struct mixer_plugin *plugin = plug_data->plugin;
|
||||
struct snd_control *ctl;
|
||||
struct snd_value_tlv_bytes *val_tlv;
|
||||
|
||||
ctl = plugin->controls + tlv->numid;
|
||||
val_tlv = ctl->value;
|
||||
|
||||
return val_tlv->put(plugin, ctl, tlv);
|
||||
}
|
||||
|
||||
static int mixer_plug_tlv_read(struct mixer_plug_data *plug_data,
|
||||
struct snd_ctl_tlv *tlv)
|
||||
{
|
||||
struct mixer_plugin *plugin = plug_data->plugin;
|
||||
struct snd_control *ctl;
|
||||
struct snd_value_tlv_bytes *val_tlv;
|
||||
|
||||
ctl = plugin->controls + tlv->numid;
|
||||
val_tlv = ctl->value;
|
||||
|
||||
return val_tlv->get(plugin, ctl, tlv);
|
||||
}
|
||||
|
||||
static int mixer_plug_elem_write(struct mixer_plug_data *plug_data,
|
||||
struct snd_ctl_elem_value *ev)
|
||||
{
|
||||
struct mixer_plugin *plugin = plug_data->plugin;
|
||||
struct snd_control *ctl;
|
||||
int ret;
|
||||
|
||||
ret = mixer_plug_get_elem_id(plug_data, &ev->id, ev->id.numid);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ctl = plugin->controls + ev->id.numid;
|
||||
|
||||
return ctl->put(plugin, ctl, ev);
|
||||
}
|
||||
|
||||
static int mixer_plug_elem_read(struct mixer_plug_data *plug_data,
|
||||
struct snd_ctl_elem_value *ev)
|
||||
{
|
||||
struct mixer_plugin *plugin = plug_data->plugin;
|
||||
struct snd_control *ctl;
|
||||
int ret;
|
||||
|
||||
ret = mixer_plug_get_elem_id(plug_data, &ev->id, ev->id.numid);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ctl = plugin->controls + ev->id.numid;
|
||||
|
||||
return ctl->get(plugin, ctl, ev);
|
||||
|
||||
}
|
||||
|
||||
static int mixer_plug_get_elem_info(struct mixer_plug_data *plug_data,
|
||||
struct snd_ctl_elem_info *einfo)
|
||||
{
|
||||
struct mixer_plugin *plugin = plug_data->plugin;
|
||||
struct snd_control *ctl;
|
||||
int ret;
|
||||
|
||||
ret = mixer_plug_get_elem_id(plug_data, &einfo->id,
|
||||
einfo->id.numid);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ctl = plugin->controls + einfo->id.numid;
|
||||
einfo->type = ctl->type;
|
||||
einfo->access = ctl->access;
|
||||
|
||||
switch (einfo->type) {
|
||||
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
|
||||
ret = mixer_plug_info_enum(ctl, einfo);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
break;
|
||||
case SNDRV_CTL_ELEM_TYPE_BYTES:
|
||||
ret = mixer_plug_info_bytes(ctl, einfo);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
break;
|
||||
case SNDRV_CTL_ELEM_TYPE_INTEGER:
|
||||
ret = mixer_plug_info_integer(ctl, einfo);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr,"%s: unknown type %d\n",
|
||||
__func__, einfo->type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mixer_plug_get_elem_list(struct mixer_plug_data *plug_data,
|
||||
struct snd_ctl_elem_list *elist)
|
||||
{
|
||||
struct mixer_plugin *plugin = plug_data->plugin;
|
||||
unsigned int avail;
|
||||
struct snd_ctl_elem_id *id;
|
||||
int ret;
|
||||
|
||||
elist->count = plugin->num_controls;
|
||||
elist->used = 0;
|
||||
avail = elist->space;
|
||||
|
||||
while (avail > 0) {
|
||||
id = elist->pids + elist->used;
|
||||
ret = mixer_plug_get_elem_id(plug_data, id, elist->used);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
avail--;
|
||||
elist->used++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mixer_plug_get_card_info(struct mixer_plug_data *plug_data,
|
||||
struct snd_ctl_card_info *card_info)
|
||||
{
|
||||
/*TODO: Fill card_info here from snd-card-def */
|
||||
memset(card_info, 0, sizeof(*card_info));
|
||||
card_info->card = plug_data->card;
|
||||
memcpy(card_info->id, "card_id", sizeof(card_info->id));
|
||||
memcpy(card_info->driver, "mymixer-so-name", sizeof(card_info->driver));
|
||||
memcpy(card_info->name, "card-name", sizeof(card_info->name));
|
||||
memcpy(card_info->longname, "card-name", sizeof(card_info->longname));
|
||||
memcpy(card_info->mixername, "mixer-name", sizeof(card_info->mixername));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mixer_plug_close(void *data)
|
||||
{
|
||||
struct mixer_plug_data *plug_data = data;
|
||||
struct mixer_plugin *plugin = plug_data->plugin;
|
||||
eventfd_t evfd;
|
||||
|
||||
if (plugin->event_cnt)
|
||||
eventfd_read(plugin->eventfd, &evfd);
|
||||
|
||||
plug_data->ops->close(&plugin);
|
||||
dlclose(plug_data->dl_hdl);
|
||||
|
||||
free(plug_data);
|
||||
plug_data = NULL;
|
||||
}
|
||||
|
||||
static int mixer_plug_ioctl(void *data, unsigned int cmd, ...)
|
||||
{
|
||||
struct mixer_plug_data *plug_data = data;
|
||||
int ret;
|
||||
va_list ap;
|
||||
void *arg;
|
||||
|
||||
va_start(ap, cmd);
|
||||
arg = va_arg(ap, void *);
|
||||
va_end(ap);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_CTL_IOCTL_CARD_INFO:
|
||||
ret = mixer_plug_get_card_info(plug_data, arg);
|
||||
break;
|
||||
case SNDRV_CTL_IOCTL_ELEM_LIST:
|
||||
ret = mixer_plug_get_elem_list(plug_data, arg);
|
||||
break;
|
||||
case SNDRV_CTL_IOCTL_ELEM_INFO:
|
||||
ret = mixer_plug_get_elem_info(plug_data, arg);
|
||||
break;
|
||||
case SNDRV_CTL_IOCTL_ELEM_READ:
|
||||
ret = mixer_plug_elem_read(plug_data, arg);
|
||||
break;
|
||||
case SNDRV_CTL_IOCTL_ELEM_WRITE:
|
||||
ret = mixer_plug_elem_write(plug_data, arg);
|
||||
break;
|
||||
case SNDRV_CTL_IOCTL_TLV_READ:
|
||||
ret = mixer_plug_tlv_read(plug_data, arg);
|
||||
break;
|
||||
case SNDRV_CTL_IOCTL_TLV_WRITE:
|
||||
ret = mixer_plug_tlv_write(plug_data, arg);
|
||||
break;
|
||||
case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
|
||||
ret = mixer_plug_subscribe_events(plug_data, arg);
|
||||
break;
|
||||
default:
|
||||
/* TODO: plugin should support ioctl */
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct mixer_ops mixer_plug_ops = {
|
||||
.close = mixer_plug_close,
|
||||
.read_event = mixer_plug_read_event,
|
||||
.get_poll_fd = mixer_plug_get_poll_fd,
|
||||
.ioctl = mixer_plug_ioctl,
|
||||
};
|
||||
|
||||
int mixer_plugin_open(unsigned int card, void **data,
|
||||
const struct mixer_ops **ops)
|
||||
{
|
||||
struct mixer_plug_data *plug_data;
|
||||
struct mixer_plugin *plugin = NULL;
|
||||
void *dl_hdl;
|
||||
char *so_name;
|
||||
int ret;
|
||||
|
||||
plug_data = calloc(1, sizeof(*plug_data));
|
||||
if (!plug_data)
|
||||
return -ENOMEM;
|
||||
|
||||
plug_data->mixer_node = snd_utils_open_mixer(card);
|
||||
if (!plug_data->mixer_node) {
|
||||
/* Do not print error here.
|
||||
* It is valid for card to not have virtual mixer node
|
||||
*/
|
||||
goto err_get_mixer_node;
|
||||
}
|
||||
|
||||
ret = snd_utils_get_str(plug_data->mixer_node, "so-name",
|
||||
&so_name);
|
||||
if(ret) {
|
||||
fprintf(stderr, "%s: mixer so-name not found for card %u\n",
|
||||
__func__, card);
|
||||
goto err_get_lib_name;
|
||||
|
||||
}
|
||||
|
||||
dl_hdl = dlopen(so_name, RTLD_NOW);
|
||||
if (!dl_hdl) {
|
||||
fprintf(stderr, "%s: unable to open %s\n",
|
||||
__func__, so_name);
|
||||
goto err_dlopen;
|
||||
}
|
||||
|
||||
dlerror();
|
||||
plug_data->ops = dlsym(dl_hdl, "mixer_plugin_ops");
|
||||
if (!plug_data->ops) {
|
||||
fprintf(stderr, "%s: dlsym open fn failed: %s\n",
|
||||
__func__, dlerror());
|
||||
goto err_ops;
|
||||
}
|
||||
|
||||
ret = plug_data->ops->open(&plugin, card);
|
||||
if (ret) {
|
||||
fprintf(stderr, "%s: failed to open plugin, err: %d\n",
|
||||
__func__, ret);
|
||||
goto err_ops;
|
||||
}
|
||||
|
||||
plug_data->plugin = plugin;
|
||||
plug_data->card = card;
|
||||
plug_data->dl_hdl = dl_hdl;
|
||||
plugin->eventfd = eventfd(0, 0);
|
||||
|
||||
*data = plug_data;
|
||||
*ops = &mixer_plug_ops;
|
||||
|
||||
return 0;
|
||||
|
||||
err_ops:
|
||||
dlclose(dl_hdl);
|
||||
err_dlopen:
|
||||
err_get_lib_name:
|
||||
snd_utils_close_dev_node(plug_data->mixer_node);
|
||||
err_get_mixer_node:
|
||||
free(plug_data);
|
||||
return -1;
|
||||
}
|
||||
@@ -59,6 +59,8 @@
|
||||
|
||||
#include <tinyalsa/pcm.h>
|
||||
#include <tinyalsa/limits.h>
|
||||
#include "pcm_io.h"
|
||||
#include "snd_card_plugin.h"
|
||||
|
||||
#ifndef PARAM_MAX
|
||||
#define PARAM_MAX SNDRV_PCM_HW_PARAM_LAST_INTERVAL
|
||||
@@ -68,6 +70,7 @@
|
||||
#define SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP (1<<2)
|
||||
#endif /* SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP */
|
||||
|
||||
|
||||
static inline int param_is_mask(int p)
|
||||
{
|
||||
return (p >= SNDRV_PCM_HW_PARAM_FIRST_MASK) &&
|
||||
@@ -234,6 +237,12 @@ struct pcm {
|
||||
long pcm_delay;
|
||||
/** The subdevice corresponding to the PCM */
|
||||
unsigned int subdevice;
|
||||
/** Pointer to the pcm ops, either hw or plugin */
|
||||
const struct pcm_ops *ops;
|
||||
/** Private data for pcm_hw or pcm_plugin */
|
||||
void *data;
|
||||
/** Pointer to the pcm node from snd card definition */
|
||||
struct snd_node *snd_node;
|
||||
};
|
||||
|
||||
static int oops(struct pcm *pcm, int e, const char *fmt, ...)
|
||||
@@ -383,7 +392,7 @@ int pcm_set_config(struct pcm *pcm, const struct pcm_config *config)
|
||||
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS,
|
||||
SNDRV_PCM_ACCESS_RW_INTERLEAVED);
|
||||
|
||||
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) {
|
||||
if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) {
|
||||
int errno_copy = errno;
|
||||
oops(pcm, -errno, "cannot set hw params");
|
||||
return -errno_copy;
|
||||
@@ -440,7 +449,7 @@ int pcm_set_config(struct pcm *pcm, const struct pcm_config *config)
|
||||
while (pcm->boundary * 2 <= INT_MAX - pcm->buffer_size)
|
||||
pcm->boundary *= 2;
|
||||
|
||||
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {
|
||||
if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {
|
||||
int errno_copy = errno;
|
||||
oops(pcm, -errno, "cannot set sw params");
|
||||
return -errno_copy;
|
||||
@@ -519,7 +528,8 @@ static int pcm_sync_ptr(struct pcm *pcm, int flags)
|
||||
}
|
||||
} else {
|
||||
pcm->sync_ptr->flags = flags;
|
||||
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SYNC_PTR, pcm->sync_ptr) < 0) {
|
||||
if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_SYNC_PTR,
|
||||
pcm->sync_ptr) < 0) {
|
||||
oops(pcm, errno, "failed to sync mmap ptr");
|
||||
return -1;
|
||||
}
|
||||
@@ -599,19 +609,30 @@ struct pcm_params *pcm_params_get(unsigned int card, unsigned int device,
|
||||
unsigned int flags)
|
||||
{
|
||||
struct snd_pcm_hw_params *params;
|
||||
char fn[256];
|
||||
void *snd_node = NULL, *data;
|
||||
const struct pcm_ops *ops;
|
||||
int fd;
|
||||
|
||||
snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
|
||||
flags & PCM_IN ? 'c' : 'p');
|
||||
|
||||
if (flags & PCM_NONBLOCK)
|
||||
fd = open(fn, O_RDWR | O_NONBLOCK);
|
||||
else
|
||||
fd = open(fn, O_RDWR);
|
||||
ops = &hw_ops;
|
||||
fd = ops->open(card, device, flags, &data, snd_node);
|
||||
|
||||
#ifdef TINYALSA_USES_PLUGINS
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "cannot open device '%s': %s\n", fn, strerror(errno));
|
||||
int pcm_type;
|
||||
snd_node = snd_utils_open_pcm(card, device);
|
||||
pcm_type = snd_utils_get_node_type(snd_node);
|
||||
if (!snd_node || pcm_type != SND_NODE_TYPE_PLUGIN) {
|
||||
fprintf(stderr, "no device (hw/plugin) for card(%u), device(%u)",
|
||||
card, device);
|
||||
goto err_open;
|
||||
}
|
||||
ops = &plug_ops;
|
||||
fd = ops->open(card, device, flags, &data, snd_node);
|
||||
}
|
||||
#endif
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "cannot open card(%d) device (%d): %s\n",
|
||||
card, device, strerror(errno));
|
||||
goto err_open;
|
||||
}
|
||||
|
||||
@@ -620,19 +641,27 @@ struct pcm_params *pcm_params_get(unsigned int card, unsigned int device,
|
||||
goto err_calloc;
|
||||
|
||||
param_init(params);
|
||||
if (ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, params)) {
|
||||
if (ops->ioctl(data, SNDRV_PCM_IOCTL_HW_REFINE, params)) {
|
||||
fprintf(stderr, "SNDRV_PCM_IOCTL_HW_REFINE error (%d)\n", errno);
|
||||
goto err_hw_refine;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
#ifdef TINYALSA_USES_PLUGINS
|
||||
if (snd_node)
|
||||
snd_utils_close_dev_node(snd_node);
|
||||
#endif
|
||||
ops->close(data);
|
||||
|
||||
return (struct pcm_params *)params;
|
||||
|
||||
err_hw_refine:
|
||||
free(params);
|
||||
err_calloc:
|
||||
close(fd);
|
||||
#ifdef TINYALSA_USES_PLUGINS
|
||||
if (snd_node)
|
||||
snd_utils_close_dev_node(snd_node);
|
||||
#endif
|
||||
ops->close(data);
|
||||
err_open:
|
||||
return NULL;
|
||||
}
|
||||
@@ -787,8 +816,8 @@ int pcm_close(struct pcm *pcm)
|
||||
munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
|
||||
}
|
||||
|
||||
if (pcm->fd >= 0)
|
||||
close(pcm->fd);
|
||||
snd_utils_close_dev_node(pcm->snd_node);
|
||||
pcm->ops->close(pcm->data);
|
||||
pcm->buffer_size = 0;
|
||||
pcm->fd = -1;
|
||||
free(pcm);
|
||||
@@ -851,29 +880,39 @@ struct pcm *pcm_open(unsigned int card, unsigned int device,
|
||||
{
|
||||
struct pcm *pcm;
|
||||
struct snd_pcm_info info;
|
||||
char fn[256];
|
||||
int rc;
|
||||
|
||||
pcm = calloc(1, sizeof(struct pcm));
|
||||
if (!pcm)
|
||||
return &bad_pcm;
|
||||
|
||||
snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
|
||||
flags & PCM_IN ? 'c' : 'p');
|
||||
|
||||
pcm->flags = flags;
|
||||
|
||||
if (flags & PCM_NONBLOCK)
|
||||
pcm->fd = open(fn, O_RDWR | O_NONBLOCK);
|
||||
else
|
||||
pcm->fd = open(fn, O_RDWR);
|
||||
/* Default to hw_ops, attemp plugin open only if hw (/dev/snd/pcm*) open fails */
|
||||
pcm->ops = &hw_ops;
|
||||
pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, NULL);
|
||||
|
||||
#ifdef TINYALSA_USES_PLUGINS
|
||||
if (pcm->fd < 0) {
|
||||
oops(pcm, errno, "cannot open device '%s'", fn);
|
||||
int pcm_type;
|
||||
pcm->snd_node = snd_utils_open_pcm(card, device);
|
||||
pcm_type = snd_utils_get_node_type(pcm->snd_node);
|
||||
if (!pcm->snd_node || pcm_type != SND_NODE_TYPE_PLUGIN) {
|
||||
oops(pcm, -ENODEV, "no device (hw/plugin) for card(%u), device(%u)",
|
||||
card, device);
|
||||
return pcm;
|
||||
}
|
||||
pcm->ops = &plug_ops;
|
||||
pcm->fd = pcm->ops->open(card, device, flags, &pcm->data, pcm->snd_node);
|
||||
}
|
||||
#endif
|
||||
if (pcm->fd < 0) {
|
||||
oops(pcm, errno, "cannot open device (%u) for card (%u)",
|
||||
device, card);
|
||||
return pcm;
|
||||
}
|
||||
|
||||
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) {
|
||||
pcm->flags = flags;
|
||||
|
||||
if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_INFO, &info)) {
|
||||
oops(pcm, errno, "cannot get info");
|
||||
goto fail_close;
|
||||
}
|
||||
@@ -891,7 +930,7 @@ struct pcm *pcm_open(unsigned int card, unsigned int device,
|
||||
#ifdef SNDRV_PCM_IOCTL_TTSTAMP
|
||||
if (pcm->flags & PCM_MONOTONIC) {
|
||||
int arg = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC;
|
||||
rc = ioctl(pcm->fd, SNDRV_PCM_IOCTL_TTSTAMP, &arg);
|
||||
rc = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_TTSTAMP, &arg);
|
||||
if (rc < 0) {
|
||||
oops(pcm, rc, "cannot set timestamp type");
|
||||
goto fail;
|
||||
@@ -911,7 +950,11 @@ fail:
|
||||
if (flags & PCM_MMAP)
|
||||
munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
|
||||
fail_close:
|
||||
close(pcm->fd);
|
||||
#ifdef TINYALSA_USES_PLUGINS
|
||||
if (pcm->snd_node)
|
||||
snd_utils_close_dev_node(pcm->snd_node);
|
||||
#endif
|
||||
pcm->ops->close(pcm->data);
|
||||
pcm->fd = -1;
|
||||
return pcm;
|
||||
}
|
||||
@@ -970,7 +1013,7 @@ int pcm_unlink(struct pcm *pcm)
|
||||
*/
|
||||
int pcm_prepare(struct pcm *pcm)
|
||||
{
|
||||
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) < 0)
|
||||
if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_PREPARE) < 0)
|
||||
return oops(pcm, errno, "cannot prepare channel");
|
||||
|
||||
/* get appl_ptr and avail_min from kernel */
|
||||
@@ -991,7 +1034,7 @@ int pcm_start(struct pcm *pcm)
|
||||
return -1;
|
||||
|
||||
if (pcm->mmap_status->state != PCM_STATE_RUNNING) {
|
||||
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START) < 0)
|
||||
if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_START) < 0)
|
||||
return oops(pcm, errno, "cannot start channel");
|
||||
}
|
||||
|
||||
@@ -1005,7 +1048,7 @@ int pcm_start(struct pcm *pcm)
|
||||
*/
|
||||
int pcm_stop(struct pcm *pcm)
|
||||
{
|
||||
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DROP) < 0)
|
||||
if (pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_DROP) < 0)
|
||||
return oops(pcm, errno, "cannot stop channel");
|
||||
|
||||
return 0;
|
||||
@@ -1288,7 +1331,7 @@ int pcm_mmap_transfer(struct pcm *pcm, void *buffer, unsigned int frames)
|
||||
*/
|
||||
if (!is_playback && state == PCM_STATE_PREPARED &&
|
||||
frames >= pcm->config.start_threshold) {
|
||||
err = ioctl(pcm->fd, SNDRV_PCM_IOCTL_START);
|
||||
err = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_START);
|
||||
if (err == -1)
|
||||
return -1;
|
||||
/* state = PCM_STATE_RUNNING */
|
||||
@@ -1326,7 +1369,7 @@ int pcm_mmap_transfer(struct pcm *pcm, void *buffer, unsigned int frames)
|
||||
/* start playback if written >= start_threshold */
|
||||
if (is_playback && state == PCM_STATE_PREPARED &&
|
||||
pcm->buffer_size - avail >= pcm->config.start_threshold) {
|
||||
err = ioctl(pcm->fd, SNDRV_PCM_IOCTL_START);
|
||||
err = pcm->ops->ioctl(pcm->data, SNDRV_PCM_IOCTL_START);
|
||||
if (err == -1)
|
||||
break;
|
||||
}
|
||||
@@ -1365,9 +1408,9 @@ static int pcm_rw_transfer(struct pcm *pcm, void *data, unsigned int frames)
|
||||
transfer.frames = frames;
|
||||
transfer.result = 0;
|
||||
|
||||
res = ioctl(pcm->fd, is_playback
|
||||
? SNDRV_PCM_IOCTL_WRITEI_FRAMES
|
||||
: SNDRV_PCM_IOCTL_READI_FRAMES, &transfer);
|
||||
res = pcm->ops->ioctl(pcm->data, is_playback
|
||||
? SNDRV_PCM_IOCTL_WRITEI_FRAMES
|
||||
: SNDRV_PCM_IOCTL_READI_FRAMES, &transfer);
|
||||
|
||||
return res == 0 ? (int) transfer.result : -1;
|
||||
}
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/* pcm_hw.c
|
||||
** Copyright (c) 2019, The Linux Foundation.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above
|
||||
** copyright notice, this list of conditions and the following
|
||||
** disclaimer in the documentation and/or other materials provided
|
||||
** with the distribution.
|
||||
** * Neither the name of The Linux Foundation nor the names of its
|
||||
** contributors may be used to endorse or promote products derived
|
||||
** from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
|
||||
** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||
** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <sound/asound.h>
|
||||
#include <tinyalsa/asoundlib.h>
|
||||
|
||||
#include "pcm_io.h"
|
||||
|
||||
struct pcm_hw_data {
|
||||
/** Card number of the pcm device */
|
||||
unsigned int card;
|
||||
/** Device number for the pcm device */
|
||||
unsigned int device;
|
||||
/** File descriptor to the pcm device file node */
|
||||
unsigned int fd;
|
||||
/** Pointer to the pcm node from snd card definiton */
|
||||
struct snd_node *node;
|
||||
};
|
||||
|
||||
static void pcm_hw_close(void *data)
|
||||
{
|
||||
struct pcm_hw_data *hw_data = data;
|
||||
|
||||
if (hw_data->fd > 0)
|
||||
close(hw_data->fd);
|
||||
|
||||
free(hw_data);
|
||||
}
|
||||
|
||||
static int pcm_hw_ioctl(void *data, unsigned int cmd, ...)
|
||||
{
|
||||
struct pcm_hw_data *hw_data = data;
|
||||
va_list ap;
|
||||
void *arg;
|
||||
|
||||
va_start(ap, cmd);
|
||||
arg = va_arg(ap, void *);
|
||||
va_end(ap);
|
||||
|
||||
return ioctl(hw_data->fd, cmd, arg);
|
||||
}
|
||||
|
||||
static int pcm_hw_open(unsigned int card, unsigned int device,
|
||||
unsigned int flags, void **data, struct snd_node *node)
|
||||
{
|
||||
struct pcm_hw_data *hw_data;
|
||||
char fn[256];
|
||||
int fd;
|
||||
|
||||
hw_data = calloc(1, sizeof(*hw_data));
|
||||
if (!hw_data) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
|
||||
flags & PCM_IN ? 'c' : 'p');
|
||||
if (flags & PCM_NONBLOCK)
|
||||
fd = open(fn, O_RDWR|O_NONBLOCK);
|
||||
else
|
||||
fd = open(fn, O_RDWR);
|
||||
|
||||
if (fd < 0) {
|
||||
return fd;
|
||||
}
|
||||
|
||||
hw_data->card = card;
|
||||
hw_data->device = device;
|
||||
hw_data->fd = fd;
|
||||
hw_data->node = node;
|
||||
|
||||
*data = hw_data;
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
const struct pcm_ops hw_ops = {
|
||||
.open = pcm_hw_open,
|
||||
.close = pcm_hw_close,
|
||||
.ioctl = pcm_hw_ioctl,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/* pcm_io.h
|
||||
** Copyright (c) 2019, The Linux Foundation.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above
|
||||
** copyright notice, this list of conditions and the following
|
||||
** disclaimer in the documentation and/or other materials provided
|
||||
** with the distribution.
|
||||
** * Neither the name of The Linux Foundation nor the names of its
|
||||
** contributors may be used to endorse or promote products derived
|
||||
** from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
|
||||
** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||
** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**/
|
||||
|
||||
#ifndef TINYALSA_SRC_PCM_IO_H
|
||||
#define TINYALSA_SRC_PCM_IO_H
|
||||
|
||||
#include <sound/asound.h>
|
||||
|
||||
struct snd_node;
|
||||
|
||||
struct pcm_ops {
|
||||
int (*open) (unsigned int card, unsigned int device,
|
||||
unsigned int flags, void **data, struct snd_node *node);
|
||||
void (*close) (void *data);
|
||||
int (*ioctl) (void *data, unsigned int cmd, ...);
|
||||
};
|
||||
|
||||
extern const struct pcm_ops hw_ops;
|
||||
extern const struct pcm_ops plug_ops;
|
||||
|
||||
#endif /* TINYALSA_SRC_PCM_IO_H */
|
||||
@@ -0,0 +1,718 @@
|
||||
/* pcm_plugin.c
|
||||
** Copyright (c) 2019, The Linux Foundation.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above
|
||||
** copyright notice, this list of conditions and the following
|
||||
** disclaimer in the documentation and/or other materials provided
|
||||
** with the distribution.
|
||||
** * Neither the name of The Linux Foundation nor the names of its
|
||||
** contributors may be used to endorse or promote products derived
|
||||
** from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
|
||||
** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||
** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <sound/asound.h>
|
||||
#include <tinyalsa/asoundlib.h>
|
||||
#include <tinyalsa/plugin.h>
|
||||
|
||||
#include "pcm_io.h"
|
||||
#include "snd_card_plugin.h"
|
||||
|
||||
/* 2 words of uint32_t = 64 bits of mask */
|
||||
#define PCM_MASK_SIZE (2)
|
||||
#define ARRAY_SIZE(a) \
|
||||
(sizeof(a) / sizeof(a[0]))
|
||||
|
||||
#define PCM_PARAM_GET_MASK(p, n) \
|
||||
&p->masks[n - SNDRV_PCM_HW_PARAM_FIRST_MASK];
|
||||
|
||||
enum {
|
||||
PCM_PLUG_HW_PARAM_SELECT_MIN,
|
||||
PCM_PLUG_HW_PARAM_SELECT_MAX,
|
||||
PCM_PLUG_HW_PARAM_SELECT_VAL,
|
||||
};
|
||||
|
||||
enum {
|
||||
PCM_PLUG_STATE_OPEN,
|
||||
PCM_PLUG_STATE_SETUP,
|
||||
PCM_PLUG_STATE_PREPARED,
|
||||
PCM_PLUG_STATE_RUNNING,
|
||||
};
|
||||
|
||||
struct pcm_plug_data {
|
||||
unsigned int card;
|
||||
unsigned int device;
|
||||
unsigned int fd;
|
||||
unsigned int flags;
|
||||
|
||||
void *dl_hdl;
|
||||
/** pointer to plugin operation */
|
||||
const struct pcm_plugin_ops *ops;
|
||||
struct pcm_plugin *plugin;
|
||||
void *dev_node;
|
||||
};
|
||||
|
||||
static unsigned int param_list[] = {
|
||||
SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
|
||||
SNDRV_PCM_HW_PARAM_CHANNELS,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS,
|
||||
};
|
||||
|
||||
static void pcm_plug_close(void *data)
|
||||
{
|
||||
struct pcm_plug_data *plug_data = data;
|
||||
struct pcm_plugin *plugin = plug_data->plugin;
|
||||
|
||||
plug_data->ops->close(plugin);
|
||||
dlclose(plug_data->dl_hdl);
|
||||
|
||||
free(plug_data);
|
||||
}
|
||||
|
||||
static int pcm_plug_info(struct pcm_plug_data *plug_data,
|
||||
struct snd_pcm_info *info)
|
||||
{
|
||||
int stream = SNDRV_PCM_STREAM_PLAYBACK;
|
||||
int ret = 0, val = -1;
|
||||
char *name;
|
||||
|
||||
memset(info, 0, sizeof(*info));
|
||||
|
||||
if (plug_data->flags & PCM_IN) {
|
||||
stream = SNDRV_PCM_STREAM_CAPTURE;
|
||||
ret = snd_utils_get_int(plug_data->dev_node, "capture", &val);
|
||||
if (ret || !val) {
|
||||
fprintf(stderr, "%s: not a capture device\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
stream = SNDRV_PCM_STREAM_PLAYBACK;
|
||||
ret = snd_utils_get_int(plug_data->dev_node, "playback", &val);
|
||||
if (ret || !val) {
|
||||
fprintf(stderr, "%s: not a playback device\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
info->stream = stream;
|
||||
info->card = plug_data->card;
|
||||
info->device = plug_data->device;
|
||||
|
||||
ret = snd_utils_get_str(plug_data->dev_node, "name", &name);
|
||||
if (ret) {
|
||||
fprintf(stderr, "%s: failed to get pcm device name\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
strncpy((char *)info->id, name, sizeof(info->id));
|
||||
strncpy((char *)info->name, name, sizeof(info->name));
|
||||
strncpy((char *)info->subname, name, sizeof(info->subname));
|
||||
|
||||
info->subdevices_count = 1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void pcm_plug_set_mask(struct snd_pcm_hw_params *p, int n, uint64_t v)
|
||||
{
|
||||
struct snd_mask *mask;
|
||||
|
||||
mask = PCM_PARAM_GET_MASK(p, n);
|
||||
|
||||
mask->bits[0] |= (v & 0xFFFFFFFF);
|
||||
mask->bits[1] |= ((v >> 32) & 0xFFFFFFFF);
|
||||
/*
|
||||
* currently only supporting 64 bits, may need to update to support
|
||||
* more than 64 bits
|
||||
*/
|
||||
}
|
||||
|
||||
static void pcm_plug_set_interval(struct snd_pcm_hw_params *params,
|
||||
int p, struct pcm_plugin_min_max *v, int is_integer)
|
||||
{
|
||||
struct snd_interval *i;
|
||||
|
||||
i = ¶ms->intervals[p - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL];
|
||||
|
||||
i->min = v->min;
|
||||
i->max = v->max;
|
||||
|
||||
if (is_integer)
|
||||
i->integer = 1;
|
||||
}
|
||||
|
||||
static int pcm_plug_frames_to_bytes(unsigned int frames,
|
||||
unsigned int frame_bits)
|
||||
{
|
||||
return (frames * (frame_bits / 8));
|
||||
}
|
||||
|
||||
static int pcm_plug_bytes_to_frames(unsigned int size,
|
||||
unsigned int frame_bits)
|
||||
{
|
||||
return (size * 8) / frame_bits;
|
||||
}
|
||||
|
||||
static int pcm_plug_get_params(struct pcm_plugin *plugin,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct pcm_plugin_min_max bw, ch, pb, periods;
|
||||
struct pcm_plugin_min_max val;
|
||||
struct pcm_plugin_min_max frame_bits, buffer_bytes;
|
||||
|
||||
/*
|
||||
* populate the struct snd_pcm_hw_params structure
|
||||
* using the hw_param constraints provided by plugin
|
||||
* via the plugin->constraints
|
||||
*/
|
||||
|
||||
/* Set the mask params */
|
||||
pcm_plug_set_mask(params, SNDRV_PCM_HW_PARAM_ACCESS,
|
||||
plugin->constraints->access);
|
||||
pcm_plug_set_mask(params, SNDRV_PCM_HW_PARAM_FORMAT,
|
||||
plugin->constraints->format);
|
||||
pcm_plug_set_mask(params, SNDRV_PCM_HW_PARAM_SUBFORMAT,
|
||||
SNDRV_PCM_SUBFORMAT_STD);
|
||||
|
||||
/* Set the standard interval params */
|
||||
pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
|
||||
&plugin->constraints->bit_width, 1);
|
||||
pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS,
|
||||
&plugin->constraints->channels, 1);
|
||||
pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_RATE,
|
||||
&plugin->constraints->rate, 1);
|
||||
pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
|
||||
&plugin->constraints->period_bytes, 0);
|
||||
pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_PERIODS,
|
||||
&plugin->constraints->periods, 1);
|
||||
|
||||
/* set the calculated interval params */
|
||||
|
||||
bw.min = plugin->constraints->bit_width.min;
|
||||
bw.max = plugin->constraints->bit_width.max;
|
||||
|
||||
ch.min = plugin->constraints->channels.min;
|
||||
ch.max = plugin->constraints->channels.max;
|
||||
|
||||
pb.min = plugin->constraints->period_bytes.min;
|
||||
pb.max = plugin->constraints->period_bytes.max;
|
||||
|
||||
periods.min = plugin->constraints->periods.min;
|
||||
periods.max = plugin->constraints->periods.max;
|
||||
|
||||
/* Calculate and set frame bits */
|
||||
frame_bits.min = bw.min * ch.min;
|
||||
frame_bits.max = bw.max * ch.max;
|
||||
pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS,
|
||||
&frame_bits, 1);
|
||||
|
||||
|
||||
/* Calculate and set period_size in frames */
|
||||
val.min = pcm_plug_bytes_to_frames(pb.min, frame_bits.min);
|
||||
val.max = pcm_plug_bytes_to_frames(pb.max, frame_bits.min);
|
||||
pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
|
||||
&val, 1);
|
||||
|
||||
/* Calculate and set buffer_bytes */
|
||||
buffer_bytes.min = pb.min * periods.min;
|
||||
buffer_bytes.max = pb.max * periods.max;
|
||||
pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
|
||||
&buffer_bytes, 1);
|
||||
|
||||
/* Calculate and set buffer_size in frames */
|
||||
val.min = pcm_plug_bytes_to_frames(buffer_bytes.min, frame_bits.min);
|
||||
val.max = pcm_plug_bytes_to_frames(buffer_bytes.max, frame_bits.min);
|
||||
pcm_plug_set_interval(params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
|
||||
&val, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcm_plug_masks_refine(struct snd_pcm_hw_params *p,
|
||||
struct snd_pcm_hw_params *c)
|
||||
{
|
||||
struct snd_mask *req_mask;
|
||||
struct snd_mask *con_mask;
|
||||
unsigned int idx, i, masks;
|
||||
|
||||
masks = SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK;
|
||||
|
||||
for (idx = 0; idx <= masks; idx++) {
|
||||
|
||||
if (!(p->rmask & (1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_MASK))))
|
||||
continue;
|
||||
|
||||
req_mask = PCM_PARAM_GET_MASK(p, idx);
|
||||
con_mask = PCM_PARAM_GET_MASK(c, idx);
|
||||
|
||||
/*
|
||||
* set the changed mask if requested mask value is not the same as
|
||||
* constrained mask value
|
||||
*/
|
||||
if (memcmp(req_mask, con_mask, PCM_MASK_SIZE * sizeof(uint32_t)))
|
||||
p->cmask |= 1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_MASK);
|
||||
|
||||
/* Actually change the requested mask to constrained mask */
|
||||
for (i = 0; i < PCM_MASK_SIZE; i++)
|
||||
req_mask->bits[i] &= con_mask->bits[i];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcm_plug_interval_refine(struct snd_pcm_hw_params *p,
|
||||
struct snd_pcm_hw_params *c)
|
||||
{
|
||||
struct snd_interval *ri;
|
||||
struct snd_interval *ci;
|
||||
unsigned int idx;
|
||||
unsigned int intervals;
|
||||
int changed = 0;
|
||||
|
||||
intervals = SNDRV_PCM_HW_PARAM_LAST_INTERVAL -
|
||||
SNDRV_PCM_HW_PARAM_FIRST_INTERVAL;
|
||||
|
||||
for (idx = 0; idx <= intervals; idx++) {
|
||||
ri = &p->intervals[idx];
|
||||
ci = &c->intervals[idx];
|
||||
|
||||
if (!(p->rmask & (1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_INTERVAL)) ))
|
||||
continue;
|
||||
|
||||
if (ri->min < ci->min) {
|
||||
ri->min = ci->min;
|
||||
ri->openmin = ci->openmin;
|
||||
changed = 1;
|
||||
} else if (ri->min == ci->min && !ri->openmin && ci->openmin) {
|
||||
ri->openmin = 1;
|
||||
changed = 1;
|
||||
}
|
||||
|
||||
if (ri->max > ci->max) {
|
||||
ri->max = ci->max;
|
||||
ri->openmax = ci->openmax;
|
||||
changed = 1;
|
||||
} else if (ri->max == ci->max && !ri->openmax && ci->openmax) {
|
||||
ri->openmax = 1;
|
||||
changed = 1;
|
||||
};
|
||||
|
||||
if (!ri->integer && ci->integer) {
|
||||
ri->integer = 1;
|
||||
changed = 1;
|
||||
}
|
||||
|
||||
if (ri->integer) {
|
||||
if (ri->openmin) {
|
||||
ri->min++;
|
||||
ri->openmin = 0;
|
||||
}
|
||||
if (ri->openmax) {
|
||||
ri->max--;
|
||||
ri->openmax = 0;
|
||||
}
|
||||
} else if (!ri->openmin && !ri->openmax && ri->min == ri->max) {
|
||||
ri->integer = 1;
|
||||
}
|
||||
|
||||
/* Set the changed mask */
|
||||
if (changed)
|
||||
p->cmask |= (1 << (idx + SNDRV_PCM_HW_PARAM_FIRST_INTERVAL));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int pcm_plug_hw_params_refine(struct snd_pcm_hw_params *p,
|
||||
struct snd_pcm_hw_params *c)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = pcm_plug_masks_refine(p, c);
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: masks refine failed %d\n", __func__, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = pcm_plug_interval_refine(p, c);
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: interval refine failed %d\n", __func__, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* clear the requested params */
|
||||
p->rmask = 0;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int __pcm_plug_hrefine(struct pcm_plug_data *plug_data,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct pcm_plugin *plugin = plug_data->plugin;
|
||||
struct snd_pcm_hw_params plug_params;
|
||||
int rc;
|
||||
|
||||
memset(&plug_params, 0, sizeof(plug_params));
|
||||
rc = pcm_plug_get_params(plugin, &plug_params);
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: pcm_plug_get_params failed %d\n",
|
||||
__func__, rc);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return pcm_plug_hw_params_refine(params, &plug_params);
|
||||
|
||||
}
|
||||
|
||||
static int pcm_plug_hrefine(struct pcm_plug_data *plug_data,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
return __pcm_plug_hrefine(plug_data, params);
|
||||
}
|
||||
|
||||
static int pcm_plug_interval_select(struct snd_pcm_hw_params *p,
|
||||
unsigned int param, unsigned int select, unsigned int val)
|
||||
{
|
||||
struct snd_interval *i;
|
||||
|
||||
if (param < SNDRV_PCM_HW_PARAM_FIRST_INTERVAL ||
|
||||
param > SNDRV_PCM_HW_PARAM_LAST_INTERVAL)
|
||||
return -EINVAL;
|
||||
|
||||
i = &p->intervals[param - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL];
|
||||
|
||||
if (!i->min)
|
||||
return -EINVAL;
|
||||
|
||||
switch (select) {
|
||||
|
||||
case PCM_PLUG_HW_PARAM_SELECT_MIN:
|
||||
i->max = i->min;
|
||||
break;
|
||||
|
||||
case PCM_PLUG_HW_PARAM_SELECT_MAX:
|
||||
i->min = i->max;
|
||||
break;
|
||||
|
||||
case PCM_PLUG_HW_PARAM_SELECT_VAL:
|
||||
i->min = i->max = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pcm_plug_hw_params_set(struct snd_pcm_hw_params *p)
|
||||
{
|
||||
unsigned int i, select;
|
||||
unsigned int bw, ch, period_sz, periods;
|
||||
unsigned int val1, val2, offset;
|
||||
|
||||
offset = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL;
|
||||
|
||||
/* Select the min values first */
|
||||
select = PCM_PLUG_HW_PARAM_SELECT_MIN;
|
||||
for (i = 0; i < ARRAY_SIZE(param_list); i++)
|
||||
pcm_plug_interval_select(p, param_list[i], select, 0);
|
||||
|
||||
/* Select calculated values */
|
||||
select = PCM_PLUG_HW_PARAM_SELECT_VAL;
|
||||
bw = (p->intervals[SNDRV_PCM_HW_PARAM_SAMPLE_BITS - offset]).min;
|
||||
ch = (p->intervals[SNDRV_PCM_HW_PARAM_CHANNELS - offset]).min;
|
||||
period_sz = (p->intervals[SNDRV_PCM_HW_PARAM_PERIOD_SIZE - offset]).min;
|
||||
periods = (p->intervals[SNDRV_PCM_HW_PARAM_PERIODS - offset]).min;
|
||||
|
||||
val1 = bw * ch; // frame_bits;
|
||||
pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_FRAME_BITS, select, val1);
|
||||
|
||||
val2 = pcm_plug_frames_to_bytes(period_sz, val1); // period_bytes;
|
||||
pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, select,
|
||||
val2);
|
||||
|
||||
val2 = period_sz * periods; //buffer_size;
|
||||
pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, select, val2);
|
||||
|
||||
val2 = pcm_plug_frames_to_bytes(period_sz * periods, val1); //buffer_bytes;
|
||||
pcm_plug_interval_select(p, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, select, val2);
|
||||
}
|
||||
|
||||
static int pcm_plug_hparams(struct pcm_plug_data *plug_data,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct pcm_plugin *plugin = plug_data->plugin;
|
||||
int rc;
|
||||
|
||||
if (plugin->state != PCM_PLUG_STATE_OPEN)
|
||||
return -EBADFD;
|
||||
|
||||
params->rmask = ~0U;
|
||||
|
||||
rc = __pcm_plug_hrefine(plug_data, params);
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: __pcm_plug_hrefine failed %d\n",
|
||||
__func__, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
pcm_plug_hw_params_set(params);
|
||||
|
||||
rc = plug_data->ops->hw_params(plugin, params);
|
||||
if (!rc)
|
||||
plugin->state = PCM_PLUG_STATE_SETUP;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int pcm_plug_sparams(struct pcm_plug_data *plug_data,
|
||||
struct snd_pcm_sw_params *params)
|
||||
{
|
||||
struct pcm_plugin *plugin = plug_data->plugin;
|
||||
|
||||
if (plugin->state != PCM_PLUG_STATE_SETUP)
|
||||
return -EBADFD;
|
||||
|
||||
return plug_data->ops->sw_params(plugin, params);
|
||||
}
|
||||
|
||||
static int pcm_plug_sync_ptr(struct pcm_plug_data *plug_data,
|
||||
struct snd_pcm_sync_ptr *sync_ptr)
|
||||
{
|
||||
struct pcm_plugin *plugin = plug_data->plugin;
|
||||
|
||||
return plug_data->ops->sync_ptr(plugin, sync_ptr);
|
||||
}
|
||||
|
||||
static int pcm_plug_writei_frames(struct pcm_plug_data *plug_data,
|
||||
struct snd_xferi *x)
|
||||
{
|
||||
struct pcm_plugin *plugin = plug_data->plugin;
|
||||
|
||||
if (plugin->state != PCM_PLUG_STATE_PREPARED &&
|
||||
plugin->state != PCM_PLUG_STATE_RUNNING)
|
||||
return -EBADFD;
|
||||
|
||||
return plug_data->ops->writei_frames(plugin, x);
|
||||
}
|
||||
|
||||
static int pcm_plug_readi_frames(struct pcm_plug_data *plug_data,
|
||||
struct snd_xferi *x)
|
||||
{
|
||||
struct pcm_plugin *plugin = plug_data->plugin;
|
||||
|
||||
if (plugin->state != PCM_PLUG_STATE_RUNNING)
|
||||
return -EBADFD;
|
||||
|
||||
return plug_data->ops->readi_frames(plugin, x);
|
||||
}
|
||||
|
||||
static int pcm_plug_ttstamp(struct pcm_plug_data *plug_data,
|
||||
int *tstamp)
|
||||
{
|
||||
struct pcm_plugin *plugin = plug_data->plugin;
|
||||
|
||||
if (plugin->state != PCM_PLUG_STATE_RUNNING)
|
||||
return -EBADFD;
|
||||
|
||||
return plug_data->ops->ttstamp(plugin, tstamp);
|
||||
}
|
||||
|
||||
static int pcm_plug_prepare(struct pcm_plug_data *plug_data)
|
||||
{
|
||||
struct pcm_plugin *plugin = plug_data->plugin;
|
||||
int rc;
|
||||
|
||||
if (plugin->state != PCM_PLUG_STATE_SETUP)
|
||||
return -EBADFD;
|
||||
|
||||
rc = plug_data->ops->prepare(plugin);
|
||||
if (!rc)
|
||||
plugin->state = PCM_PLUG_STATE_PREPARED;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int pcm_plug_start(struct pcm_plug_data *plug_data)
|
||||
{
|
||||
struct pcm_plugin *plugin = plug_data->plugin;
|
||||
int rc;
|
||||
|
||||
if (plugin->state != PCM_PLUG_STATE_PREPARED)
|
||||
return -EBADFD;
|
||||
|
||||
rc = plug_data->ops->start(plugin);
|
||||
if (!rc)
|
||||
plugin->state = PCM_PLUG_STATE_RUNNING;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int pcm_plug_drop(struct pcm_plug_data *plug_data)
|
||||
{
|
||||
struct pcm_plugin *plugin = plug_data->plugin;
|
||||
int rc;
|
||||
|
||||
rc = plug_data->ops->drop(plugin);
|
||||
if (!rc)
|
||||
plugin->state = PCM_PLUG_STATE_SETUP;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int pcm_plug_ioctl(void *data, unsigned int cmd, ...)
|
||||
{
|
||||
struct pcm_plug_data *plug_data = data;
|
||||
struct pcm_plugin *plugin = plug_data->plugin;
|
||||
int ret;
|
||||
va_list ap;
|
||||
void *arg;
|
||||
|
||||
va_start(ap, cmd);
|
||||
arg = va_arg(ap, void *);
|
||||
va_end(ap);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_IOCTL_INFO:
|
||||
ret = pcm_plug_info(plug_data, arg);
|
||||
break;
|
||||
case SNDRV_PCM_IOCTL_TTSTAMP:
|
||||
ret = pcm_plug_ttstamp(plug_data, arg);
|
||||
break;
|
||||
case SNDRV_PCM_IOCTL_HW_REFINE:
|
||||
ret = pcm_plug_hrefine(plug_data, arg);
|
||||
break;
|
||||
case SNDRV_PCM_IOCTL_HW_PARAMS:
|
||||
ret = pcm_plug_hparams(plug_data, arg);
|
||||
break;
|
||||
case SNDRV_PCM_IOCTL_SW_PARAMS:
|
||||
ret = pcm_plug_sparams(plug_data, arg);
|
||||
break;
|
||||
case SNDRV_PCM_IOCTL_SYNC_PTR:
|
||||
ret = pcm_plug_sync_ptr(plug_data, arg);
|
||||
break;
|
||||
case SNDRV_PCM_IOCTL_PREPARE:
|
||||
ret = pcm_plug_prepare(plug_data);
|
||||
break;
|
||||
case SNDRV_PCM_IOCTL_START:
|
||||
ret = pcm_plug_start(plug_data);
|
||||
break;
|
||||
case SNDRV_PCM_IOCTL_DROP:
|
||||
ret = pcm_plug_drop(plug_data);
|
||||
break;
|
||||
case SNDRV_PCM_IOCTL_WRITEI_FRAMES:
|
||||
ret = pcm_plug_writei_frames(plug_data, arg);
|
||||
break;
|
||||
case SNDRV_PCM_IOCTL_READI_FRAMES:
|
||||
ret = pcm_plug_readi_frames(plug_data, arg);
|
||||
break;
|
||||
default:
|
||||
ret = plug_data->ops->ioctl(plugin, cmd, arg);
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pcm_plug_open(unsigned int card, unsigned int device,
|
||||
unsigned int flags, void **data, struct snd_node *pcm_node)
|
||||
{
|
||||
struct pcm_plug_data *plug_data;
|
||||
void *dl_hdl;
|
||||
int rc = 0;
|
||||
char *so_name;
|
||||
|
||||
plug_data = calloc(1, sizeof(*plug_data));
|
||||
if (!plug_data) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rc = snd_utils_get_str(pcm_node, "so-name", &so_name);
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: failed to get plugin lib name\n", __func__);
|
||||
goto err_get_lib;
|
||||
}
|
||||
|
||||
dl_hdl = dlopen(so_name, RTLD_NOW);
|
||||
if (!dl_hdl) {
|
||||
fprintf(stderr, "%s: unable to open %s\n", __func__, so_name);
|
||||
goto err_dl_open;
|
||||
} else {
|
||||
fprintf(stderr, "%s: dlopen successful for %s\n", __func__, so_name);
|
||||
}
|
||||
|
||||
dlerror();
|
||||
|
||||
plug_data->ops = dlsym(dl_hdl, "pcm_plugin_ops");
|
||||
if (!plug_data->ops) {
|
||||
fprintf(stderr, "%s: dlsym to open fn failed, err = '%s'\n",
|
||||
__func__, dlerror());
|
||||
goto err_dlsym;
|
||||
}
|
||||
|
||||
rc = plug_data->ops->open(&plug_data->plugin, card, device, flags);
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: failed to open plugin\n", __func__);
|
||||
goto err_open;
|
||||
}
|
||||
|
||||
plug_data->dl_hdl = dl_hdl;
|
||||
plug_data->card = card;
|
||||
plug_data->device = device;
|
||||
plug_data->dev_node = pcm_node;
|
||||
plug_data->flags = flags;
|
||||
|
||||
*data = plug_data;
|
||||
|
||||
plug_data->plugin->state = PCM_PLUG_STATE_OPEN;
|
||||
|
||||
return 0;
|
||||
|
||||
err_open:
|
||||
err_dlsym:
|
||||
dlclose(dl_hdl);
|
||||
err_get_lib:
|
||||
err_dl_open:
|
||||
free(plug_data);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
const struct pcm_ops plug_ops = {
|
||||
.open = pcm_plug_open,
|
||||
.close = pcm_plug_close,
|
||||
.ioctl = pcm_plug_ioctl,
|
||||
};
|
||||
@@ -0,0 +1,149 @@
|
||||
/* snd_card_plugin.c
|
||||
** Copyright (c) 2019, The Linux Foundation.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above
|
||||
** copyright notice, this list of conditions and the following
|
||||
** disclaimer in the documentation and/or other materials provided
|
||||
** with the distribution.
|
||||
** * Neither the name of The Linux Foundation nor the names of its
|
||||
** contributors may be used to endorse or promote products derived
|
||||
** from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
|
||||
** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||
** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**/
|
||||
|
||||
#include "snd_card_plugin.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define SND_DLSYM(h, p, s, err) \
|
||||
do { \
|
||||
err = 0; \
|
||||
p = dlsym(h, s); \
|
||||
if (!p) \
|
||||
err = -ENODEV; \
|
||||
} while(0)
|
||||
|
||||
int snd_utils_get_int(struct snd_node *node, const char *prop, int *val)
|
||||
{
|
||||
if (!node || !node->card_node || !node->dev_node)
|
||||
return SND_NODE_TYPE_HW;
|
||||
|
||||
return node->ops->get_int(node->dev_node, prop, val);
|
||||
}
|
||||
|
||||
int snd_utils_get_str(struct snd_node *node, const char *prop, char **val)
|
||||
{
|
||||
if (!node || !node->card_node || !node->dev_node)
|
||||
return SND_NODE_TYPE_HW;
|
||||
|
||||
return node->ops->get_str(node->dev_node, prop, val);
|
||||
}
|
||||
|
||||
void snd_utils_close_dev_node(struct snd_node *node)
|
||||
{
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
if (node->card_node)
|
||||
node->ops->close_card(node->card_node);
|
||||
|
||||
if (node->dl_hdl)
|
||||
dlclose(node->dl_hdl);
|
||||
|
||||
free(node);
|
||||
}
|
||||
|
||||
enum snd_node_type snd_utils_get_node_type(struct snd_node *node)
|
||||
{
|
||||
int val = SND_NODE_TYPE_HW;
|
||||
|
||||
if (!node || !node->card_node || !node->dev_node)
|
||||
return SND_NODE_TYPE_HW;
|
||||
|
||||
node->ops->get_int(node->dev_node, "type", &val);
|
||||
|
||||
return val;
|
||||
};
|
||||
|
||||
static int snd_utils_resolve_symbols(struct snd_node *node)
|
||||
{
|
||||
void *dl = node->dl_hdl;
|
||||
int err;
|
||||
SND_DLSYM(dl, node->ops, "snd_card_ops", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct snd_node *snd_utils_open_dev_node(unsigned int card,
|
||||
unsigned int device,
|
||||
int dev_type)
|
||||
{
|
||||
struct snd_node *node;
|
||||
int rc = 0;
|
||||
|
||||
node = calloc(1, sizeof(*node));
|
||||
if (!node)
|
||||
return NULL;
|
||||
|
||||
node->dl_hdl = dlopen("libsndcardparser.so", RTLD_NOW);
|
||||
if (!node->dl_hdl) {
|
||||
goto err_dl_open;
|
||||
}
|
||||
|
||||
rc = snd_utils_resolve_symbols(node);
|
||||
if (rc < 0)
|
||||
goto err_resolve_symbols;
|
||||
|
||||
node->card_node = node->ops->open_card(card);
|
||||
if (!node->card_node)
|
||||
goto err_resolve_symbols;
|
||||
|
||||
if (dev_type == NODE_PCM) {
|
||||
node->dev_node = node->ops->get_pcm(node->card_node, device);
|
||||
} else {
|
||||
node->dev_node = node->ops->get_mixer(node->card_node);
|
||||
}
|
||||
|
||||
if (!node->dev_node)
|
||||
goto err_get_node;
|
||||
|
||||
return node;
|
||||
|
||||
err_get_node:
|
||||
node->ops->close_card(node->card_node);
|
||||
|
||||
err_resolve_symbols:
|
||||
dlclose(node->dl_hdl);
|
||||
|
||||
err_dl_open:
|
||||
free(node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct snd_node* snd_utils_open_pcm(unsigned int card,
|
||||
unsigned int device)
|
||||
{
|
||||
return snd_utils_open_dev_node(card, device, NODE_PCM);
|
||||
}
|
||||
|
||||
struct snd_node* snd_utils_open_mixer(unsigned int card)
|
||||
{
|
||||
return snd_utils_open_dev_node(card, 0, NODE_MIXER);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/* snd_utils.h
|
||||
** Copyright (c) 2019, The Linux Foundation.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above
|
||||
** copyright notice, this list of conditions and the following
|
||||
** disclaimer in the documentation and/or other materials provided
|
||||
** with the distribution.
|
||||
** * Neither the name of The Linux Foundation nor the names of its
|
||||
** contributors may be used to endorse or promote products derived
|
||||
** from this software without specific prior written permission.
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
|
||||
** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
** BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
** OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||
** IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**/
|
||||
|
||||
#ifndef TINYALSA_SRC_SND_CARD_UTILS_H
|
||||
#define TINYALSA_SRC_SND_CARD_UTILS_H
|
||||
|
||||
#include <tinyalsa/plugin.h>
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
/** Encapsulates the pcm device definition from
|
||||
* the sound card definition configuration file.
|
||||
*/
|
||||
struct snd_node {
|
||||
/** Pointer the card definition */
|
||||
void *card_node;
|
||||
/** Pointer to device definition, either PCM or MIXER device */
|
||||
void *dev_node;
|
||||
/** Pointer to the sound card parser library */
|
||||
void *dl_hdl;
|
||||
/** A pointer to the operations structure. */
|
||||
const struct snd_node_ops* ops;
|
||||
};
|
||||
|
||||
enum snd_node_type {
|
||||
SND_NODE_TYPE_HW = 0,
|
||||
SND_NODE_TYPE_PLUGIN,
|
||||
SND_NODE_TYPE_INVALID,
|
||||
};
|
||||
|
||||
enum {
|
||||
NODE_PCM,
|
||||
NODE_MIXER
|
||||
};
|
||||
|
||||
struct snd_node *snd_utils_open_pcm(unsigned int card, unsigned int device);
|
||||
|
||||
struct snd_node *snd_utils_open_mixer(unsigned int card);
|
||||
|
||||
void snd_utils_close_dev_node(struct snd_node *node);
|
||||
|
||||
enum snd_node_type snd_utils_get_node_type(struct snd_node *node);
|
||||
|
||||
int snd_utils_get_int(struct snd_node *node, const char *prop, int *val);
|
||||
|
||||
int snd_utils_get_str(struct snd_node *node, const char *prop, char **val);
|
||||
|
||||
#endif /* end of TINYALSA_SRC_SND_CARD_UTILS_H */
|
||||
+4
-4
@@ -19,19 +19,19 @@ VPATH = ../src:../include/tinyalsa
|
||||
.PHONY: all
|
||||
all: -ltinyalsa tinyplay tinycap tinymix tinypcminfo
|
||||
|
||||
tinyplay: tinyplay.o libtinyalsa.a
|
||||
tinyplay: tinyplay.o libtinyalsa.a -ldl
|
||||
|
||||
tinyplay.o: tinyplay.c pcm.h mixer.h asoundlib.h
|
||||
|
||||
tinycap: tinycap.o libtinyalsa.a
|
||||
tinycap: tinycap.o libtinyalsa.a -ldl
|
||||
|
||||
tinycap.o: tinycap.c pcm.h mixer.h asoundlib.h
|
||||
|
||||
tinymix: tinymix.o libtinyalsa.a
|
||||
tinymix: tinymix.o libtinyalsa.a -ldl
|
||||
|
||||
tinymix.o: tinymix.c pcm.h mixer.h asoundlib.h
|
||||
|
||||
tinypcminfo: tinypcminfo.o libtinyalsa.a
|
||||
tinypcminfo: tinypcminfo.o libtinyalsa.a -ldl
|
||||
|
||||
tinypcminfo.o: tinypcminfo.c pcm.h mixer.h asoundlib.h
|
||||
|
||||
|
||||
Reference in New Issue
Block a user