| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #include <stdio.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <signal.h> |
|
|
| #include <pocketsphinx.h> |
|
|
| #include "util/ckd_alloc.h" |
| #include "config_macro.h" |
| #include "pocketsphinx_internal.h" |
| #include "ps_alignment_internal.h" |
|
|
| |
| static const ps_arg_t ps_main_args_def[] = { |
| POCKETSPHINX_OPTIONS, |
| { "config", |
| ARG_STRING, |
| NULL, |
| "JSON file with configuration." }, |
| { "phone_align", |
| ARG_BOOLEAN, |
| "no", |
| "Run a second pass to align phones and print their durations " |
| "(DOES NOT WORK IN LIVE MODE)." }, |
| { "state_align", |
| ARG_BOOLEAN, |
| "no", |
| "Run a second pass to align phones and states and print their durations " |
| "(Implies -phone_align) " |
| "(DOES NOT WORK IN LIVE MODE)." }, |
| CMDLN_EMPTY_OPTION |
| }; |
|
|
| static int global_done = 0; |
| static void |
| catch_sig(int signum) |
| { |
| (void)signum; |
| global_done = 1; |
| } |
|
|
| #define HYP_FORMAT "{\"b\":%.3f,\"d\":%.3f,\"p\":%.3f,\"t\":\"%s\"" |
| static int |
| format_hyp(char *outptr, int len, ps_endpointer_t *ep, ps_decoder_t *decoder) |
| { |
| logmath_t *lmath; |
| double prob, st, et; |
| const char *hyp; |
|
|
| lmath = ps_get_logmath(decoder); |
| prob = logmath_exp(lmath, ps_get_prob(decoder)); |
| if (ep == NULL) { |
| st = 0.0; |
| et = (double)ps_get_n_frames(decoder) |
| / ps_config_int(ps_get_config(decoder), "frate"); |
| } |
| else { |
| st = ps_endpointer_speech_start(ep); |
| et = ps_endpointer_speech_end(ep); |
| } |
| hyp = ps_get_hyp(decoder, NULL); |
| if (hyp == NULL) |
| hyp = ""; |
| return snprintf(outptr, len, HYP_FORMAT, st, et - st, prob, hyp); |
| } |
|
|
| static int |
| format_seg(char *outptr, int len, ps_seg_t *seg, |
| double utt_start, int frate, |
| logmath_t *lmath) |
| { |
| double prob, st, dur; |
| int sf, ef; |
| const char *word; |
|
|
| ps_seg_frames(seg, &sf, &ef); |
| st = utt_start + (double)sf / frate; |
| dur = (double)(ef + 1 - sf) / frate; |
| word = ps_seg_word(seg); |
| if (word == NULL) |
| word = ""; |
| prob = logmath_exp(lmath, ps_seg_prob(seg, NULL, NULL, NULL)); |
| len = snprintf(outptr, len, HYP_FORMAT, st, dur, prob, word); |
| if (outptr) { |
| outptr += len; |
| *outptr++ = '}'; |
| *outptr = '\0'; |
| } |
| len++; |
| return len; |
| } |
|
|
| static int |
| format_align_iter(char *outptr, int maxlen, |
| ps_alignment_iter_t *itor, double utt_start, int frate, logmath_t *lmath) |
| { |
| int start, duration, score; |
| double prob, st, dur; |
| const char *word; |
|
|
| score = ps_alignment_iter_seg(itor, &start, &duration); |
| st = utt_start + (double)start / frate; |
| dur = (double)duration / frate; |
| prob = logmath_exp(lmath, score); |
| word = ps_alignment_iter_name(itor); |
| if (word == NULL) |
| word = ""; |
|
|
| return snprintf(outptr, maxlen, HYP_FORMAT, st, dur, prob, word); |
| } |
|
|
| static int |
| format_seg_align(char *outptr, int maxlen, |
| ps_alignment_iter_t *itor, |
| double utt_start, int frate, |
| logmath_t *lmath, int state_align) |
| { |
| ps_alignment_iter_t *pitor; |
| int len = 0, hyplen; |
|
|
| hyplen = format_align_iter(outptr, maxlen, |
| itor, utt_start, frate, lmath); |
| len += hyplen; |
| if (outptr) |
| outptr += hyplen; |
| if (maxlen) |
| maxlen -= hyplen; |
|
|
| len += 6; |
| if (outptr) { |
| memcpy(outptr, ",\"w\":[", 6); |
| outptr += 6; |
| } |
| if (maxlen) |
| maxlen -= 6; |
| |
| pitor = ps_alignment_iter_children(itor); |
| while (pitor != NULL) { |
| hyplen = format_align_iter(outptr, maxlen, |
| pitor, utt_start, frate, lmath); |
| len += hyplen; |
| if (outptr) |
| outptr += hyplen; |
| if (maxlen) |
| maxlen -= hyplen; |
|
|
| |
| if (state_align) { |
| ps_alignment_iter_t *sitor = ps_alignment_iter_children(pitor); |
| len += 6; |
| if (outptr) { |
| memcpy(outptr, ",\"w\":[", 6); |
| outptr += 6; |
| } |
| if (maxlen) |
| maxlen -= 6; |
| while (sitor != NULL) { |
| hyplen = format_align_iter(outptr, maxlen, |
| sitor, utt_start, frate, lmath); |
| len += hyplen; |
| if (outptr) |
| outptr += hyplen; |
| if (maxlen) |
| maxlen -= hyplen; |
|
|
| len++; |
| if (outptr) |
| *outptr++ = '}'; |
| if (maxlen) |
| maxlen--; |
| sitor = ps_alignment_iter_next(sitor); |
| if (sitor != NULL) { |
| len++; |
| if (outptr) |
| *outptr++ = ','; |
| if (maxlen) |
| maxlen--; |
| } |
| } |
| len++; |
| if (outptr) |
| *outptr++ = ']'; |
| if (maxlen) |
| maxlen--; |
| } |
|
|
| len++; |
| if (outptr) |
| *outptr++ = '}'; |
| if (maxlen) |
| maxlen--; |
| pitor = ps_alignment_iter_next(pitor); |
| if (pitor != NULL) { |
| len++; |
| if (outptr) |
| *outptr++ = ','; |
| if (maxlen) |
| maxlen--; |
| } |
| } |
|
|
| len += 2; |
| if (outptr) { |
| *outptr++ = ']'; |
| *outptr++ = '}'; |
| *outptr = '\0'; |
| } |
| if (maxlen) |
| maxlen--; |
| |
| return len; |
| } |
|
|
| static void |
| output_hyp(ps_endpointer_t *ep, ps_decoder_t *decoder, ps_alignment_t *alignment) |
| { |
| logmath_t *lmath; |
| char *hyp_json, *ptr; |
| int frate; |
| int maxlen, len; |
| double st; |
| int state_align = ps_config_bool(decoder->config, "state_align"); |
|
|
| maxlen = format_hyp(NULL, 0, ep, decoder); |
| maxlen += 6; |
| lmath = ps_get_logmath(decoder); |
| frate = ps_config_int(ps_get_config(decoder), "frate"); |
| if (ep == NULL) |
| st = 0.0; |
| else |
| st = ps_endpointer_speech_start(ep); |
| if (alignment) { |
| ps_alignment_iter_t *itor = ps_alignment_words(alignment); |
| if (itor == NULL) |
| maxlen++; |
| for (; itor; itor = ps_alignment_iter_next(itor)) { |
| maxlen += format_seg_align(NULL, 0, itor, st, frate, |
| lmath, state_align); |
| maxlen++; |
| } |
| } |
| else { |
| ps_seg_t *itor = ps_seg_iter(decoder); |
| if (itor == NULL) |
| maxlen++; |
| for (; itor; itor = ps_seg_next(itor)) { |
| maxlen += format_seg(NULL, 0, itor, st, frate, lmath); |
| maxlen++; |
| } |
| } |
| maxlen++; |
| maxlen++; |
|
|
| ptr = hyp_json = ckd_calloc(maxlen, 1); |
| len = maxlen; |
| len = format_hyp(hyp_json, len, ep, decoder); |
| ptr += len; |
| maxlen -= len; |
|
|
| assert(maxlen > 6); |
| memcpy(ptr, ",\"w\":[", 6); |
| ptr += 6; |
| maxlen -= 6; |
|
|
| if (alignment) { |
| ps_alignment_iter_t *itor; |
| for (itor = ps_alignment_words(alignment); itor; |
| itor = ps_alignment_iter_next(itor)) { |
| assert(maxlen > 0); |
| len = format_seg_align(ptr, maxlen, itor, st, frate, lmath, |
| state_align); |
| ptr += len; |
| maxlen -= len; |
| *ptr++ = ','; |
| maxlen--; |
| } |
| } |
| else { |
| ps_seg_t *itor = ps_seg_iter(decoder); |
| if (itor == NULL) { |
| *ptr++ = ']'; |
| maxlen--; |
| } |
| for (; itor; itor = ps_seg_next(itor)) { |
| assert(maxlen > 0); |
| len = format_seg(ptr, maxlen, itor, st, frate, lmath); |
| ptr += len; |
| maxlen -= len; |
| *ptr++ = ','; |
| maxlen--; |
| } |
| } |
| --ptr; |
| *ptr++ = ']'; |
| assert(maxlen == 2); |
| *ptr++ = '}'; |
| --maxlen; |
| *ptr = '\0'; |
| puts(hyp_json); |
| ckd_free(hyp_json); |
| } |
|
|
| static int |
| live(ps_config_t *config, FILE *infile) |
| { |
| ps_decoder_t *decoder = NULL; |
| ps_endpointer_t *ep = NULL; |
| short *frame = NULL; |
| size_t frame_size; |
|
|
| if ((decoder = ps_init(config)) == NULL) { |
| E_FATAL("PocketSphinx decoder init failed\n"); |
| goto error_out; |
| } |
| if ((ep = ps_endpointer_init(0, 0.0, |
| 0, ps_config_int(config, "samprate"), |
| 0)) == NULL) { |
| E_ERROR("PocketSphinx endpointer init failed\n"); |
| goto error_out; |
| } |
| frame_size = ps_endpointer_frame_size(ep); |
| if ((frame = ckd_calloc(frame_size, sizeof(frame[0]))) == NULL) { |
| E_ERROR("Failed to allocate frame"); |
| goto error_out; |
| } |
| if (signal(SIGINT, catch_sig) == SIG_ERR) |
| E_FATAL_SYSTEM("Failed to set SIGINT handler"); |
| while (!global_done) { |
| const int16 *speech; |
| int prev_in_speech = ps_endpointer_in_speech(ep); |
| size_t len, end_samples; |
| if ((len = fread(frame, sizeof(frame[0]), |
| frame_size, infile)) != frame_size) { |
| if (len > 0) { |
| speech = ps_endpointer_end_stream(ep, frame, |
| frame_size, |
| &end_samples); |
| } |
| else |
| break; |
| } else |
| speech = ps_endpointer_process(ep, frame); |
| if (speech != NULL) { |
| if (!prev_in_speech) { |
| E_INFO("Speech start at %.2f\n", |
| ps_endpointer_speech_start(ep)); |
| ps_start_utt(decoder); |
| } |
| if (ps_process_raw(decoder, speech, frame_size, FALSE, FALSE) < 0) { |
| E_ERROR("ps_process_raw() failed\n"); |
| goto error_out; |
| } |
| if (!ps_endpointer_in_speech(ep)) { |
| E_INFO("Speech end at %.2f\n", |
| ps_endpointer_speech_end(ep)); |
| ps_end_utt(decoder); |
| if (ps_config_bool(decoder->config, "phone_align")) |
| E_WARN("Subword alignment not yet supported in live mode\n"); |
| output_hyp(ep, decoder, NULL); |
| } |
| } |
| } |
| ckd_free(frame); |
| ps_endpointer_free(ep); |
| ps_free(decoder); |
| return 0; |
|
|
| error_out: |
| if (frame) |
| ckd_free(frame); |
| if (ep) |
| ps_endpointer_free(ep); |
| if (decoder) |
| ps_free(decoder); |
| return -1; |
| } |
|
|
| static int |
| decode_single(ps_decoder_t *decoder, FILE *infile) |
| { |
| ps_alignment_t *alignment = NULL; |
| size_t data_size, block_size; |
| short *data, *ptr; |
| int rv = 0; |
|
|
| data_size = 65536; |
| block_size = 2048; |
| ptr = data = ckd_calloc(data_size, sizeof(*data)); |
| if (signal(SIGINT, catch_sig) == SIG_ERR) |
| E_FATAL_SYSTEM("Failed to set SIGINT handler"); |
| while (!global_done) { |
| size_t len; |
| if ((size_t)(ptr + block_size - data) > data_size) { |
| len = ptr - data; |
| data_size *= 2; |
| data = ckd_realloc(data, data_size * sizeof(*data)); |
| ptr = data + len; |
| } |
| len = fread(ptr, sizeof(*ptr), block_size, infile); |
| if (len == 0) { |
| if (feof(infile)) |
| break; |
| else { |
| E_ERROR_SYSTEM("Failed to read %d bytes\n", |
| sizeof(*ptr) * block_size); |
| rv = -1; |
| goto error_out; |
| } |
| } |
| ptr += len; |
| } |
| if ((rv = ps_start_utt(decoder)) < 0) |
| goto error_out; |
| if ((rv = ps_process_raw(decoder, data, ptr - data, FALSE, TRUE)) < 0) { |
| E_ERROR("ps_process_raw() failed\n"); |
| goto error_out; |
| } |
| if ((rv = ps_end_utt(decoder)) < 0) |
| goto error_out; |
| if (ps_config_bool(decoder->config, "phone_align")) { |
| const char *prev_search = ps_current_search(decoder); |
| if (ps_set_alignment(decoder, NULL) < 0) |
| goto error_out; |
| if ((rv = ps_start_utt(decoder)) < 0) |
| goto error_out; |
| if ((rv = ps_process_raw(decoder, data, ptr - data, FALSE, TRUE)) < 0) { |
| E_ERROR("ps_process_raw() failed\n"); |
| goto error_out; |
| } |
| if ((rv = ps_end_utt(decoder)) < 0) |
| goto error_out; |
| if ((alignment = ps_get_alignment(decoder)) == NULL) |
| goto error_out; |
| ps_activate_search(decoder, prev_search); |
| } |
| output_hyp(NULL, decoder, alignment); |
| |
| error_out: |
| ckd_free(data); |
| return rv; |
| } |
|
|
| static int |
| single(ps_config_t *config, FILE *infile) |
| { |
| ps_decoder_t *decoder; |
| int rv = 0; |
|
|
| if ((decoder = ps_init(config)) == NULL) { |
| E_FATAL("PocketSphinx decoder init failed\n"); |
| return -1; |
| } |
| rv = decode_single(decoder, infile); |
| ps_free(decoder); |
| return rv; |
| } |
|
|
| static char * |
| string_array_join(char **strings, int nstrings) |
| { |
| char *joined, *ptr; |
| int i, *len, jlen; |
|
|
| len = ckd_malloc(nstrings * sizeof(*len)); |
| for (jlen = i = 0; i < nstrings; ++i) { |
| len[i] = strlen(strings[i]); |
| jlen += len[i] + 1; |
| } |
| ptr = joined = ckd_malloc(jlen); |
| for (i = 0; i < nstrings; ++i) { |
| memcpy(ptr, strings[i], len[i]); |
| ptr += len[i]; |
| *ptr++ = ' '; |
| } |
| *--ptr = '\0'; |
| ckd_free(len); |
| return joined; |
| } |
|
|
| static int |
| align(ps_config_t *config, char **inputs, int ninputs) |
| { |
| int rv = 0, is_stdin = FALSE; |
| ps_decoder_t *decoder = NULL; |
| char *text = NULL; |
| FILE *fh = NULL; |
|
|
| if (ninputs < 2) { |
| E_ERROR("Usage: pocketsphinx align INFILE TEXT...\n"); |
| return -1; |
| } |
| |
| ps_config_set_bool(config, "bestpath", FALSE); |
| ps_config_set_str(config, "lm", NULL); |
| if (0 == strcmp(inputs[0], "-")) { |
| is_stdin = TRUE; |
| fh = stdin; |
| } |
| else if ((fh = fopen(inputs[0], "rb")) == NULL) { |
| E_ERROR_SYSTEM("Failed to open %s for input", inputs[0]); |
| goto error_out; |
| } |
| if ((rv = ps_config_soundfile(config, fh, inputs[0])) < 0) |
| goto error_out; |
| if ((decoder = ps_init(config)) == NULL) { |
| E_FATAL("PocketSphinx decoder init failed\n"); |
| rv = -1; |
| goto error_out; |
| } |
| text = string_array_join(inputs + 1, ninputs - 1); |
| if ((rv = ps_set_align_text(decoder, text)) < 0) |
| goto error_out; |
| rv = decode_single(decoder, fh); |
| |
| error_out: |
| if (fh && !is_stdin) |
| fclose(fh); |
| if (text) |
| ckd_free(text); |
| if (decoder) |
| ps_free(decoder); |
| return rv; |
| } |
|
|
| #if 0 |
| static int sample_rates[] = { |
| 8000, |
| 11025, |
| 16000, |
| 22050, |
| 32000, |
| 44100, |
| 48000 |
| }; |
| static const int n_sample_rates = sizeof(sample_rates)/sizeof(sample_rates[0]); |
|
|
| static int |
| minimum_samprate(ps_config_t *config) |
| { |
| double upperf = ps_config_float(config, "upperf"); |
| int nyquist = (int)(upperf * 2); |
| int i; |
| for (i = 0; i < n_sample_rates; ++i) |
| if (sample_rates[i] >= nyquist) |
| break; |
| if (i == n_sample_rates) |
| E_FATAL("Unable to find sampling rate for -upperf %f\n", upperf); |
| return sample_rates[i]; |
| } |
| #endif |
|
|
| #define SOX_FORMAT "-r %d -c 1 -b 16 -e signed-integer -t raw -" |
| static int |
| soxflags(ps_config_t *config) |
| { |
| int maxlen, len; |
| int samprate; |
| char *args; |
|
|
| |
| ps_expand_model_config(config); |
| samprate = ps_config_int(config, "samprate"); |
|
|
| maxlen = snprintf(NULL, 0, SOX_FORMAT, samprate); |
| if (maxlen < 0) { |
| E_ERROR_SYSTEM("Failed to snprintf()"); |
| return -1; |
| } |
| maxlen++; |
| args = ckd_calloc(maxlen, 1); |
| len = snprintf(args, maxlen, SOX_FORMAT, samprate); |
| if (len != maxlen - 1) { |
| E_ERROR_SYSTEM("Failed to snprintf()"); |
| return -1; |
| } |
| puts(args); |
| fflush(stdout); |
| ckd_free(args); |
| return 0; |
| } |
|
|
| static char * |
| find_command(int *argc, char **argv) |
| { |
| int i; |
| for (i = 1; i < *argc; i += 2) { |
| char *arg = argv[i]; |
| if (arg && arg[0] && arg[0] != '-') { |
| memmove(&argv[i], |
| &argv[i + 1], |
| (*argc - i - 1) * sizeof(argv[i])); |
| --*argc; |
| return arg; |
| } |
| } |
| return "live"; |
| } |
|
|
| static char ** |
| find_inputs(int *argc, char **argv, int *ninputs) |
| { |
| char **inputs = NULL; |
| int i = 1; |
| *ninputs = 0; |
| while (i < *argc) { |
| char *arg = argv[i]; |
| |
| if (arg && arg[0] |
| |
| && (arg[0] != '-' || arg[1] == '\0')) { |
| memmove(&argv[i], |
| &argv[i + 1], |
| (*argc - i - 1) * sizeof(argv[i])); |
| --*argc; |
| argv[*argc] = arg; |
| inputs = &argv[*argc]; |
| ++*ninputs; |
| } |
| else |
| i += 2; |
| } |
| |
| |
| for (i = 0; i < *ninputs / 2; ++i) { |
| char *tmp = inputs[i]; |
| inputs[i] = inputs[*ninputs - i - 1]; |
| inputs[*ninputs - i - 1] = tmp; |
| } |
| return inputs; |
| } |
|
|
| int |
| process_inputs(int (*func)(ps_config_t *, FILE *), |
| ps_config_t *config, |
| char **inputs, int ninputs) |
| { |
| int rv = 0; |
| |
| if (ninputs == 0) |
| return func(config, stdin); |
| else { |
| int i, rv_one; |
| for (i = 0; i < ninputs; ++i) { |
| char *file = inputs[i]; |
| int is_stdin = FALSE; |
| FILE *fh; |
|
|
| if (0 == strcmp(file, "-")) { |
| is_stdin = TRUE; |
| fh = stdin; |
| } |
| else if ((fh = fopen(file, "rb")) == NULL) { |
| E_ERROR_SYSTEM("Failed to open %s for input", file); |
| rv = -1; |
| continue; |
| } |
| if ((rv_one = ps_config_soundfile(config, fh, file)) < 0) { |
| fclose(fh); |
| rv = rv_one; |
| continue; |
| } |
| if ((rv_one = func(config, fh)) < 0) { |
| rv = rv_one; |
| E_ERROR("Recognition failed on %s\n", file); |
| } |
| if (!is_stdin) |
| fclose(fh); |
| } |
| } |
| return rv; |
| } |
|
|
| static int |
| print_config(ps_config_t *config) |
| { |
| if (puts(ps_config_serialize_json(config)) < 0) |
| return -1; |
| return 0; |
| } |
|
|
| void |
| usage(char *name, int help_config) |
| { |
| fprintf(stderr, "Usage: %s [PARAMS] [soxflags | config | help | help-config | live | single | align] INPUTS...\n", name); |
| fprintf(stderr, "Examples:\n"); |
| fprintf(stderr, "\tsox input.mp3 $(%s soxflags) | %s single -\n", name, name); |
| fprintf(stderr, "\tsox -qd $(%s soxflags) | %s live -\n", name, name); |
| fprintf(stderr, "\t%s single INPUT\n", name); |
| fprintf(stderr, "\t%s align INPUT WORDS...\n", name); |
| fprintf(stderr, "\nFor detailed PARAMS values, run %s help-config\n", name); |
| if (help_config) { |
| err_set_loglevel(ERR_INFO); |
| cmd_ln_log_help_r(NULL, ps_args()); |
| } |
| } |
|
|
| int |
| main(int argc, char *argv[]) |
| { |
| ps_config_t *config; |
| const char *conffile; |
| char *command; |
| char **inputs; |
| int rv, ninputs; |
|
|
| command = find_command(&argc, argv); |
| inputs = find_inputs(&argc, argv, &ninputs); |
| |
| if (ninputs == 0) { |
| if ((0 != strcmp(command, "soxflags")) |
| && 0 != strcmp(command, "config") |
| && 0 != strcmp(command, "help-config")) { |
| usage(argv[0], FALSE); |
| return 1; |
| } |
| } |
| |
| if ((config = ps_config_parse_args(ps_main_args_def, argc, argv)) == NULL) { |
| usage(argv[0], FALSE); |
| return 1; |
| } |
| ps_default_search_args(config); |
| if (ps_config_bool(config, "state_align")) |
| ps_config_set_bool(config, "phone_align", TRUE); |
| if ((conffile = ps_config_str(config, "config")) != NULL) { |
| char *json; |
| FILE *infh; |
| size_t len; |
| if ((infh = fopen(conffile, "rt")) == NULL) { |
| E_ERROR_SYSTEM("Failed to open config file %s", conffile); |
| return 1; |
| } |
| fseek(infh, 0, SEEK_END); |
| len = (size_t)ftell(infh); |
| fseek(infh, 0, SEEK_SET); |
| json = ckd_malloc(len + 1); |
| if (fread(json, 1, len, infh) != len) { |
| E_ERROR_SYSTEM("Failed to read config file %s", conffile); |
| ckd_free(json); |
| fclose(infh); |
| return 1; |
| } |
| json[len] = '\0'; |
| fclose(infh); |
| config = ps_config_parse_json(config, json); |
| ckd_free(json); |
| if (config == NULL) |
| return 1; |
| ps_config_set_str(config, "config", NULL); |
| } |
| if (0 == strcmp(command, "soxflags")) |
| rv = soxflags(config); |
| else if (0 == strcmp(command, "config")) |
| rv = print_config(config); |
| else if (0 == strcmp(command, "live")) |
| rv = process_inputs(live, config, inputs, ninputs); |
| else if (0 == strcmp(command, "single")) |
| rv = process_inputs(single, config, inputs, ninputs); |
| else if (0 == strcmp(command, "align")) |
| rv = align(config, inputs, ninputs); |
| else if (0 == strcmp(command, "help")) { |
| rv = 0; |
| usage(argv[0], FALSE); |
| } |
| else if (0 == strcmp(command, "help-config")) { |
| rv = 0; |
| usage(argv[0], TRUE); |
| } |
| else { |
| E_ERROR("Unknown command \"%s\"\n", command); |
| return 1; |
| } |
|
|
| ps_config_free(config); |
| return rv; |
| } |
|
|