| /* 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 <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 <gapk/codecs.h> |
| #include <gapk/formats.h> |
| #include <gapk/procqueue.h> |
| #include <gapk/benchmark.h> |
| |
| |
| struct gapk_options |
| { |
| const char *fname_in; |
| struct { |
| const char *hostname; |
| uint16_t port; |
| } rtp_in; |
| const struct format_desc *fmt_in; |
| |
| const char *fname_out; |
| struct { |
| const char *hostname; |
| uint16_t port; |
| } rtp_out; |
| const struct format_desc *fmt_out; |
| }; |
| |
| struct gapk_state |
| { |
| struct gapk_options opts; |
| |
| struct 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) |
| { |
| 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\t\tInput RTP stream\n"); |
| fprintf(stdout, " -o, --output-file=FILE\tOutput file\n"); |
| fprintf(stdout, " -O, --output-rtp=HOST/PORT\tOutput RTP stream\n"); |
| fprintf(stdout, " -f, --input-format=FMT\tInput format (see below)\n"); |
| fprintf(stdout, " -g, --output-format=FMT\tOutput format (see below)\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++) { |
| const struct codec_desc *codec = codec_get_from_type(i); |
| fprintf(stdout, " %s\t %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 format_desc *fmt = fmt_get_from_type(i); |
| fprintf(stdout, " %s%s%s\t%s\n", |
| fmt->name, |
| strlen(fmt->name) < 7 ? "\t" : "", |
| strlen(fmt->name) < 15 ? "\t" : "", |
| 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'}, |
| {"input-format", 1, 0, 'f'}, |
| {"output-format", 1, 0, 'g'}, |
| {"help", 0, 0, 'h'}, |
| }; |
| const char *short_options = "i:o:I:O:f:g:h"; |
| |
| 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; |
| |
| 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 = fmt_get_from_name(optarg); |
| if (!opt->fmt_in) { |
| fprintf(stderr, "[!] Unsupported format: %s\n", optarg); |
| return -EINVAL; |
| } |
| break; |
| |
| case 'g': |
| opt->fmt_out = fmt_get_from_name(optarg); |
| if (!opt->fmt_out) { |
| fprintf(stderr, "[!] Unsupported format: %s\n", optarg); |
| return -EINVAL; |
| } |
| 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 codec_desc *codec; |
| |
| /* Check source codec */ |
| codec = 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 = 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 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 |
| 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 |
| 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 format_desc *fmt_in, *fmt_out; |
| const struct 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 = codec_get_from_type(fmt_in->codec_type); |
| codec_out = 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) |
| pq_queue_file_input(gs->pq, gs->in.file.fh, fmt_in->frame_len); |
| else |
| pq_queue_rtp_input(gs->pq, gs->in.rtp.fd, fmt_in->frame_len); |
| |
| /* Decoding to PCM ? */ |
| if (need_dec) |
| { |
| /* Convert input to decoder input fmt */ |
| if (fmt_in->type != codec_in->codec_dec_format_type) |
| { |
| const struct format_desc *fmt_dec; |
| |
| fmt_dec = fmt_get_from_type(codec_in->codec_dec_format_type); |
| if (!fmt_dec) |
| return -EINVAL; |
| |
| pq_queue_fmt_convert(gs->pq, fmt_in, 0); |
| pq_queue_fmt_convert(gs->pq, fmt_dec, 1); |
| } |
| |
| /* Do decoding */ |
| pq_queue_codec(gs->pq, codec_in, 0); |
| } |
| else if (fmt_in->type != fmt_out->type) |
| { |
| /* Convert input to canonical fmt */ |
| pq_queue_fmt_convert(gs->pq, fmt_in, 0); |
| } |
| |
| /* Encoding from PCM ? */ |
| if (need_enc) |
| { |
| /* Do encoding */ |
| pq_queue_codec(gs->pq, codec_out, 1); |
| |
| /* Convert encoder output to output fmt */ |
| if (fmt_out->type != codec_out->codec_enc_format_type) |
| { |
| const struct format_desc *fmt_enc; |
| |
| fmt_enc = fmt_get_from_type(codec_out->codec_enc_format_type); |
| if (!fmt_enc) |
| return -EINVAL; |
| |
| pq_queue_fmt_convert(gs->pq, fmt_enc, 0); |
| pq_queue_fmt_convert(gs->pq, fmt_out, 1); |
| } |
| } |
| else if (fmt_in->type != fmt_out->type) |
| { |
| /* Convert canonical to output fmt */ |
| pq_queue_fmt_convert(gs->pq, fmt_out, 1); |
| } |
| |
| /* File write */ |
| if (gs->out.file.fh) |
| pq_queue_file_output(gs->pq, gs->out.file.fh, fmt_out->frame_len); |
| else |
| pq_queue_rtp_output(gs->pq, gs->out.rtp.fd, fmt_out->frame_len); |
| |
| return 0; |
| } |
| |
| static int |
| run(struct gapk_state *gs) |
| { |
| int rv, frames; |
| |
| rv = pq_prepare(gs->pq); |
| if (rv) |
| return rv; |
| |
| for (frames=0; !(rv = 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 signal_handler(int signal) |
| { |
| switch (signal) { |
| case SIGINT: |
| fprintf(stderr, "catching sigint, closing files\n"); |
| files_close(gs); |
| pq_destroy(gs->pq); |
| 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 = pq_create(); |
| if (!gs->pq) { |
| rv = -ENOMEM; |
| goto error; |
| } |
| |
| /* Open source / destination files */ |
| rv = files_open(gs); |
| if (rv) |
| goto error; |
| |
| /* Handle input/output headers */ |
| rv = handle_headers(gs); |
| if (rv) |
| goto error; |
| |
| /* Make processing chain */ |
| rv = make_processing_chain(gs); |
| if (rv) |
| goto error; |
| |
| signal(SIGINT, &signal_handler); |
| |
| /* Run the processing queue */ |
| rv = run(gs); |
| |
| error: |
| /* Close source / destination files */ |
| files_close(gs); |
| |
| /* Release processing queue */ |
| pq_destroy(gs->pq); |
| |
| benchmark_dump(); |
| |
| return rv; |
| } |