typefinding: add AAC level to ADTS caps
authorArun Raghavan <arun.raghavan@collabora.co.uk>
Mon, 12 Apr 2010 12:33:18 +0000 (13:33 +0100)
committerTim-Philipp Müller <tim.muller@collabora.co.uk>
Mon, 12 Apr 2010 14:04:31 +0000 (15:04 +0100)
This adds code to calculate the level for a given AAC stream and export
it in the stream caps. For AAC LC streams, the level is calculated
according to the definition under the AAC Profile. For other streams,
the definition under the Main Profile is used.

HE-AAC support is still to be done, and is dependent on detecting the
presence of SBR and PS in the stream.

Level is added as a field of type string because that's the way it's
done in H.264 caps as well. There are only a few possible levels, so
not using a numerical type is not too painful in this case, and
consistency is nice.

Fixes #613589.

gst/typefind/Makefile.am
gst/typefind/gstaacutil.c [new file with mode: 0644]
gst/typefind/gstaacutil.h [new file with mode: 0644]
gst/typefind/gsttypefindfunctions.c

index 131c4b2..b5d5766 100644 (file)
@@ -1,8 +1,9 @@
 plugin_LTLIBRARIES = libgsttypefindfunctions.la
 
-libgsttypefindfunctions_la_SOURCES = gsttypefindfunctions.c
+libgsttypefindfunctions_la_SOURCES = gsttypefindfunctions.c gstaacutil.c
 libgsttypefindfunctions_la_CFLAGS = $(GST_CFLAGS) $(GIO_CFLAGS)
 libgsttypefindfunctions_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
 libgsttypefindfunctions_la_LIBADD = $(GST_LIBS) $(GIO_LIBS)
 libgsttypefindfunctions_la_LIBTOOLFLAGS = --tag=disable-static
 
