Android中对音频多声道数据的配置binary tinymix是tinyplay执行前必要的步骤,对tinymix的源码进行check在整合tinyalsa相关通道配置以及流程上是非常有必要的。tinymix梳理之后还是比较简单的,主要有mixer_open, mixer_close, tinymix_list_controls, tinymix_detail_control, tinymix_set_value这个几个方法。
1.tinymix.c
file path: external/tinyalsa/tinymix.c
int main(int argc, char **argv)
{
struct mixer *mixer;
int card = 0;
int ret = 0;
while (1) {
int option_index = 0;
int option_char = 0;
option_char = getopt_long(argc, argv, tinymix_short_options,
tinymix_long_options, &option_index);
if (option_char == -1)
break;
switch (option_char) {
case 'D':
...
}
//mixer_open打开对应card的mixer配置,默认为0
mixer = mixer_open(card);
if (!mixer) {
fprintf(stderr, "Failed to open mixer\n");
return ENODEV;
}
if (argc == optind) {
printf("Mixer name: '%s'\n", mixer_get_name(mixer));
//查看所有的mixer配置参数
tinymix_list_controls(mixer);
} else if (argc == optind + 1) {
//查看某一项mixer配置参数
ret = tinymix_detail_control(mixer, argv[optind], !g_value_only, !g_value_only);
} else if (argc >= optind + 2) {
//通过mixer设置通道参数
ret = tinymix_set_value(mixer, argv[optind], &argv[optind + 1], argc - optind - 1);
}
//最后通过mixer_close关闭mixer配置
mixer_close(mixer);
return ret;
}
2.tinytest.c
参考tinymix.c写入相关的音频配置参数S_NORMAL_AP01_C_CODEC SWITCH 和 S_VOICE_C_CODEC SWITCH都写为1,on的状态。
int main(int argc, char **argv)
{
struct mixer *mixer;
int card = 0;
int i;
char *control1 = "S_NORMAL_AP01_C_CODEC SWITCH";
char *control2 = "S_VOICE_C_CODEC SWITCH";
char *values = "1";
if ((argc > 2) && (strcmp(argv[1], "-D") == 0)) {
argv++;
if (argv[1]) {
card = atoi(argv[1]);
argv++;
argc -= 2;
} else {
argc -= 1;
}
}
mixer = mixer_open(card);
if (!mixer) {
DEBUG_Log_Err("Failed to open mixer\n");
return EXIT_FAILURE;
}
DEBUG_Log_Info("shengjie-setmixer");
tinymix_set_value(mixer, control1, &values, 1);
tinymix_set_value(mixer, control2, &values, 1);
mixer_close(mixer);
return 0;
}
3.tinymix-tinymix_list_controls
这边可以显示出所有的mixer配置的音频参数通道以及通过tinymix_detail_control查看通道现在的值,具体使用方法如下图所示。
static void tinymix_list_controls(struct mixer *mixer)
{
struct mixer_ctl *ctl;
const char *name, *type;
unsigned int num_ctls, num_values;
unsigned int i;
num_ctls = mixer_get_num_ctls(mixer);
printf("Number of controls: %u\n", num_ctls);
if (g_tabs_only)
printf("ctl\ttype\tnum\tname\tvalue");
else
printf("ctl\ttype\tnum\t%-40s value\n", "name");
if (g_all_values)
printf("\trange/values\n");
else
printf("\n");
for (i = 0; i < num_ctls; i++) {
ctl = mixer_get_ctl(mixer, i);
name = mixer_ctl_get_name(ctl);
type = mixer_ctl_get_type_string(ctl);
num_values = mixer_ctl_get_num_values(ctl);
if (g_tabs_only)
printf("%d\t%s\t%d\t%s\t", i, type, num_values, name);
else
printf("%d\t%s\t%d\t%-40s ", i, type, num_values, name);
tinymix_detail_control(mixer, name, 0, g_all_values);
}
}
4.tinymix-tinymix_detail_control
tinymix_detail_control接口目的就是查看具体音频通道的配置信息,具体使用方法如下图所示。
static int tinymix_detail_control(struct mixer *mixer, const char *control,
int prefix, int print_all)
{
struct mixer_ctl *ctl;
enum mixer_ctl_type type;
unsigned int num_values;
unsigned int i;
int min, max;
int ret;
char *buf = NULL;
size_t len;
unsigned int tlv_header_size = 0;
const char *space = g_tabs_only ? "\t" : " ";
if (isdigit(control[0]))
ctl = mixer_get_ctl(mixer, atoi(control));
else
ctl = mixer_get_ctl_by_name(mixer, control);
if (!ctl) {
fprintf(stderr, "Invalid mixer control: %s\n", control);
return ENOENT;
}
type = mixer_ctl_get_type(ctl);
num_values = mixer_ctl_get_num_values(ctl);
if (type == MIXER_CTL_TYPE_BYTE) {
if (mixer_ctl_is_access_tlv_rw(ctl)) {
tlv_header_size = TLV_HEADER_SIZE;
}
buf = calloc(1, num_values + tlv_header_size);
if (buf == NULL) {
fprintf(stderr, "Failed to alloc mem for bytes %d\n", num_values);
return ENOENT;
}
len = num_values;
ret = mixer_ctl_get_array(ctl, buf, len + tlv_header_size);
if (ret < 0) {
fprintf(stderr, "Failed to mixer_ctl_get_array\n");
free(buf);
return ENOENT;
}
}
printf("shengjie-%s, num:%d", mixer_ctl_get_name(ctl), num_values);
if (prefix)
printf("%s:%s", mixer_ctl_get_name(ctl), space);
for (i = 0; i < num_values; i++) {
switch (type)
{
case MIXER_CTL_TYPE_INT:
printf("%d", mixer_ctl_get_value(ctl, i));
break;
case MIXER_CTL_TYPE_BOOL:
printf("%s", mixer_ctl_get_value(ctl, i) ? "On" : "Off");
break;
case MIXER_CTL_TYPE_ENUM:
tinymix_print_enum(ctl, space, print_all);
break;
case MIXER_CTL_TYPE_BYTE:
/* skip printing TLV header if exists */
printf(" %02x", buf[i + tlv_header_size]);
break;
default:
printf("unknown");
break;
}
if (i < num_values - 1)
printf("%s", space);
}
if (print_all) {
if (type == MIXER_CTL_TYPE_INT) {
min = mixer_ctl_get_range_min(ctl);
max = mixer_ctl_get_range_max(ctl);
printf("%s(dsrange %d->%d)", space, min, max);
}
}
free(buf);
printf("\n");
return 0;
}
5.tinymix-tinymix_set_value
tinymix_set_value接口在tinymix使用中应该是最重要的,所有的配置都需要这个接口进行控制,应用层或Audio HAL层调用的set audio mixer参数都要通过mixer_open、tinymix_set_value、mixer_close接口。下图所示是包含配置增益的参数配置check。
static int tinymix_set_value(struct mixer *mixer, const char *control,
char **values, unsigned int num_values)
{
struct mixer_ctl *ctl;
enum mixer_ctl_type type;
unsigned int num_ctl_values;
unsigned int i;
if (isdigit(control[0]))
ctl = mixer_get_ctl(mixer, atoi(control));
else
ctl = mixer_get_ctl_by_name(mixer, control);
if (!ctl) {
fprintf(stderr, "Invalid mixer control: %s\n", control);
return ENOENT;
}
type = mixer_ctl_get_type(ctl);
num_ctl_values = mixer_ctl_get_num_values(ctl);
if (type == MIXER_CTL_TYPE_BYTE) {
tinymix_set_byte_ctl(ctl, values, num_values);
return ENOENT;
}
if (isdigit(values[0][0])) {
if (num_values == 1) {
/* Set all values the same */
int value = atoi(values[0]);
for (i = 0; i < num_ctl_values; i++) {
if (mixer_ctl_set_value(ctl, i, value)) {
fprintf(stderr, "Error: invalid value\n");
return EINVAL;
}
}
} else {
/* Set multiple values */
if (num_values > num_ctl_values) {
fprintf(stderr,
"Error: %u values given, but control only takes %u\n",
num_values, num_ctl_values);
return EINVAL;
}
for (i = 0; i < num_values; i++) {
if (mixer_ctl_set_value(ctl, i, atoi(values[i]))) {
fprintf(stderr, "Error: invalid value for index %d\n", i);
return EINVAL;
}
}
}
} else {
if (type == MIXER_CTL_TYPE_ENUM) {
if (num_values != 1) {
fprintf(stderr, "Enclose strings in quotes and try again\n");
return EINVAL;
}
if (mixer_ctl_set_enum_by_string(ctl, values[0])) {
fprintf(stderr, "Error: invalid enum value\n");
return EINVAL;
}
} else {
fprintf(stderr, "Error: only enum types can be set with strings\n");
return EINVAL;
}
}
return 0;
}
小结
mixer和pcm一起构成了android 音频控制的底层架构TinyAlsa,对Android所有的音频控件而言,都需要通过JNI或者Audio HAL调到tinyalsa底层的mixer、pcm去进行音频device的初始以及配置问题。
mixer里面有提供很多灵活的接口供linux底层,比如mixer_get_ctl、mixer_ctl_get_name、mixer_get_ctl_by_name、mixer_ctl_get_num_values等,相关的开发任务关键在于check API的熟悉度。