访问mixer
在调试audio时,大多数场景下可以通过tinymix命令,可以直接访问控制注册到audio子系统的kcontrol节点;
在android audio的开发过程中,有需求通过app直接访问到这部分的控制,可以通过jni 本地方法访问声卡
mixer来完成;
创建一个声卡,会对应一个snd control节点,它的路径是:
/dev/snd/controlC0
可以通过open 该节点,通过IOCTL来访问声卡;
对于mixer ctrl的访问,可以参考tinyalsa 中tinymix的实现;
其中mixer的定义:
struct mixer {
int fd;
struct snd_ctl_card_info card_info;
struct snd_ctl_elem_info *elem_info;
struct mixer_ctl *ctl;
unsigned int count;
};
需要访问的具体的mixer ctrl,它的定义:
struct mixer_ctl {
struct mixer *mixer;
struct snd_ctl_elem_info *info;
char **ename;
bool info_retrieved;
};
打开 mixer
struct mixer *mixer_open(unsigned int card)
{
struct mixer *mixer = NULL;
//......
snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card);
fd = open(fn, O_RDWR);
memset(&elist, 0, sizeof(elist));
ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist)
//......
mixer = calloc(1, sizeof(*mixer));
//......
mixer->ctl = calloc(elist.count, sizeof(struct mixer_ctl));
mixer->elem_info = calloc(elist.count, sizeof(struct snd_ctl_elem_info));
ioctl(fd, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info)
eid = calloc(elist.count, sizeof(struct snd_ctl_elem_id));
mixer->count = elist.count;
mixer->fd = fd;
elist.space = mixer->count;
elist.pids = eid;
if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
goto fail;
for (n = 0; n < mixer->count; n++) {
struct mixer_ctl *ctl = mixer->ctl + n;
ctl->mixer = mixer;
ctl->info = mixer->elem_info + n;
ctl->info->id.numid = eid[n].numid;
strncpy((char *)ctl->info->id.name, (char *)eid[n].name,
SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
ctl->info->id.name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 0;
}
return mixer;
}
根据name获取mixer ctl
根据name 获取mixer ctrl;
struct mixer_ctl *mixer_get_ctl_by_name(struct mixer *mixer, const char *name)
{
//......
for (n = 0; n < mixer->count; n++)
if (!strcmp(name, (char*) mixer->elem_info[n].id.name))
return mixer_get_ctl(mixer, n);
//......
}
设置mixer ctrl
根据不同的mixer ctrl 获取其参数类型(bool,int,bytes...),设置其value
int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value)
{
struct snd_ctl_elem_value ev;
int ret;
memset(&ev, 0, sizeof(ev));
ev.id.numid = ctl->info->id.numid;
ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
//......
switch (ctl->info->type) {
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
ev.value.integer.value[id] = !!value;
break;
case SNDRV_CTL_ELEM_TYPE_INTEGER:
ev.value.integer.value[id] = value;
break;
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
ev.value.enumerated.item[id] = value;
break;
case SNDRV_CTL_ELEM_TYPE_BYTES:
ev.value.bytes.data[id] = value;
break;
default:
return -EINVAL;
}
return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
}
JNI实现访问
提供一个简单的JNI实现, 对"RX1 Digital Volume", "RX2 Digital Volume" 这两个控件的设置;
包含头文件:
#include "asoundlib.h"
JNI访问mixer 函数SetCtl
static jint
SetCtl(JNIEnv *_env, jclass _this, jint a, jint b) {
struct mixer *mixer;
struct mixer_ctl *ctl1;
struct mixer_ctl *ctl2;
int card = 0;
int ret = 0;
ALOGD("SetCtl %d %d", a, b);
mixer = mixer_open(card);
if (!mixer) {
fprintf(stderr, "Failed to open mixer\n");
ALOGE("Failed to open mixer\n");
return ENODEV;
}
ctl1 = mixer_get_ctl_by_name(mixer, "RX1 Digital Volume");
ctl2 = mixer_get_ctl_by_name(mixer, "RX2 Digital Volume");
if(a == 1)
{
if (mixer_ctl_set_value(ctl1, 0, b)) {
fprintf(stderr, "Error: invalid value\n");
ALOGE("Error: invalid value\n");
return EINVAL;
}
}
if(a == 2)
{
if (mixer_ctl_set_value(ctl2, 0, b)) {
fprintf(stderr, "Error: invalid value\n");
ALOGE("Error: invalid value\n");
return EINVAL;
}
}
mixer_close(mixer);
ALOGD("SetCtl end");
return ret;
}
JNI demo
JNI的实现可以参考 android源码中的demo;
路径如下:development/samples/SimpleJNI/
java代码中,加载jni的类的路径定义:
static const char *classPathName = "com/example/setmixerctl/Native";
JNI native方法映射的定义:
static JNINativeMethod methods[] = {
{"SetMixerCtl", "(II)I", (void*)SetCtl },
};
JNI_OnLoad和register函数:
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
ALOGE("Native registration unable to find class '%s'", className);
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
ALOGE("RegisterNatives failed for '%s'", className);
return JNI_FALSE;
}
return JNI_TRUE;
}
static int registerNatives(JNIEnv* env)
{
if (!registerNativeMethods(env, classPathName,
methods, sizeof(methods) / sizeof(methods[0]))) {
return JNI_FALSE;
}
return JNI_TRUE;
}
typedef union {
JNIEnv* env;
void* venv;
} UnionJNIEnvToVoid;
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
UnionJNIEnvToVoid uenv;
uenv.venv = NULL;
jint result = -1;
JNIEnv* env = NULL;
ALOGI("JNI_OnLoad");
if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("ERROR: GetEnv failed");
goto bail;
}
env = uenv.env;
if (registerNatives(env) != JNI_TRUE) {
ALOGE("ERROR: registerNatives failed");
goto bail;
}
result = JNI_VERSION_1_4;
bail:
return result;
}
编译jni so文件
Android.mk的内容如下,编译jni so库文件;
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_CFLAGS += -Wno-unused-parameter
LOCAL_MODULE:= libsetmixerctljni
LOCAL_SRC_FILES:= \
SetMixerCtl.cpp mixer.c
LOCAL_LDLIBS := -llog
LOCAL_STATIC_LIBRARIES :=
#LOCAL_CFLAGS := -Wall -Werror
LOCAL_NDK_STL_VARIANT := none
LOCAL_SDK_VERSION := current
include $(BUILD_SHARED_LIBRARY)
App Demo
通过android studio创建一个 空白app; 在MainActivity中实现native方法的访问:
package com.example.setmixerctl;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Native.SetMixerCtl(1, 60);
Native.SetMixerCtl(2, 50);
}
}
class Native {
static {
// The runtime will add "lib" on the front and ".o" on the end of
// the name supplied to loadLibrary.
System.loadLibrary("setmixerctljni");
}
static native int SetMixerCtl(int ctl, int value);
//ctl=1: "RX1 Digital Volume"
//ctl=2: "RX2 Digital Volume"
//value: 0-124
}
JNI 库文件处理
通常可以将编译生成 libsetmixerctljni.so放到 system/lib64/ 下;
由于android后期版本安全的限制,第三方非白名单app 无法直接访问,建议将 jni的so库文件,打包到app中;
在Android studio中,源码工程java同级目录下,创建 jniLibs目录(默认是这个目录,如果放在其他路径,需修改android studio中的build.gradle)
创建平台目录;比如arm64-v8a;将libsetmixerctljni.so 存放自此路径下,编译app,即可将libsetmixerctljni.so 打包到lib目录下;