+noinst_HEADERS = gstaacutil.h
diff --git a/gst/typefind/gstaacutil.c b/gst/typefind/gstaacutil.c
new file mode 100644 (file)
index 0000000..229b078
--- /dev/null
@@ -0,0 +1,176 @@
+/* GStreamer
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2010 Collabora Multimedia
+ * Copyright (C) 2010 Arun Raghavan <arun.raghavan@collabora.co.uk>
+ *
+ * gstaacutil.c: collection of AAC helper utilities
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <gst/gst.h>
+
+#include "gstaacutil.h"
+
+/* FIXME: This file is duplicated in gst-plugins-* wherever needed, so if you
+ * update this file, please find all other instances and update them as well.
+ * This less-than-optimal setup is being used till there is a standard location
+ * for such common functionality.
+ */
+
+/* Determines the level of a stream as defined in ISO/IEC 14496-3. The
+ * sample_frequency_index and channel_configuration must be got from the ESDS
+ * for MP4 files and the ADTS header for ADTS streams.
+ *
+ * For AAC LC streams, we assume that apply the constraints from the AAC audio
+ * profile. For AAC Main/LTP/SSR/..., we use the Main profile.
+ *
+ * FIXME: HE-AAC support is TBD.
+ *
+ * Returns -1 if the level could not be determined.
+ */
+gint
+gst_aac_level_from_header (guint profile, guint rate, guint channel_config)
+{
+  /* Number of single channel elements, channel pair elements, low frequency
+   * elements, independently switched coupling channel elements, and
+   * dependently switched coupling channel elements.
+   *
+   * Note: The 2 CCE types are ignored for now as they require us to actually
+   * parse the first frame, and they are rarely found in actual streams.
+   */
+  int num_sce = 0, num_cpe = 0, num_lfe = 0, num_cce_indep = 0, num_cce_dep = 0;
+  int num_channels;
+  /* Processor and RAM Complexity Units (calculated and "reference" for single
+   * channel) */
+  int pcu, rcu, pcu_ref, rcu_ref;
+
+  switch (channel_config) {
+    case 0:
+      /* Channel config is defined in the AudioObjectType's SpecificConfig,
+       * which requires some amount of digging through the headers. I only see
+       * this done in the MPEG conformance streams - FIXME */
+      GST_WARNING ("Found a stream with channel configuration in the "
+          "AudioSpecificConfig. Please file a bug with a link to the media if "
+          "possible.");
+      return -1;
+    case 1:
+      /* front center */
+      num_sce = 1;
+      break;
+    case 2:
+      /* front left and right */
+      num_cpe = 1;
+      break;
+    case 3:
+      /* front left, right, and center */
+      num_sce = 1;
+      num_cpe = 1;
+      break;
+    case 4:
+      /* front left, right, and center; rear surround */
+      num_sce = 2;
+      num_cpe = 1;
+      break;
+    case 5:
+      /* front left, right, and center; rear left and right surround */
+      num_sce = 1;
+      num_cpe = 2;
+      break;
+    case 6:
+      /* front left, right, center and LFE; rear left and right surround */
+      num_sce = 1;
+      num_cpe = 2;
+      break;
+    case 7:
+      /* front left, right, center and LFE; outside front left and right;
+       * rear left and right surround */
+      num_sce = 1;
+      num_cpe = 3;
+      num_lfe = 1;
+      break;
+    default:
+      GST_WARNING ("Unknown channel config in header: %d", channel_config);
+      return -1;
+  }
+
+  switch (profile) {
+    case 0:                    /* NULL */
+      GST_WARNING ("profile 0 is not a valid profile");
+      return -1;
+    case 2:                    /* LC */
+      pcu_ref = 3;
+      rcu_ref = 3;
+      break;
+    case 3:                    /* SSR */
+      pcu_ref = 4;
+      rcu_ref = 3;
+      break;
+    case 4:                    /* LTP */
+      pcu_ref = 4;
+      rcu_ref = 4;
+      break;
+    case 1:                    /* Main */
+    default:
+      /* Other than a couple of ER profiles, Main is the worst-case */
+      pcu_ref = 5;
+      rcu_ref = 5;
+      break;
+  }
+
+  /* "fs_ref" is 48000 Hz for AAC Main/LC/SSR/LTP. SBR's fs_ref is defined as
+   * 24000/48000 (in/out), for SBR streams. Actual support is a FIXME */
+
+  pcu = ((float) rate / 48000) * pcu_ref *
+      ((2 * num_cpe) + num_sce + num_lfe + num_cce_indep + (0.3 * num_cce_dep));
+
+  rcu = ((float) rcu_ref) * (num_sce + (0.5 * num_lfe) + (0.5 * num_cce_indep) +
+      (0.4 * num_cce_dep));
+
+  if (num_cpe < 2)
+    rcu += (rcu_ref + (rcu_ref - 1)) * num_cpe;
+  else
+    rcu += (rcu_ref + (rcu_ref - 1) * ((2 * num_cpe) - 1));
+
+  num_channels = num_sce + (2 * num_cpe) + num_lfe;
+
+  if (profile == 2) {
+    /* AAC LC => return the level as per the 'AAC Profile' */
+    if (num_channels <= 2 && rate <= 24000 && pcu <= 3 && rcu <= 5)
+      return 1;
+    if (num_channels <= 2 && rate <= 48000 && pcu <= 6 && rcu <= 5)
+      return 2;
+    /* There is no level 3 for the AAC Profile */
+    if (num_channels <= 5 && rate <= 48000 && pcu <= 19 && rcu <= 15)
+      return 4;
+    if (num_channels <= 5 && rate <= 96000 && pcu <= 38 && rcu <= 15)
+      return 5;
+  } else {
+    /* Return the level as per the 'Main Profile' */
+    if (pcu < 40 && rcu < 20)
+      return 1;
+    if (pcu < 80 && rcu < 64)
+      return 2;
+    if (pcu < 160 && rcu < 128)
+      return 3;
+    if (pcu < 320 && rcu < 256)
+      return 4;
+  }
+
+  GST_WARNING ("couldn't determine level: profile=%u,rate=%u,channel_config=%u,"
+      "pcu=%d,rcu=%d", profile, rate, channel_config, pcu, rcu);
+  return -1;
+}
diff --git a/gst/typefind/gstaacutil.h b/gst/typefind/gstaacutil.h
new file mode 100644 (file)
index 0000000..4fe5a58
--- /dev/null
@@ -0,0 +1,43 @@
+/* GStreamer
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2010 Collabora Multimedia
+ * Copyright (C) 2010 Arun Raghavan <arun.raghavan@collabora.co.uk>
+ *
+ * gstaacutil.h: collection of AAC helper utilities
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GST_AAC_UTIL_H__
+#define __GST_AAC_UTIL_H__
+
+#include <glib.h>
+
+/* FIXME: This file is duplicated in gst-plugins-* wherever needed, so if you
+ * update this file, please find all other instances and update them as well.
+ * This less-than-optimal setup is being used till there is a standard location
+ * for such common functionality.
+ */
+
+G_BEGIN_DECLS
+
+gint    gst_aac_level_from_header (guint profile,
+                                   guint sample_freq_idx,
+                                   guint channel_config);
+
+G_END_DECLS
+
+#endif /* __GST_AAC_UTIL_H__*/
index 887dc2f..cb30e8d 100644 (file)
@@ -26,6 +26,7 @@
 #endif
 
 #include <glib.h>
