From 7007aedb78262af262e7e7db8d010c6498e79290 Mon Sep 17 00:00:00 2001
From: Andre Noll <maan@systemlinux.org>
Date: Sun, 14 Oct 2012 18:44:57 +0200
Subject: [PATCH] The opus audio format handler.

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          |  11 ++-
 ogg_afh_common.c |   2 +-
 opus_afh.c       | 128 ++++++++++++++++++++++++++++++++++-
 opus_common.c    | 169 +++++++++++++++++++++++++++++++++++++++++++++++
 opus_common.h    |  21 ++++++
 5 files changed, 327 insertions(+), 4 deletions(-)

diff --git a/error.h b/error.h
index 5307774b..55089c46 100644
--- 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"), \
diff --git a/ogg_afh_common.c b/ogg_afh_common.c
index 9cdb8092..ac70eb3f 100644
--- a/ogg_afh_common.c
+++ b/ogg_afh_common.c
@@ -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>
diff --git a/opus_afh.c b/opus_afh.c
index 880e9035..b079bcd5 100644
--- a/opus_afh.c
+++ b/opus_afh.c
@@ -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>
 
@@ -8,6 +16,123 @@
 #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.
  *
@@ -15,5 +140,6 @@
  */
 void opus_afh_init(struct audio_format_handler *afh)
 {
-
+	afh->get_file_info = opus_get_file_info,
+	afh->suffixes = opus_suffixes;
 }
diff --git a/opus_common.c b/opus_common.c
index e69de29b..927df1f3 100644
--- a/opus_common.c
+++ b/opus_common.c
@@ -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;
+}
diff --git a/opus_common.h b/opus_common.h
index e69de29b..71923f11 100644
--- a/opus_common.h
+++ b/opus_common.h
@@ -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);
-- 
2.39.5