1 /*
2 * Copyright (c) 2023 Centralp
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <stdlib.h>
8 #include <zephyr/shell/shell.h>
9 #include <zephyr/audio/codec.h>
10
11 #define CODEC_START_HELP \
12 SHELL_HELP("Start output audio playback", \
13 "<device>")
14
15 #define CODEC_STOP_HELP \
16 SHELL_HELP("Stop output audio playback", \
17 "<device>")
18
19 #define CODEC_SET_PROP_HELP \
20 SHELL_HELP("Set a codec property", \
21 "<device> <property> <channel> <value>")
22
23 #define CODEC_APPLY_PROP_HELP \
24 SHELL_HELP("Apply any cached properties", \
25 "<device>")
26
27 static const char *const codec_property_name[] = {
28 [AUDIO_PROPERTY_OUTPUT_VOLUME] = "volume",
29 [AUDIO_PROPERTY_OUTPUT_MUTE] = "mute",
30 };
31
32 static const char *const codec_channel_name[] = {
33 [AUDIO_CHANNEL_FRONT_LEFT] = "front_left",
34 [AUDIO_CHANNEL_FRONT_RIGHT] = "front_right",
35 [AUDIO_CHANNEL_LFE] = "lfe",
36 [AUDIO_CHANNEL_FRONT_CENTER] = "front_center",
37 [AUDIO_CHANNEL_REAR_LEFT] = "rear_left",
38 [AUDIO_CHANNEL_REAR_RIGHT] = "rear_right",
39 [AUDIO_CHANNEL_REAR_CENTER] = "rear_center",
40 [AUDIO_CHANNEL_SIDE_LEFT] = "side_left",
41 [AUDIO_CHANNEL_SIDE_RIGHT] = "side_right",
42 [AUDIO_CHANNEL_HEADPHONE_LEFT] = "headphone_left",
43 [AUDIO_CHANNEL_HEADPHONE_RIGHT] = "headphone_right",
44 [AUDIO_CHANNEL_ALL] = "all",
45 };
46
47 struct args_index {
48 uint8_t device;
49 uint8_t property;
50 uint8_t channel;
51 uint8_t value;
52 };
53
54 static const struct args_index args_indx = {
55 .device = 1,
56 .property = 2,
57 .channel = 3,
58 .value = 4,
59 };
60
parse_named_int(const char * name,const char * const keystack[],size_t count)61 static int parse_named_int(const char *name, const char *const keystack[], size_t count)
62 {
63 char *endptr;
64 int i;
65
66 /* Attempt to parse name as a number first */
67 i = strtoul(name, &endptr, 0);
68 if (*endptr == '\0') {
69 return i;
70 }
71
72 /* Name is not a number, look it up */
73 for (i = 0; i < count; i++) {
74 if (strcmp(name, keystack[i]) == 0) {
75 return i;
76 }
77 }
78
79 return -ENOTSUP;
80 }
81
cmd_start(const struct shell * sh,size_t argc,char * argv[])82 static int cmd_start(const struct shell *sh, size_t argc, char *argv[])
83 {
84 const struct device *dev;
85
86 dev = shell_device_get_binding(argv[args_indx.device]);
87 if (!dev) {
88 shell_error(sh, "Audio Codec device not found");
89 return -ENODEV;
90 }
91 audio_codec_start_output(dev);
92
93 return 0;
94 }
95
cmd_stop(const struct shell * sh,size_t argc,char * argv[])96 static int cmd_stop(const struct shell *sh, size_t argc, char *argv[])
97 {
98 const struct device *dev;
99
100 dev = shell_device_get_binding(argv[args_indx.device]);
101 if (!dev) {
102 shell_error(sh, "Audio Codec device not found");
103 return -ENODEV;
104 }
105 audio_codec_stop_output(dev);
106
107 return 0;
108 }
109
cmd_set_prop(const struct shell * sh,size_t argc,char * argv[])110 static int cmd_set_prop(const struct shell *sh, size_t argc, char *argv[])
111 {
112 const struct device *dev;
113 int property;
114 int channel;
115 long value;
116 char *endptr;
117 audio_property_value_t property_value;
118
119 dev = shell_device_get_binding(argv[args_indx.device]);
120 if (!dev) {
121 shell_error(sh, "Audio Codec device not found");
122 return -ENODEV;
123 }
124
125 property = parse_named_int(argv[args_indx.property], codec_property_name,
126 ARRAY_SIZE(codec_property_name));
127 if (property < 0) {
128 shell_error(sh, "Property '%s' unknown", argv[args_indx.property]);
129 return -EINVAL;
130 }
131
132 channel = parse_named_int(argv[args_indx.channel], codec_channel_name,
133 ARRAY_SIZE(codec_channel_name));
134 if (channel < 0) {
135 shell_error(sh, "Channel '%s' unknown", argv[args_indx.channel]);
136 return -EINVAL;
137 }
138
139 value = strtol(argv[args_indx.value], &endptr, 0);
140 if (*endptr != '\0') {
141 return -EINVAL;
142 }
143 if (value > INT32_MAX || value < INT32_MIN) {
144 return -EINVAL;
145 }
146 switch (property) {
147 case AUDIO_PROPERTY_OUTPUT_VOLUME:
148 property_value.vol = value;
149 break;
150 case AUDIO_PROPERTY_OUTPUT_MUTE:
151 property_value.mute = value;
152 break;
153 default:
154 return -EINVAL;
155 }
156
157 return audio_codec_set_property(dev, property, channel, property_value);
158 }
159
cmd_apply_prop(const struct shell * sh,size_t argc,char * argv[])160 static int cmd_apply_prop(const struct shell *sh, size_t argc, char *argv[])
161 {
162 const struct device *dev;
163
164 dev = shell_device_get_binding(argv[args_indx.device]);
165 if (!dev) {
166 shell_error(sh, "Audio Codec device not found");
167 return -ENODEV;
168 }
169
170 return audio_codec_apply_properties(dev);
171 }
172
173 /* Device name autocompletion support */
device_name_get(size_t idx,struct shell_static_entry * entry)174 static void device_name_get(size_t idx, struct shell_static_entry *entry)
175 {
176 const struct device *dev = shell_device_lookup(idx, NULL);
177
178 entry->syntax = (dev != NULL) ? dev->name : NULL;
179 entry->handler = NULL;
180 entry->help = NULL;
181 entry->subcmd = NULL;
182 }
183
184 SHELL_DYNAMIC_CMD_CREATE(dsub_device_name, device_name_get);
185
186 /* clang-format off */
187 SHELL_STATIC_SUBCMD_SET_CREATE(sub_codec,
188 SHELL_CMD_ARG(start, &dsub_device_name, CODEC_START_HELP, cmd_start,
189 2, 0),
190 SHELL_CMD_ARG(stop, &dsub_device_name, CODEC_STOP_HELP, cmd_stop,
191 2, 0),
192 SHELL_CMD_ARG(set_prop, &dsub_device_name, CODEC_SET_PROP_HELP, cmd_set_prop,
193 5, 0),
194 SHELL_CMD_ARG(apply_prop, &dsub_device_name, CODEC_APPLY_PROP_HELP, cmd_apply_prop,
195 2, 0),
196 SHELL_SUBCMD_SET_END
197 );
198 /* clang-format on */
199
200 SHELL_CMD_REGISTER(codec, &sub_codec, "Audio Codec commands", NULL);
201