+#include <glib/gprintf.h>
 
 /* don't want to add gio xdgmime typefinder if gio was disabled via configure */
 #ifdef HAVE_GIO
@@ -43,6 +44,8 @@
 #include <string.h>
 #include <ctype.h>
 
+#include "gstaacutil.h"
+
 GST_DEBUG_CATEGORY_STATIC (type_find_debug);
 #define GST_CAT_DEFAULT type_find_debug
 
@@ -652,6 +655,9 @@ aac_type_find (GstTypeFind * tf, gpointer unused)
 {
   /* LUT to convert the AudioObjectType from the ADTS header to a string */
   static const gchar profile_to_string[][5] = { "main", "lc", "ssr", "ltp" };
+  static const guint sample_freq[] = { 96000, 88200, 64000, 48000, 44100,
+    32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350
+  };
   DataScanCtx c = { 0, NULL, 0 };
 
   while (c.offset < AAC_AMOUNT) {
@@ -682,18 +688,54 @@ aac_type_find (GstTypeFind * tf, gpointer unused)
 
       snc = GST_READ_UINT16_BE (c.data + len);
       if ((snc & 0xfff6) == 0xfff0) {
-        gint mpegversion, profile;
+        guint mpegversion, sample_freq_idx, channel_config, profile, rate;
+        gint level;
 
         mpegversion = (c.data[1] & 0x08) ? 2 : 4;
         profile = c.data[2] >> 6;
+        sample_freq_idx = ((c.data[2] & 0x3c) >> 2);
+        channel_config = ((c.data[2] & 0x01) << 2) + (c.data[3] >> 6);
+
         GST_DEBUG ("Found second ADTS-%d syncpoint at offset 0x%"
             G_GINT64_MODIFIER "x, framelen %u", mpegversion, c.offset, len);
-        gst_type_find_suggest_simple (tf, GST_TYPE_FIND_LIKELY, "audio/mpeg",
-            "framed", G_TYPE_BOOLEAN, FALSE,
-            "mpegversion", G_TYPE_INT, mpegversion,
-            "base-profile", G_TYPE_STRING, profile_to_string[profile],
-            "profile", G_TYPE_STRING, profile_to_string[profile],
-            "stream-type", G_TYPE_STRING, "adts", NULL);
+
+        /* 0xd and 0xe are reserved. 0xf means the sample frequency is directly
+         * specified in the header, but that's not allowed for ADTS */
+        if (sample_freq_idx > 0xc) {
+          GST_DEBUG ("Unexpected sample frequency index %d or wrong sync",
+              sample_freq_idx);
+          goto next;
+        }
+
+        rate = sample_freq[sample_freq_idx];
+        GST_LOG ("ADTS: profile=%u, rate=%u", profile, rate);
+
+        /* ADTS counts profiles from 0 instead of 1 to save bits */
+        level = gst_aac_level_from_header (profile + 1, rate, channel_config);
+
+        if (level == -1) {
+          /* Could not determine the level */
+          gst_type_find_suggest_simple (tf, GST_TYPE_FIND_LIKELY, "audio/mpeg",
+              "framed", G_TYPE_BOOLEAN, FALSE,
+              "mpegversion", G_TYPE_INT, mpegversion,
+              "stream-type", G_TYPE_STRING, "adts",
+              "base-profile", G_TYPE_STRING, profile_to_string[profile],
+              "profile", G_TYPE_STRING, profile_to_string[profile], NULL);
+        } else {
+          gchar level_str[16];
+
+          /* we use a string here because h.264 levels are also strings and
+           * there aren't a lot of levels, so it's not too awkward to not use
+           * and integer here and keep the field type consistent with h.264 */
+          g_snprintf (level_str, sizeof (level_str), "%d", level);
+          gst_type_find_suggest_simple (tf, GST_TYPE_FIND_LIKELY, "audio/mpeg",
+              "framed", G_TYPE_BOOLEAN, FALSE,
+              "mpegversion", G_TYPE_INT, mpegversion,
+              "stream-type", G_TYPE_STRING, "adts",
+              "base-profile", G_TYPE_STRING, profile_to_string[profile],
+              "profile", G_TYPE_STRING, profile_to_string[profile],
+              "level", G_TYPE_STRING, level_str, NULL);
+        }
         break;
       }