| /* Main */ |
| |
| /* |
| * This file is part of gapk (GSM Audio Pocket Knife). |
| * |
| * gapk is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * gapk is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with gapk. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include "config.h" |
| |
| #include <errno.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <getopt.h> |
| #include <talloc.h> |
| |
| #include <sys/signal.h> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| |
| #include <osmocom/core/application.h> |
| #include <osmocom/core/logging.h> |
| #include <osmocom/core/socket.h> |
| #include <osmocom/core/utils.h> |
| #include <osmocom/core/select.h> |
| |
| #include <osmocom/gapk/common.h> |
| #include <osmocom/gapk/codecs.h> |
| #include <osmocom/gapk/formats.h> |
| #include <osmocom/gapk/procqueue.h> |
| #include <osmocom/gapk/benchmark.h> |
| |
| /* The root talloc context of application */ |
| TALLOC_CTX *app_root_ctx; |
| |
| struct gapk_options |
| { |
| const char *fname_in; |
| struct { |
| const char *hostname; |
| uint16_t port; |
| } rtp_in; |
| const char *alsa_in; |
| const struct osmo_gapk_format_desc *fmt_in; |
| |
| const char *fname_out; |
| struct { |
| const char *hostname; |
| uint16_t port; |
| } rtp_out; |
| const char *alsa_out; |
| const struct osmo_gapk_format_desc *fmt_out; |
| |
| /* RTP payload type */ |
| uint8_t rtp_pt_in, rtp_pt_out; |
| |
| bool loop_input; |
| int throttle; |
| int benchmark; |
| int verbose; |
| }; |
| |
| struct gapk_state |
| { |
| struct gapk_options opts; |
| struct osmo_gapk_pq *pq; |
| struct osmo_fd timerfd; /* for optional throttling */ |
| unsigned int num_frames; |
| int exit; |
| |
| struct { |
| struct { |
| FILE *fh; |
| } file; |
| struct { |
| int fd; |
| } rtp; |
| } in; |
| |
| struct { |
| struct { |
| FILE *fh; |
| } file; |
| struct { |
| int fd; |
| } rtp; |
| } out; |
| }; |
| |
| /* Logging related routines */ |
| enum { |
| DAPP, |
| }; |
| |
| static struct log_info_cat gapk_log_info_cat[] = { |
| [DAPP] = { |
| .name = "DAPP", |
| .description = "Application", |
| .color = "\033[0;36m", |
| .enabled = 1, .loglevel = LOGL_NOTICE, |
| }, |
| }; |
| |
| static const struct log_info gapk_log_info = { |
| .cat = gapk_log_info_cat, |
| .num_cat = ARRAY_SIZE(gapk_log_info_cat), |
| }; |
| |
| |
| static void |
| print_help(char *progname) |
| { |
| const struct osmo_gapk_codec_desc *codec; |
| int i; |
| |
| /* Header */ |
| fprintf(stdout, "Usage: %s [options]\n", progname); |
| fprintf(stdout, "\n"); |
| fprintf(stdout, "Options:\n"); |
| fprintf(stdout, " -i, --input-file=FILE\t\tInput file\n"); |
| fprintf(stdout, " -I, --input-rtp=HOST/PORT\tInput RTP stream\n"); |
| fprintf(stdout, " -o, --output-file=FILE\tOutput file\n"); |
| fprintf(stdout, " -O, --output-rtp=HOST/PORT\tOutput RTP stream\n"); |
| #ifdef HAVE_ALSA |
| fprintf(stdout, " -a, --input-alsa=dev\t\tInput ALSA stream\n"); |
| fprintf(stdout, " -A, --output-alsa=dev\t\tOutput ALSA stream\n"); |
| #endif |
| fprintf(stdout, " -f, --input-format=FMT\tInput format (see below)\n"); |
| fprintf(stdout, " -g, --output-format=FMT\tOutput format (see below)\n"); |
| fprintf(stdout, " -p --rtp-pt-in=TYPE\t\tRTP payload type for incoming frames\n"); |
| fprintf(stdout, " -P --rtp-pt-out=TYPE\t\tRTP payload type for outgoing frames\n"); |
| fprintf(stdout, " -b, --enable-benchmark\tEnable codec benchmarking\n"); |
| fprintf(stdout, " -t, --throttle\tEnable throttling (one codec frame every 20ms)\n"); |
| fprintf(stdout, " -l, --loop-input\tEnable looping of the input file\n"); |
| fprintf(stdout, " -v, --verbose\t\t\tEnable debug messages\n"); |
| fprintf(stdout, "\n"); |
| |
| /* Print all codecs */ |
| fprintf(stdout, "Supported codecs:\n"); |
| fprintf(stdout, " name\tfmt enc dec\tdescription\n"); |
| |
| for (i=CODEC_INVALID+1; i<_CODEC_MAX; i++) { |
| codec = osmo_gapk_codec_get_from_type(i); |
| fprintf(stdout, " %4s %c %c %c \t%s\n", |
| codec->name, |
| '*', |
| codec->codec_encode ? '*' : ' ', |
| codec->codec_decode ? '*' : ' ', |
| codec->description |
| ); |
| } |
| |
| fprintf(stdout, "\n"); |
| |
| /* Print all formats */ |
| fprintf(stdout, "Supported formats:\n"); |
| |
| for (i=FMT_INVALID+1; i<_FMT_MAX; i++) { |
| const struct osmo_gapk_format_desc *fmt = osmo_gapk_fmt_get_from_type(i); |
| fprintf(stdout, " %-19s %s\n", |
| fmt->name, |
| fmt->description |
| ); |
| } |
| |
| fprintf(stdout, "\n"); |
| } |
| |
| static int |
| parse_host_port(const char *host_port, const char **host) |
| { |
| char *dup = strdup(host_port); |
| char *tok; |
| |
| if (!dup) |
| return -ENOMEM; |
| |
| tok = strtok(dup, "/"); |
| if (!tok) |
| return -EINVAL; |
| *host = tok; |
| |
| tok = strtok(NULL, "/"); |
| if (!tok) |
| return -EINVAL; |
| |
| return atoi(tok); |
| } |
| |
| static int |
| parse_options(struct gapk_state *state, int argc, char *argv[]) |
| { |
| const struct option long_options[] = { |
| {"input-file", 1, 0, 'i'}, |
| {"output-file", 1, 0, 'o'}, |
| {"input-rtp", 1, 0, 'I'}, |
| {"output-rtp", 1, 0, 'O'}, |
| #ifdef HAVE_ALSA |
| {"input-alsa", 1, 0, 'a'}, |
| {"output-alsa", 1, 0, 'A'}, |
| #endif |
| {"input-format", 1, 0, 'f'}, |
| {"output-format", 1, 0, 'g'}, |
| {"rtp-pt-in", 1, 0, 'p'}, |
| {"rtp-pt-out", 1, 0, 'P'}, |
| {"enable-benchmark", 0, 0, 'b'}, |
| {"throttle", 0, 0, 't'}, |
| {"loop-input", 0, 0, 'l'}, |
| {"verbose", 0, 0, 'v'}, |
| {"help", 0, 0, 'h'}, |
| }; |
| const char *short_options = "i:o:I:O:f:g:p:P:btlvh" |
| #ifdef HAVE_ALSA |
| "a:A:" |
| #endif |
| ; |
| |
| struct gapk_options *opt = &state->opts; |
| |
| /* Set some defaults */ |
| memset(opt, 0x00, sizeof(*opt)); |
| |
| /* Default RTP payload type (GSM FR, see RFC 3551) */ |
| opt->rtp_pt_in = 3; |
| opt->rtp_pt_out = 3; |
| |
| /* Parse */ |
| while (1) { |
| int c, rv; |
| int opt_idx; |
| |
| c = getopt_long( |
| argc, argv, short_options, long_options, &opt_idx); |
| if (c == -1) |
| break; |
| |
| switch (c) { |
| case 'i': |
| opt->fname_in = optarg; |
| break; |
| |
| case 'o': |
| opt->fname_out = optarg; |
| break; |
| |
| case 'I': |
| rv = parse_host_port(optarg, &opt->rtp_in.hostname); |
| if (rv < 0 || rv > 0xffff) { |
| LOGP(DAPP, LOGL_ERROR, "Invalid port: %d\n", rv); |
| return -EINVAL; |
| } |
| opt->rtp_in.port = rv; |
| break; |
| #ifdef HAVE_ALSA |
| case 'a': |
| opt->alsa_in = optarg; |
| break; |
| |
| case 'A': |
| opt->alsa_out = optarg; |
| break; |
| #endif |
| case 'O': |
| rv = parse_host_port(optarg, &opt->rtp_out.hostname); |
| if (rv < 0 || rv > 0xffff) { |
| LOGP(DAPP, LOGL_ERROR, "Invalid port: %d\n", rv); |
| return -EINVAL; |
| } |
| opt->rtp_out.port = rv; |
| break; |
| |
| case 'f': |
| opt->fmt_in = osmo_gapk_fmt_get_from_name(optarg); |
| if (!opt->fmt_in) { |
| LOGP(DAPP, LOGL_ERROR, "Unsupported format: %s\n", optarg); |
| return -EINVAL; |
| } |
| break; |
| |
| case 'g': |
| opt->fmt_out = osmo_gapk_fmt_get_from_name(optarg); |
| if (!opt->fmt_out) { |
| LOGP(DAPP, LOGL_ERROR, "Unsupported format: %s\n", optarg); |
| return -EINVAL; |
| } |
| break; |
| |
| case 'p': |
| rv = atoi(optarg); |
| if (rv < 0 || rv > 0xff) { |
| LOGP(DAPP, LOGL_ERROR, "Invalid RTP payload type: %d\n", rv); |
| return -EINVAL; |
| } |
| opt->rtp_pt_in = rv; |
| break; |
| |
| case 'P': |
| rv = atoi(optarg); |
| if (rv < 0 || rv > 0xff) { |
| LOGP(DAPP, LOGL_ERROR, "Invalid RTP payload type: %d\n", rv); |
| return -EINVAL; |
| } |
| opt->rtp_pt_out = rv; |
| break; |
| |
| case 'b': |
| opt->benchmark = 1; |
| break; |
| |
| case 't': |
| opt->throttle = 1; |
| break; |
| |
| case 'l': |
| opt->loop_input = true; |
| break; |
| |
| case 'v': |
| log_parse_category_mask(osmo_stderr_target, "DAPP"); |
| opt->verbose = 1; |
| break; |
| |
| case 'h': |
| return 1; |
| |
| default: |
| LOGP(DAPP, LOGL_ERROR, "Unknown option\n"); |
| return -EINVAL; |
| |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int |
| check_options(struct gapk_state *gs) |
| { |
| /* Required formats */ |
| if (!gs->opts.fmt_in || !gs->opts.fmt_out) { |
| LOGP(DAPP, LOGL_ERROR, "Input and output formats are required arguments\n"); |
| return -EINVAL; |
| } |
| |
| /* Transcoding */ |
| if (gs->opts.fmt_in->codec_type != gs->opts.fmt_out->codec_type) { |
| const struct osmo_gapk_codec_desc *codec; |
| |
| /* Check source codec */ |
| codec = osmo_gapk_codec_get_from_type(gs->opts.fmt_in->codec_type); |
| if (!codec) { |
| LOGP(DAPP, LOGL_ERROR, "Internal error: bad codec reference\n"); |
| return -EINVAL; |
| } |
| if ((codec->type != CODEC_PCM) && !codec->codec_decode) { |
| LOGP(DAPP, LOGL_ERROR, "Decoding from '%s' codec is unsupported\n", codec->name); |
| return -ENOTSUP; |
| } |
| |
| /* Check destination codec */ |
| codec = osmo_gapk_codec_get_from_type(gs->opts.fmt_out->codec_type); |
| if (!codec) { |
| LOGP(DAPP, LOGL_ERROR, "Internal error: bad codec reference\n"); |
| return -EINVAL; |
| } |
| if ((codec->type != CODEC_PCM) && !codec->codec_encode) { |
| LOGP(DAPP, LOGL_ERROR, "Encoding to '%s' codec is unsupported\n", codec->name); |
| return -ENOTSUP; |
| } |
| } |
| |
| /* Check I/O combinations */ |
| int src_count = 0; |
| int sink_count = 0; |
| |
| if (gs->opts.fname_in) |
| src_count++; |
| if (gs->opts.rtp_in.port) |
| src_count++; |
| if (gs->opts.alsa_in) |
| src_count++; |
| |
| if (gs->opts.fname_out) |
| sink_count++; |
| if (gs->opts.rtp_out.port) |
| sink_count++; |
| if (gs->opts.alsa_out) |
| sink_count++; |
| |
| if (src_count > 1 || sink_count > 1) { |
| LOGP(DAPP, LOGL_ERROR, "You have to decide on " |
| "a single input and a single output\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static void |
| benchmark_dump(void) |
| { |
| int i; |
| |
| for (i = 0; i < _CODEC_MAX; i++) { |
| struct osmo_gapk_bench_cycles *bc; |
| unsigned long long cycles; |
| unsigned int frames; |
| |
| /* Check if there are benchmark data */ |
| bc = osmo_gapk_bench_codec[i]; |
| if (!bc) |
| continue; |
| |
| if (bc->enc_used) { |
| cycles = osmo_gapk_bench_get_cycles(i, 1); |
| frames = osmo_gapk_bench_get_frames(i, 1); |
| |
| LOGP(DAPP, LOGL_NOTICE, "Codec %u (ENC): %llu cycles for %u frames" |
| " => %llu cycles/frame\n", i, cycles, |
| frames, cycles / frames); |
| } |
| |
| if (bc->dec_used) { |
| cycles = osmo_gapk_bench_get_cycles(i, 0); |
| frames = osmo_gapk_bench_get_frames(i, 0); |
| |
| LOGP(DAPP, LOGL_NOTICE, "Codec %u (DEC): %llu cycles for %u frames" |
| " => %llu cycles/frame\n", i, cycles, |
| frames, cycles / frames); |
| } |
| } |
| } |
| |
| static int |
| files_open(struct gapk_state *gs) |
| { |
| LOGP(DAPP, LOGL_NOTICE, "Opening I/O streams\n"); |
| |
| if (gs->opts.fname_in) { |
| LOGP(DAPP, LOGL_NOTICE, "Using a file as source\n"); |
| gs->in.file.fh = fopen(gs->opts.fname_in, "rb"); |
| if (!gs->in.file.fh) { |
| LOGP(DAPP, LOGL_ERROR, "Error while opening input file for reading\n"); |
| perror("fopen"); |
| return -errno; |
| } |
| } else if (gs->opts.rtp_in.port) { |
| LOGP(DAPP, LOGL_NOTICE, "Using RTP as source\n"); |
| gs->in.rtp.fd = osmo_sock_init(AF_UNSPEC, SOCK_DGRAM, |
| IPPROTO_UDP, |
| gs->opts.rtp_in.hostname, |
| gs->opts.rtp_in.port, |
| OSMO_SOCK_F_BIND); |
| if (gs->in.rtp.fd < 0) { |
| LOGP(DAPP, LOGL_ERROR, "Error while opening input socket\n"); |
| return gs->in.rtp.fd; |
| } |
| } else if (gs->opts.alsa_in) { |
| /* Do nothing, ALSA source does the initialization itself */ |
| LOGP(DAPP, LOGL_NOTICE, "Using ALSA as source\n"); |
| } else { |
| LOGP(DAPP, LOGL_NOTICE, "Using stdin as source\n"); |
| gs->in.file.fh = stdin; |
| } |
| |
| if (gs->opts.fname_out) { |
| LOGP(DAPP, LOGL_NOTICE, "Using a file as sink\n"); |
| gs->out.file.fh = fopen(gs->opts.fname_out, "wb"); |
| if (!gs->out.file.fh) { |
| LOGP(DAPP, LOGL_ERROR, "Error while opening output file for writing\n"); |
| perror("fopen"); |
| return -errno; |
| } |
| } else if (gs->opts.rtp_out.port) { |
| LOGP(DAPP, LOGL_NOTICE, "Using RTP as sink\n"); |
| gs->out.rtp.fd = osmo_sock_init(AF_UNSPEC, SOCK_DGRAM, |
| IPPROTO_UDP, |
| gs->opts.rtp_out.hostname, |
| gs->opts.rtp_out.port, |
| OSMO_SOCK_F_CONNECT); |
| if (gs->out.rtp.fd < 0) { |
| LOGP(DAPP, LOGL_ERROR, "Error while opening output socket\n"); |
| return gs->out.rtp.fd; |
| } |
| } else if (gs->opts.alsa_out) { |
| /* Do nothing, ALSA sink does the initialization itself */ |
| LOGP(DAPP, LOGL_NOTICE, "Using ALSA as sink\n"); |
| } else { |
| LOGP(DAPP, LOGL_NOTICE, "Using stdout as sink\n"); |
| gs->out.file.fh = stdout; |
| } |
| |
| return 0; |
| } |
| |
| static void |
| files_close(struct gapk_state *gs) |
| { |
| LOGP(DAPP, LOGL_NOTICE, "Closing I/O streams\n"); |
| |
| if (gs->in.file.fh && gs->in.file.fh != stdin) |
| fclose(gs->in.file.fh); |
| if (gs->in.rtp.fd >= 0) |
| close(gs->in.rtp.fd); |
| if (gs->out.file.fh && gs->out.file.fh != stdout) |
| fclose(gs->out.file.fh); |
| if (gs->out.rtp.fd >= 0) |
| close(gs->out.rtp.fd); |
| } |
| |
| static int |
| handle_headers(struct gapk_state *gs) |
| { |
| int rv; |
| unsigned int len; |
| |
| /* Input file header (remove & check it) */ |
| len = gs->opts.fmt_in->header_len; |
| if (len && gs->in.file.fh) { |
| uint8_t *buf; |
| |
| buf = talloc_size(app_root_ctx, len); |
| if (!buf) |
| return -ENOMEM; |
| |
| rv = fread(buf, len, 1, gs->in.file.fh); |
| if ((rv != 1) || |
| memcmp(buf, gs->opts.fmt_in->header, len)) |
| { |
| LOGP(DAPP, LOGL_ERROR, "Invalid header in input file"); |
| talloc_free(buf); |
| return -EINVAL; |
| } |
| |
| talloc_free(buf); |
| } |
| |
| /* Output file header (write it) */ |
| len = gs->opts.fmt_out->header_len; |
| if (len && gs->out.file.fh) { |
| rv = fwrite(gs->opts.fmt_out->header, len, 1, gs->out.file.fh); |
| if (rv != 1) |
| return -ENOSPC; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| make_processing_chain(struct gapk_state *gs) |
| { |
| const struct osmo_gapk_format_desc *fmt_in, *fmt_out; |
| const struct osmo_gapk_codec_desc *codec_in, *codec_out; |
| |
| int need_dec, need_enc; |
| int rc; |
| |
| LOGP(DAPP, LOGL_NOTICE, "Creating a processing queue\n"); |
| |
| fmt_in = gs->opts.fmt_in; |
| fmt_out = gs->opts.fmt_out; |
| |
| codec_in = osmo_gapk_codec_get_from_type(fmt_in->codec_type); |
| codec_out = osmo_gapk_codec_get_from_type(fmt_out->codec_type); |
| |
| need_dec = (fmt_in->codec_type != CODEC_PCM) && |
| (fmt_in->codec_type != fmt_out->codec_type); |
| need_enc = (fmt_out->codec_type != CODEC_PCM) && |
| (fmt_out->codec_type != fmt_in->codec_type); |
| |
| /* File read */ |
| if (gs->in.file.fh) |
| osmo_gapk_pq_queue_file_input(gs->pq, gs->in.file.fh, fmt_in->frame_len, gs->opts.loop_input); |
| else if (gs->in.rtp.fd != -1) |
| osmo_gapk_pq_queue_rtp_input(gs->pq, gs->in.rtp.fd, |
| fmt_in->frame_len, gs->opts.rtp_pt_in); |
| #ifdef HAVE_ALSA |
| else if (gs->opts.alsa_in) |
| osmo_gapk_pq_queue_alsa_input(gs->pq, gs->opts.alsa_in, fmt_in->frame_len); |
| #endif |
| else { |
| LOGP(DAPP, LOGL_ERROR, "Unknown/invalid input\n"); |
| return -1; |
| } |
| |
| /* Decoding to PCM ? */ |
| if (need_dec) |
| { |
| /* Convert input to decoder input fmt */ |
| if (fmt_in->type != codec_in->codec_dec_format_type) |
| { |
| const struct osmo_gapk_format_desc *fmt_dec; |
| |
| fmt_dec = osmo_gapk_fmt_get_from_type(codec_in->codec_dec_format_type); |
| if (!fmt_dec) { |
| LOGP(DAPP, LOGL_ERROR, "Cannot determine decoder input format " |
| "for codec %s\n", codec_in->name); |
| return -EINVAL; |
| } |
| |
| osmo_gapk_pq_queue_fmt_convert(gs->pq, fmt_in, 0); |
| osmo_gapk_pq_queue_fmt_convert(gs->pq, fmt_dec, 1); |
| } |
| |
| /* Do decoding */ |
| osmo_gapk_pq_queue_codec(gs->pq, codec_in, 0); |
| |
| /* Allocate memory for benchmarking */ |
| if (gs->opts.benchmark) |
| osmo_gapk_bench_enable(fmt_in->codec_type); |
| } |
| else if (fmt_in->type != fmt_out->type) |
| { |
| /* Convert input to canonical fmt */ |
| osmo_gapk_pq_queue_fmt_convert(gs->pq, fmt_in, 0); |
| } |
| |
| /* Encoding from PCM ? */ |
| if (need_enc) |
| { |
| /* Do encoding */ |
| osmo_gapk_pq_queue_codec(gs->pq, codec_out, 1); |
| |
| /* Allocate memory for benchmarking */ |
| if (gs->opts.benchmark) |
| osmo_gapk_bench_enable(fmt_out->codec_type); |
| |
| /* Convert encoder output to output fmt */ |
| if (fmt_out->type != codec_out->codec_enc_format_type) |
| { |
| const struct osmo_gapk_format_desc *fmt_enc; |
| |
| fmt_enc = osmo_gapk_fmt_get_from_type(codec_out->codec_enc_format_type); |
| if (!fmt_enc) { |
| LOGP(DAPP, LOGL_ERROR, "Cannot determine encoder output format " |
| "for codec %s\n", codec_out->name); |
| return -EINVAL; |
| } |
| |
| osmo_gapk_pq_queue_fmt_convert(gs->pq, fmt_enc, 0); |
| osmo_gapk_pq_queue_fmt_convert(gs->pq, fmt_out, 1); |
| } |
| } |
| else if (fmt_in->type != fmt_out->type) |
| { |
| /* Convert canonical to output fmt */ |
| osmo_gapk_pq_queue_fmt_convert(gs->pq, fmt_out, 1); |
| } |
| |
| /* File write */ |
| if (gs->out.file.fh) |
| osmo_gapk_pq_queue_file_output(gs->pq, gs->out.file.fh, fmt_out->frame_len); |
| else if (gs->out.rtp.fd != -1) |
| osmo_gapk_pq_queue_rtp_output(gs->pq, gs->out.rtp.fd, |
| fmt_out->frame_len, gs->opts.rtp_pt_out); |
| #ifdef HAVE_ALSA |
| else if (gs->opts.alsa_out) |
| osmo_gapk_pq_queue_alsa_output(gs->pq, gs->opts.alsa_out, fmt_out->frame_len); |
| #endif |
| else { |
| LOGP(DAPP, LOGL_ERROR, "Unknown/invalid output\n"); |
| return -1; |
| } |
| |
| /* Check the processing queue in strict mode */ |
| rc = osmo_gapk_pq_check(gs->pq, 1); |
| if (rc) |
| return rc; |
| |
| return 0; |
| } |
| |
| static int |
| timerfd_cb(struct osmo_fd *ofd, unsigned int what) |
| { |
| struct gapk_state *gs = ofd->data; |
| uint64_t expire_count; |
| int rc; |
| |
| rc = read(ofd->fd, (void *)&expire_count, sizeof(expire_count)); |
| if (rc < 0 && errno == EAGAIN) |
| return 0; |
| OSMO_ASSERT(rc == sizeof(expire_count)); |
| |
| while (expire_count--) { |
| gs->num_frames++; |
| rc = osmo_gapk_pq_execute(gs->pq); |
| if (rc < 0) |
| gs->exit = true; |
| } |
| return 0; |
| } |
| |
| static int |
| run(struct gapk_state *gs) |
| { |
| struct osmo_gapk_pq_item *item; |
| int rv; |
| |
| rv = osmo_gapk_pq_prepare(gs->pq); |
| if (rv) |
| return rv; |
| |
| if (!gs->opts.throttle) { |
| for (gs->num_frames = 0; !gs->exit; gs->num_frames++) { |
| rv = osmo_gapk_pq_execute(gs->pq); |
| if (rv) |
| break; |
| } |
| } else { |
| /* setup timerfd based processing */ |
| const struct timespec interval = { |
| .tv_sec = 0, |
| .tv_nsec = 20*1000*1000, |
| }; |
| gs->timerfd.fd = -1; |
| rv = osmo_timerfd_setup(&gs->timerfd, timerfd_cb, gs); |
| OSMO_ASSERT(rv == 0); |
| rv = osmo_timerfd_schedule(&gs->timerfd, NULL, &interval); |
| OSMO_ASSERT(rv == 0); |
| |
| /* actual processing loop */ |
| while (!gs->exit) |
| osmo_select_main(0); |
| |
| /* cleanup */ |
| osmo_timerfd_disable(&gs->timerfd); |
| close(gs->timerfd.fd); |
| osmo_fd_unregister(&gs->timerfd); |
| } |
| |
| LOGP(DAPP, LOGL_NOTICE, "Processed %d frames\n", gs->num_frames); |
| |
| /* Wait for sink to process buffers */ |
| item = llist_last_entry(&gs->pq->items, struct osmo_gapk_pq_item, list); |
| if (item->wait && !gs->exit) { |
| LOGP(DAPP, LOGL_NOTICE, "Waiting for sink to finish...\n"); |
| while (item->wait(item->state)) |
| continue; |
| } |
| |
| return gs->num_frames > 0 ? 0 : rv; |
| } |
| |
| |
| static struct gapk_state _gs, *gs = &_gs; |
| |
| static void app_shutdown(void) |
| { |
| /* Close source / destination files */ |
| files_close(gs); |
| |
| /* Release processing queue */ |
| osmo_gapk_pq_destroy(gs->pq); |
| |
| /* Print benchmarking results, if enabled */ |
| benchmark_dump(); |
| |
| /* Free memory taken by benchmark data */ |
| osmo_gapk_bench_free(); |
| |
| if (gs->opts.verbose) |
| talloc_report_full(app_root_ctx, stderr); |
| } |
| |
| static void signal_handler(int signal) |
| { |
| fprintf(stderr, "signal %u received\n", signal); |
| |
| switch (signal) { |
| case SIGINT: |
| if (gs->exit++) { |
| app_shutdown(); |
| exit(0); |
| } |
| break; |
| case SIGABRT: |
| case SIGUSR1: |
| case SIGUSR2: |
| talloc_report_full(app_root_ctx, stderr); |
| default: |
| break; |
| } |
| } |
| |
| |
| int main(int argc, char *argv[]) |
| { |
| int rv; |
| |
| /* Init talloc memory management system */ |
| app_root_ctx = talloc_init("osmo-gapk root context"); |
| osmo_gapk_set_talloc_ctx(app_root_ctx); |
| |
| /* Init Osmocom logging framework */ |
| osmo_init_logging2(app_root_ctx, &gapk_log_info); |
| /* and GAPK logging wrapper */ |
| osmo_gapk_log_init(DAPP); |
| |
| /* Clear state */ |
| memset(gs, 0x00, sizeof(struct gapk_state)); |
| gs->in.rtp.fd = -1; |
| gs->out.rtp.fd = -1; |
| |
| /* Parse / check options */ |
| rv = parse_options(gs, argc, argv); |
| if (rv > 0) { |
| print_help(argv[0]); |
| return 0; |
| } |
| if (rv < 0) |
| return rv; |
| |
| /* Check request */ |
| rv = check_options(gs); |
| if (rv) |
| return rv; |
| |
| /* Create processing queue */ |
| gs->pq = osmo_gapk_pq_create("main"); |
| if (!gs->pq) { |
| rv = -ENOMEM; |
| LOGP(DAPP, LOGL_ERROR, "Error creating processing queue\n"); |
| goto error; |
| } |
| |
| /* Open source / destination files */ |
| rv = files_open(gs); |
| if (rv) { |
| LOGP(DAPP, LOGL_ERROR, "Error opening file(s)\n"); |
| goto error; |
| } |
| |
| /* Handle input/output headers */ |
| rv = handle_headers(gs); |
| if (rv) { |
| LOGP(DAPP, LOGL_ERROR, "Error handling header(s)\n"); |
| goto error; |
| } |
| |
| /* Make processing chain */ |
| rv = make_processing_chain(gs); |
| if (rv) { |
| LOGP(DAPP, LOGL_ERROR, "Error making processing chain\n"); |
| goto error; |
| } |
| |
| signal(SIGINT, &signal_handler); |
| signal(SIGABRT, &signal_handler); |
| signal(SIGUSR1, &signal_handler); |
| signal(SIGUSR2, &signal_handler); |
| |
| /* Run the processing queue */ |
| LOGP(DAPP, LOGL_NOTICE, "Init complete, starting processing queue...\n"); |
| rv = run(gs); |
| |
| error: |
| app_shutdown(); |
| return rv; |
| } |