summaryrefslogtreecommitdiffstats
path: root/src/app_osmo_gapk.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/app_osmo_gapk.c')
-rw-r--r--src/app_osmo_gapk.c679
1 files changed, 679 insertions, 0 deletions
diff --git a/src/app_osmo_gapk.c b/src/app_osmo_gapk.c
new file mode 100644
index 0000000..90779af
--- /dev/null
+++ b/src/app_osmo_gapk.c
@@ -0,0 +1,679 @@
+/* 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 <sys/signal.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/socket.h>
+
+#include <osmocom/gapk/codecs.h>
+#include <osmocom/gapk/formats.h>
+#include <osmocom/gapk/procqueue.h>
+#include <osmocom/gapk/benchmark.h>
+
+
+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;
+
+ int benchmark;
+};
+
+struct gapk_state
+{
+ struct gapk_options opts;
+
+ struct osmo_gapk_pq *pq;
+
+ struct {
+ struct {
+ FILE *fh;
+ } file;
+ struct {
+ int fd;
+ } rtp;
+ } in;
+
+ struct {
+ struct {
+ FILE *fh;
+ } file;
+ struct {
+ int fd;
+ } rtp;
+ } out;
+};
+
+
+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, " -b, --enable-benchmark\tEnable codec benchmarking\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'},
+ {"enable-benchmark", 0, 0, 'b'},
+ {"help", 0, 0, 'h'},
+ };
+ const char *short_options = "i:o:I:O:f:g:bh"
+#ifdef HAVE_ALSA
+ "a:A:"
+#endif
+ ;
+
+ struct gapk_options *opt = &state->opts;
+
+ /* Set some defaults */
+ memset(opt, 0x00, sizeof(*opt));
+
+ /* 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) {
+ fprintf(stderr, "[!] 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) {
+ fprintf(stderr, "[!] 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) {
+ fprintf(stderr, "[!] Unsupported format: %s\n", optarg);
+ return -EINVAL;
+ }
+ break;
+
+ case 'g':
+ opt->fmt_out = osmo_gapk_fmt_get_from_name(optarg);
+ if (!opt->fmt_out) {
+ fprintf(stderr, "[!] Unsupported format: %s\n", optarg);
+ return -EINVAL;
+ }
+ break;
+
+ case 'b':
+ opt->benchmark = 1;
+ break;
+
+ case 'h':
+ return 1;
+
+ default:
+ fprintf(stderr, "[+] 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) {
+ fprintf(stderr, "[!] 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) {
+ fprintf(stderr, "[!] Internal error: bad codec reference\n");
+ return -EINVAL;
+ }
+ if ((codec->type != CODEC_PCM) && !codec->codec_decode) {
+ fprintf(stderr, "[!] 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) {
+ fprintf(stderr, "[!] Internal error: bad codec reference\n");
+ return -EINVAL;
+ }
+ if ((codec->type != CODEC_PCM) && !codec->codec_encode) {
+ fprintf(stderr, "[!] Encoding to '%s' codec is unsupported\n", codec->name);
+ return -ENOTSUP;
+ }
+ }
+
+ /* Input combinations */
+ if (gs->opts.fname_in && gs->opts.rtp_in.port) {
+ fprintf(stderr, "[!] You have to decide on either file or RTP input\n");
+ return -EINVAL;
+ }
+
+ /* Output combinations */
+ if (gs->opts.fname_out && gs->opts.rtp_out.port) {
+ fprintf(stderr, "[!] You have to decide on either file or RTP 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);
+
+ fprintf(stderr, "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);
+
+ fprintf(stderr, "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)
+{
+ if (gs->opts.fname_in) {
+ gs->in.file.fh = fopen(gs->opts.fname_in, "rb");
+ if (!gs->in.file.fh) {
+ fprintf(stderr, "[!] Error while opening input file for reading\n");
+ perror("fopen");
+ return -errno;
+ }
+ } else if (gs->opts.rtp_in.port) {
+ 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) {
+ fprintf(stderr, "[!] Error while opening input socket\n");
+ return gs->in.rtp.fd;
+ }
+ } else if (gs->opts.alsa_in) {
+ printf("alsa_in, not stdin\n");
+ } else
+ gs->in.file.fh = stdin;
+
+ if (gs->opts.fname_out) {
+ gs->out.file.fh = fopen(gs->opts.fname_out, "wb");
+ if (!gs->out.file.fh) {
+ fprintf(stderr, "[!] Error while opening output file for writing\n");
+ perror("fopen");
+ return -errno;
+ }
+ } else if (gs->opts.rtp_out.port) {
+ 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) {
+ fprintf(stderr, "[!] Error while opening output socket\n");
+ return gs->out.rtp.fd;
+ }
+ } else if (gs->opts.alsa_out) {
+ printf("alsa_out, not stdout\n");
+ } else
+ gs->out.file.fh = stdout;
+
+ return 0;
+}
+
+static void
+files_close(struct gapk_state *gs)
+{
+ 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 = malloc(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))
+ {
+ free(buf);
+ fprintf(stderr, "[!] Invalid header in input file");
+ return -EINVAL;
+ }
+
+ 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;
+
+ 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);
+ else if (gs->in.rtp.fd != -1)
+ osmo_gapk_pq_queue_rtp_input(gs->pq, gs->in.rtp.fd, fmt_in->frame_len);
+#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 {
+ fprintf(stderr, "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) {
+ fprintf(stderr, "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) {
+ fprintf(stderr, "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);
+#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 {
+ fprintf(stderr, "Unknown/invalid output\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+run(struct gapk_state *gs)
+{
+ int rv, frames;
+
+ rv = osmo_gapk_pq_prepare(gs->pq);
+ if (rv)
+ return rv;
+
+ for (frames=0; !(rv = osmo_gapk_pq_execute(gs->pq)); frames++);
+
+ fprintf(stderr, "[+] Processed %d frames\n", frames);
+
+ return 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();
+}
+
+static void signal_handler(int signal)
+{
+ switch (signal) {
+ case SIGINT:
+ fprintf(stderr, "catching sigint, shutting down...\n");
+ app_shutdown();
+ exit(0);
+ break;
+ default:
+ break;
+ }
+}
+
+
+int main(int argc, char *argv[])
+{
+ int rv;
+
+ /* 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();
+ if (!gs->pq) {
+ rv = -ENOMEM;
+ fprintf(stderr, "Error creating processing queue\n");
+ goto error;
+ }
+
+ /* Open source / destination files */
+ rv = files_open(gs);
+ if (rv) {
+ fprintf(stderr, "Error opening file(s)\n");
+ goto error;
+ }
+
+ /* Handle input/output headers */
+ rv = handle_headers(gs);
+ if (rv) {
+ fprintf(stderr, "Error handling header(s)\n");
+ goto error;
+ }
+
+ /* Make processing chain */
+ rv = make_processing_chain(gs);
+ if (rv) {
+ fprintf(stderr, "Error making processing chain\n");
+ goto error;
+ }
+
+ signal(SIGINT, &signal_handler);
+
+ /* Run the processing queue */
+ rv = run(gs);
+
+error:
+ app_shutdown();
+ return rv;
+}