The opus audio format handler.
authorAndre Noll <maan@systemlinux.org>
Sun, 14 Oct 2012 16:44:57 +0000 (18:44 +0200)
committerAndre Noll <maan@systemlinux.org>
Sun, 26 May 2013 14:26:06 +0000 (16:26 +0200)
This fills the dummy files opus_afh.c and opus_common.c which were
introduced in the previous commit with content.  One function,
opus_parse_header(), will also be needed by the decoder to
be introduced in the next commit. So this function belongs to
opus_common.c and must be public.

error.h
ogg_afh_common.c
opus_afh.c
opus_common.c
opus_common.h

diff --git a/error.h b/error.h
index 5307774..55089c4 100644 (file)
--- a/error.h
+++ b/error.h
@@ -35,14 +35,13 @@ DEFINE_ERRLIST_OBJECT_ENUM;
 #define WRITE_ERRORS
 #define CHECK_WAV_ERRORS
 #define OPUSDEC_FILTER_ERRORS
-#define OPUS_AFH_ERRORS
-#define OPUS_COMMON_ERRORS
 
 extern const char **para_errlist[];
 
 #define OSS_MIX_ERRORS \
        PARA_ERROR(OSS_MIXER_CHANNEL, "invalid mixer channel"), \
 
+
 #define ALSA_MIX_ERRORS \
        PARA_ERROR(ALSA_MIX_OPEN, "could not open mixer"), \
        PARA_ERROR(ALSA_MIX_BAD_ELEM, "invalid/unsupported control element"), \
@@ -55,6 +54,14 @@ extern const char **para_errlist[];
        PARA_ERROR(LIBSAMPLERATE, "secret rabbit code error"), \
 
 
+#define OPUS_COMMON_ERRORS \
+       PARA_ERROR(OPUS_HEADER, "invalid opus header"), \
+
+
+#define OPUS_AFH_ERRORS \
+       PARA_ERROR(OPUS_COMMENT, "invalid or corrupted opus comment"), \
+
+
 #define SIDEBAND_ERRORS \
        PARA_ERROR(BAD_BAND, "invalid or unexpected band designator"), \
        PARA_ERROR(SB_PACKET_SIZE, "invalid sideband packet size or protocol error"), \
index 9cdb809..ac70eb3 100644 (file)
@@ -4,7 +4,7 @@
  * Licensed under the GPL v2. For licencing details see COPYING.
  */
 
-/** \file ogg_afh_common.c Functions common to ogg/vorbis and ogg/speex. */
+/** \file ogg_afh_common.c Functions common to all ogg/ codecs. */
 
 #include <ogg/ogg.h>
 #include <regex.h>
index 880e903..b079bcd 100644 (file)
@@ -1,3 +1,11 @@
+/*
+ * Copyright (C) 2012-2013 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file opus_afh.c Audio format handler for ogg/opus files. */
+
 #include <ogg/ogg.h>
 #include <regex.h>
 
 #include "string.h"
 #include "opus_common.h"
 #include "ogg_afh_common.h"
