#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"), \
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"), \
* 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>
+/*
+ * 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;
}
+/* 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;
+}
+/** 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);