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