+
+static const char* opus_suffixes[] = {"opus", NULL};
+
+static bool copy_if_tag_type(const char *tag, int taglen, const char *type,
+               char **p)
+{
+       char *q = key_value_copy(tag, taglen, type);
+       if (!q)
+               return false;
+       free(*p);
+       *p = q;
+       return true;
+}
+
+static int opus_get_comments(char *comments, int length,
+               struct taginfo *tags)
+{
+       char *p = comments, *end = comments + length;
+       int i;
+       uint32_t val, ntags;
+
+       /* min size of a opus header is 16 bytes */
+       if (length < 16)
+               return -E_OPUS_COMMENT;
+       if (memcmp(p, "OpusTags", 8) != 0)
+               return -E_OPUS_COMMENT;
+       p += 8;
+       val = read_u32(p);
+       p += 4;
+       if (p + val > end)
+               return -E_OPUS_COMMENT;
+       tags->comment = safe_strdup(p, val);
+       p += val;
+       ntags = read_u32(p);
+       p += 4;
+       if (p + ntags * 4 > end)
+               return -E_OPUS_COMMENT;
+       PARA_INFO_LOG("found %d tag(s)\n", ntags);
+       for (i = 0; i < ntags; i++, p += val) {
+               char *tag;
+
+               if (p + 4 > end)
+                       return -E_OPUS_COMMENT;
+               val = read_u32(p);
+               p += 4;
+               if (p + val > end)
+                       return -E_OPUS_COMMENT;
+               if (copy_if_tag_type(p, val, "author", &tags->artist))
+                       continue;
+               if (copy_if_tag_type(p, val, "artist", &tags->artist))
+                       continue;
+               if (copy_if_tag_type(p, val, "title", &tags->title))
+                       continue;
+               if (copy_if_tag_type(p, val, "album", &tags->album))
+                       continue;
+               if (copy_if_tag_type(p, val, "year", &tags->year))
+                       continue;
+               if (copy_if_tag_type(p, val, "comment", &tags->comment))
+                       continue;
+               tag = safe_strdup(p, val);
+               PARA_NOTICE_LOG("unrecognized tag: %s\n", tag);
+               free(tag);
+       }
+       return 1;
+}
+
+static int opus_packet_callback(ogg_packet *packet, int packet_num,
+               __a_unused int serial, struct afh_info *afhi,
+               void *private_data)
+{
+       int ret;
+       struct opus_header *oh = private_data;
+
+       if (packet_num == 0) {
+               ret = opus_parse_header((char *)packet->packet, packet->bytes, oh);
+               if (ret < 0)
+                       return ret;
+               afhi->channels = oh->channels;
+               afhi->techinfo = make_message("header version %d, input sample rate: %dHz",
+                       oh->version, oh->input_sample_rate);
+               /*
+                * The input sample rate is irrelevant for afhi->frequency as
+                * we always decode to 48kHz.
+                */
+               afhi->frequency = 48000;
+               return 1;
+       }
+       if (packet_num == 1) {
+               ret = opus_get_comments((char *)packet->packet, packet->bytes,
+                       &afhi->tags);
+               if (ret < 0)
+                       return ret;
+               return 0; /* header complete */
+       }
+       /* never reached */
+       assert(0);
+}
+
+static int opus_get_file_info(char *map, size_t numbytes, __a_unused int fd,
+               struct afh_info *afhi)
+{
+       int ret, ms;
+       struct opus_header oh = {.version = 0};
+
+       struct ogg_afh_callback_info opus_callback_info = {
+               .packet_callback = opus_packet_callback,
+               .private_data = &oh,
+       };
+       ret = ogg_get_file_info(map, numbytes, afhi, &opus_callback_info);
+       if (ret < 0)
+               return ret;
+       ret = (afhi->chunk_table[afhi->chunks_total] - afhi->chunk_table[0]) * 8; /* bits */
+       ms = tv2ms(&afhi->chunk_tv) * afhi->chunks_total;
+       afhi->bitrate = ret / ms;
+       return 1;
+}
+
 /**
  * The init function of the ogg/opus audio format handler.
  *
  */
 void opus_afh_init(struct audio_format_handler *afh)
 {
-
+       afh->get_file_info = opus_get_file_info,
+       afh->suffixes = opus_suffixes;
 }
