app修改mixer ctrl

访问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目录下;

app修改mixer ctrl

上一篇:Ubuntu20.04部署GitLab


下一篇:《Linux命令行与shell脚本编程大全》第十八章 图形化桌面环境中的脚本编程