13 package ai.picovoice.porcupinedemo;
16 import org.apache.commons.cli.*;
18 import javax.sound.sampled.AudioFormat;
19 import javax.sound.sampled.AudioInputStream;
20 import javax.sound.sampled.AudioSystem;
21 import javax.sound.sampled.UnsupportedAudioFileException;
23 import java.io.IOException;
24 import java.nio.ByteBuffer;
25 import java.nio.ByteOrder;
26 import java.util.Arrays;
30 public static void runDemo(String accessKey, File inputAudioFile, String libPath,
31 String modelPath, String[] keywordPaths,
float[] sensitivities) {
34 String[]
keywords =
new String[keywordPaths.length];
35 for (
int i = 0; i < keywordPaths.length; i++) {
36 File keywordFile =
new File(keywordPaths[i]);
37 if (!keywordFile.exists())
38 throw new IllegalArgumentException(String.format(
"Keyword file at '%s' " +
39 "does not exist", keywordPaths[i]));
40 keywords[i] = keywordFile.getName().split(
"_")[0];
43 AudioInputStream audioInputStream;
45 audioInputStream = AudioSystem.getAudioInputStream(inputAudioFile);
46 }
catch (UnsupportedAudioFileException e) {
47 System.err.println(
"Audio format not supported. Please provide an input file of .au, .aiff or .wav format");
49 }
catch (IOException e) {
50 System.err.println(
"Could not find input audio file at " + inputAudioFile);
64 AudioFormat audioFormat = audioInputStream.getFormat();
66 if (audioFormat.getSampleRate() != 16000.0f || audioFormat.getSampleSizeInBits() != 16) {
67 throw new IllegalArgumentException(String.format(
"Invalid input audio file format. " +
68 "Input file must be a %dkHz, 16-bit audio file.", porcupine.
getSampleRate()));
71 if (audioFormat.getChannels() > 1) {
72 System.out.println(
"Picovoice processes single-channel audio, but a multi-channel file was provided. " +
73 "Processing leftmost channel only.");
77 long totalSamplesRead = 0;
80 ByteBuffer sampleBuffer = ByteBuffer.allocate(audioFormat.getFrameSize());
81 sampleBuffer.order(ByteOrder.LITTLE_ENDIAN);
82 while (audioInputStream.available() != 0) {
85 int numBytesRead = audioInputStream.read(sampleBuffer.array());
86 if (numBytesRead < 2) {
90 porcupineFrame[frameIndex++] = sampleBuffer.getShort(0);
92 if (frameIndex == porcupineFrame.length) {
93 int result = porcupine.
process(porcupineFrame);
95 System.out.printf(
"Detected '%s' at %.02f sec\n",
keywords[result],
102 }
catch (Exception e) {
103 System.out.println(e.toString());
105 if (porcupine !=
null) {
114 CommandLineParser parser =
new DefaultParser();
115 HelpFormatter formatter =
new HelpFormatter();
119 cmd = parser.parse(options,
args);
120 }
catch (ParseException e) {
121 System.out.println(e.getMessage());
122 formatter.printHelp(
"porcupinefiledemo", options);
127 if (
cmd.hasOption(
"help")) {
128 formatter.printHelp(
"porcupinefiledemo", options);
132 String accessKey =
cmd.getOptionValue(
"access_key");
133 String inputAudioPath =
cmd.getOptionValue(
"input_audio_path");
134 String libraryPath =
cmd.getOptionValue(
"library_path");
135 String modelPath =
cmd.getOptionValue(
"model_path");
136 String[]
keywords =
cmd.getOptionValues(
"keywords");
137 String[] keywordPaths =
cmd.getOptionValues(
"keyword_paths");
138 String[] sensitivitiesStr =
cmd.getOptionValues(
"sensitivities");
141 float[] sensitivities =
null;
142 if (sensitivitiesStr !=
null) {
143 sensitivities =
new float[sensitivitiesStr.length];
145 for (
int i = 0; i < sensitivitiesStr.length; i++) {
148 sensitivity = Float.parseFloat(sensitivitiesStr[i]);
149 }
catch (Exception e) {
150 throw new IllegalArgumentException(
"Failed to parse sensitivity value. " +
151 "Must be a floating-point number between [0,1].");
154 if (sensitivity < 0 || sensitivity > 1) {
155 throw new IllegalArgumentException(String.format(
"Failed to parse sensitivity value (%s). " +
156 "Must be a floating-point number between [0,1].", sensitivitiesStr[i]));
158 sensitivities[i] = sensitivity;
162 if (accessKey ==
null || accessKey.length() == 0) {
163 throw new IllegalArgumentException(
"AccessKey is required for Porcupine.");
166 if (inputAudioPath ==
null){
167 throw new IllegalArgumentException(
"No input audio file provided. This is a required argument.");
169 File inputAudioFile =
new File(inputAudioPath);
170 if (!inputAudioFile.exists()) {
171 throw new IllegalArgumentException(String.format(
"Audio file at path %s does not exits.", inputAudioPath));
174 if (libraryPath ==
null) {
178 if (modelPath ==
null) {
182 if (keywordPaths ==
null || keywordPaths.length == 0) {
184 throw new IllegalArgumentException(
"Either '--keywords' or '--keyword_paths' must be set.");
187 keywordPaths =
new String[
keywords.length];
188 for (
int i = 0; i <
keywords.length; i++) {
189 final String keyword =
keywords[i].toUpperCase().replace(
" ",
"_");
193 }
catch (Exception e) {
194 throw new IllegalArgumentException(String.format(
"'%s' not a built-in keyword", keyword));
199 if (sensitivities ==
null) {
200 sensitivities =
new float[keywordPaths.length];
201 Arrays.fill(sensitivities, 0.5
f);
204 if (sensitivities.length != keywordPaths.length) {
205 throw new IllegalArgumentException(String.format(
"Number of keywords (%d) does " +
206 "not match number of sensitivities (%d)", keywordPaths.length, sensitivities.length));
209 runDemo(accessKey, inputAudioFile, libraryPath, modelPath, keywordPaths, sensitivities);
213 Options options =
new Options();
215 options.addOption(Option.builder(
"a")
216 .longOpt(
"access_key")
218 .desc(
"AccessKey obtained from Picovoice Console (https://picovoice.ai/console/).")
221 options.addOption(Option.builder(
"i")
222 .longOpt(
"input_audio_path")
224 .desc(
"Absolute path to input audio file.")
227 options.addOption(Option.builder(
"l")
228 .longOpt(
"library_path")
230 .desc(
"Absolute path to the Porcupine native runtime library.")
233 options.addOption(Option.builder(
"m")
234 .longOpt(
"model_path")
236 .desc(
"Absolute path to the file containing model parameters.")
239 options.addOption(Option.builder(
"k")
245 options.addOption(Option.builder(
"kp")
246 .longOpt(
"keyword_paths")
248 .desc(
"Absolute paths to keyword model files.")
251 options.addOption(Option.builder(
"s")
252 .longOpt(
"sensitivities")
254 .desc(
"Sensitivities for detecting keywords. Each value should be a number within [0, 1]. A higher " +
255 "sensitivity results in fewer misses at the cost of increasing the false alarm rate. " +
256 "If not set 0.5 will be used.")
258 options.addOption(
new Option(
"h",
"help",
false,
""));