index e69de29..927df1f 100644 (file)
@@ -0,0 +1,169 @@
+/* Copyright (C)2012 Xiph.Org Foundation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file opus_common.c Common functions of the opus decoder and audio format
+ * handler.
+ */
+
+#include <ogg/ogg.h>
+
+#include "para.h"
+#include "error.h"
+#include "opus_common.h"
+#include "portable_io.h"
+
+struct packet {
+       const char *data;
+       int maxlen;
+       int pos;
+};
+
+static int read_chars(struct packet *p, unsigned char *str, int nb_chars)
+{
+       int i;
+
+       if (p->pos > p->maxlen - nb_chars)
+               return 0;
+       for (i = 0; i < nb_chars; i++)
+               str[i] = p->data[p->pos++];
+       return 1;
+}
+
+static int read_uint32(struct packet *p, ogg_uint32_t *val)
+{
+       if (p->pos > p->maxlen - 4)
+               return 0;
+       *val = read_u32(p->data + p->pos);
+       p->pos += 4;
+       return 1;
+}
+
+static int read_uint16(struct packet *p, ogg_uint16_t *val)
+{
+       if (p->pos > p->maxlen - 2)
+               return 0;
+       *val = read_u16(p->data + p->pos);
+       p->pos += 2;
+       return 1;
+}
+
+/**
+ * Get metadata of an opus stream.
+ *
+ * This is called from both the audio format handler (which passes ogg packet
+ * 0) and from the decoder.
+ *
+ * \param packet Start of the packet.
+ * \param len Number of bytes.
+ * \param h Result.
+ *
+ * \return Standard.
+ */
+int opus_parse_header(const char *packet, int len, struct opus_header *h)
+{
+       int i;
+       char str[9];
+       struct packet p;
+       unsigned char ch, channel_mapping;
+       ogg_uint16_t shortval;
+
+       p.data = packet;
+       p.maxlen = len;
+       p.pos = 0;
+       str[8] = 0;
+       if (len < 19)
+               return -E_OPUS_HEADER;
+       read_chars(&p, (unsigned char*)str, 8);
+       if (memcmp(str, "OpusHead", 8) != 0)
+               return -E_OPUS_HEADER;
+
+       if (!read_chars(&p, &ch, 1))
+               return -E_OPUS_HEADER;
+       h->version = ch;
+       if((h->version & 240) != 0) /* Only major version 0 supported. */
+               return -E_OPUS_HEADER;
+
+       if (!read_chars(&p, &ch, 1))
+               return -E_OPUS_HEADER;
+       h->channels = ch;
+       if (h->channels == 0)
+               return -E_OPUS_HEADER;
+
+       if (!read_uint16(&p, &shortval))
+               return -E_OPUS_HEADER;
+       h->preskip = shortval;
+
+       if (!read_uint32(&p, &h->input_sample_rate))
+               return -E_OPUS_HEADER;
+
+       if (!read_uint16(&p, &shortval))
+               return -E_OPUS_HEADER;
+       h->gain = (short)shortval;
+
+       if (!read_chars(&p, &ch, 1))
+               return -E_OPUS_HEADER;
+       channel_mapping = ch;
+
+       if (channel_mapping != 0) {
+               if (!read_chars(&p, &ch, 1))
+                       return -E_OPUS_HEADER;
+
+               if (ch < 1)
+                       return -E_OPUS_HEADER;
+               h->nb_streams = ch;
+
+               if (!read_chars(&p, &ch, 1))
+                       return -E_OPUS_HEADER;
+
+               if (ch > h->nb_streams || (ch + h->nb_streams) > 255)
+                       return -E_OPUS_HEADER;
+               h->nb_coupled = ch;
+
+               /* Multi-stream support */
+               for (i = 0; i < h->channels; i++) {
+                       if (!read_chars(&p, &h->stream_map[i], 1))
+                               return -E_OPUS_HEADER;
+                       if (h->stream_map[i] > (h->nb_streams + h->nb_coupled)
+                                       && h->stream_map[i] != 255)
+                               return -E_OPUS_HEADER;
+               }
+       } else {
+               if (h->channels > 2)
+                       return -E_OPUS_HEADER;
+               h->nb_streams = 1;
+               h->nb_coupled = h->channels > 1;
+               h->stream_map[0] = 0;
+               h->stream_map[1] = 1;
+       }
+       /*
+        * For version 0/1 we know there won't be any more data so reject any
+        * that have data past the end.
+        */
+       if ((h->version == 0 || h->version == 1) && p.pos != len)
+               return -E_OPUS_HEADER;
+       return 1;
+}
index e69de29..71923f1 100644 (file)
@@ -0,0 +1,21 @@
+/** Various bits stored in the header of an opus stream. */
+struct opus_header {
+       /** lower 4 bits of the version byte, must be 0. */
+       int version;
+       /** 1..255 */
+       int channels;
+       /** Number of bytes to skip from the beginning. */
+       int preskip;
+       /** Sample rate of the input stream, used by the audio format handler. */
+       ogg_uint32_t input_sample_rate;
+       /** In dB, should be zero whenever possible. */
+       int gain;
+       /** Number of logical streams (usually 1). */
+       int nb_streams;
+       /** Number of streams to decode as 2 channel streams. */
+       int nb_coupled;
+       /** Mapping from coded channels to output channels. */
+       unsigned char stream_map[255];
+};
+
+int opus_parse_header(const char *packet, int len, struct opus_header *h);