codec-utils: support extension audio object type and sample rate
authorAlex Ashley <alex.ashley@youview.com>
Thu, 5 Jul 2018 12:45:14 +0000 (13:45 +0100)
committerArun Raghavan <arun@arunraghavan.net>
Fri, 11 Jan 2019 12:11:15 +0000 (17:41 +0530)
ISO 14496-3 defines that audioObjectType 5 is a special case that
indicates SBR is present and that an additional field has to be
parsed to find the true audioObjectType.

There are two ways of signaling SBR within an AAC stream - implicit
and explicit (see [1] section 4.2). When explicit signaling is used,
the presence of SBR data is signaled by means of the SBR
audioObjectType in the AudioSpecificConfig data.

Normally the sample rate is specified by an index into a
table of common sample rates. However index 0x0f is a special case
that indicates that the next 24 bits contain the real sample rate.

[1] https://www.telosalliance.com/support/A-closer-look-into-MPEG-4-High-Efficiency-AAC

Fixes #39

gst-libs/gst/pbutils/codec-utils.c
tests/check/libs/pbutils.c

index 8a2f6c30b285c9a35d7bb0495ac605e732de75a0..a8b14fe563521a676be0eb7708e882009aff06b7 100644 (file)
@@ -38,6 +38,7 @@
 
 #include "pbutils.h"
 #include <gst/base/base.h>
+#include <gst/base/gstbitreader.h>
 #include <gst/tag/tag.h>
 
 #include <string.h>
@@ -107,6 +108,83 @@ gst_codec_utils_aac_get_index_from_sample_rate (guint rate)
   return -1;
 }
 
+static gboolean
+gst_codec_utils_aac_get_audio_object_type (GstBitReader * br,
+    guint8 * audio_object_type)
+{
+  guint8 aot;
+
+  if (!gst_bit_reader_get_bits_uint8 (br, &aot, 5))
+    return FALSE;
+
+  if (aot == 31) {
+    if (!gst_bit_reader_get_bits_uint8 (br, &aot, 6))
+      return FALSE;
+    aot += 32;
+  }
+
+  *audio_object_type = aot;
+
+  return TRUE;
+}
+
+static gboolean
+gst_codec_utils_aac_get_audio_sample_rate (GstBitReader * br,
+    guint * sample_rate)
+{
+  guint8 sampling_freq_index;
+  guint32 sampling_rate;
+
+  if (!gst_bit_reader_get_bits_uint8 (br, &sampling_freq_index, 4))
+    return FALSE;
+
+  if (sampling_freq_index == 0xf) {
+    if (!gst_bit_reader_get_bits_uint32 (br, &sampling_rate, 24))
+      return FALSE;
+  } else {
+    sampling_rate =
+        gst_codec_utils_aac_get_sample_rate_from_index (sampling_freq_index);
+    if (!sampling_rate)
+      return FALSE;
+  }
+
+  *sample_rate = sampling_rate;
+
+  return TRUE;
+}
+
+static gboolean
+gst_codec_utils_aac_get_audio_object_type_full (GstBitReader * br,
+    guint8 * audio_object_type, guint8 * channel_config, guint * sample_rate)
+{
+  guint8 aot, channels;
+  guint rate;
+
+  if (!gst_codec_utils_aac_get_audio_object_type (br, &aot))
+    return FALSE;
+
+  if (!gst_codec_utils_aac_get_audio_sample_rate (br, &rate))
+    return FALSE;
+
+  if (!gst_bit_reader_get_bits_uint8 (br, &channels, 4))
+    return FALSE;
+
+  /* 5 indicates SBR extension (i.e. HE-AAC) */
+  /* 29 indicates PS extension */
+  if (aot == 5 || aot == 29) {
+    if (!gst_codec_utils_aac_get_audio_sample_rate (br, &rate))
+      return FALSE;
+    if (!gst_codec_utils_aac_get_audio_object_type (br, &aot))
+      return FALSE;
+  }
+
+  *audio_object_type = aot;
+  *sample_rate = rate;
+  *channel_config = channels;
+
+  return TRUE;
+}
+
 /**
  * gst_codec_utils_aac_get_sample_rate:
  * @audio_config: (array length=len): a pointer to the AudioSpecificConfig
@@ -124,13 +202,17 @@ gst_codec_utils_aac_get_index_from_sample_rate (guint rate)
 guint
 gst_codec_utils_aac_get_sample_rate (const guint8 * audio_config, guint len)
 {
-  guint rate_index;
+  guint sample_rate = 0;
+  guint8 audio_object_type = 0, channel_config = 0;
+  GstBitReader br = GST_BIT_READER_INIT (audio_config, len);
 
   if (len < 2)
     return 0;
 
-  rate_index = ((audio_config[0] & 0x7) << 1) | ((audio_config[1] & 0x80) >> 7);
-  return gst_codec_utils_aac_get_sample_rate_from_index (rate_index);
+  gst_codec_utils_aac_get_audio_object_type_full (&br, &audio_object_type,
+      &channel_config, &sample_rate);
+
+  return sample_rate;
 }
 
 /**
@@ -171,10 +253,8 @@ gst_codec_utils_aac_get_channels (const guint8 * audio_config, guint len)
  * @len: Length of @audio_config in bytes
  *
  * Returns the profile of the given AAC stream as a string. The profile is
- * determined using the AudioObjectType field which is in the first 5 bits of
- * @audio_config.
- *
- * > HE-AAC support has not yet been implemented.
+ * normally determined using the AudioObjectType field which is in the first
+ * 5 bits of @audio_config
  *
  * Returns: The profile as a const string and %NULL if the profile could not be
  * determined.
@@ -182,29 +262,40 @@ gst_codec_utils_aac_get_channels (const guint8 * audio_config, guint len)
 const gchar *
 gst_codec_utils_aac_get_profile (const guint8 * audio_config, guint len)
 {
-  guint profile;
+  const gchar *profile = NULL;
+  guint sample_rate;
+  guint8 audio_object_type, channel_config;
+  GstBitReader br = GST_BIT_READER_INIT (audio_config, len);
 
   if (len < 1)
     return NULL;
 
   GST_MEMDUMP ("audio config", audio_config, len);
 
-  profile = audio_config[0] >> 3;
-  switch (profile) {
+  if (!gst_codec_utils_aac_get_audio_object_type_full (&br, &audio_object_type,
+          &channel_config, &sample_rate)) {
+    return NULL;
+  }
+
+  switch (audio_object_type) {
     case 1:
-      return "main";
+      profile = "main";
+      break;
     case 2:
-      return "lc";
+      profile = "lc";
+      break;
     case 3:
-      return "ssr";
+      profile = "ssr";
+      break;
     case 4:
-      return "ltp";
+      profile = "ltp";
+      break;
     default:
+      GST_DEBUG ("Invalid profile idx: %u", audio_object_type);
       break;
   }
 
-  GST_DEBUG ("Invalid profile idx: %u", profile);
-  return NULL;
+  return profile;
 }
 
 /**
@@ -221,21 +312,21 @@ gst_codec_utils_aac_get_profile (const guint8 * audio_config, guint len)
  * The @audio_config parameter follows the following format, starting from the
  * most significant bit of the first byte:
  *
- *   * Bit 0:4 contains the AudioObjectType
+ *   * Bit 0:4 contains the AudioObjectType (if this is 0x5, then the
+ *     real AudioObjectType is carried after the rate and channel data)
  *   * Bit 5:8 contains the sample frequency index (if this is 0xf, then the
  *     next 24 bits define the actual sample frequency, and subsequent
  *     fields are appropriately shifted).
  *   * Bit 9:12 contains the channel configuration
  *
- * > HE-AAC support has not yet been implemented.
- *
  * Returns: The level as a const string and %NULL if the level could not be
  * determined.
  */
 const gchar *
 gst_codec_utils_aac_get_level (const guint8 * audio_config, guint len)
 {
-  int profile, sr_idx, channel_config, rate;
+  guint8 audio_object_type = 0xFF, channel_config = 0xFF;
+  guint rate;
   /* Number of single channel elements, channel pair elements, low frequency
    * elements, independently switched coupling channel elements, and
    * dependently switched coupling channel elements.
@@ -247,8 +338,9 @@ gst_codec_utils_aac_get_level (const guint8 * audio_config, guint len)
   int num_channels;
   /* Processor and RAM Complexity Units (calculated and "reference" for single
    * channel) */
-  int pcu, rcu, pcu_ref, rcu_ref;
+  int pcu = -1, rcu = -1, pcu_ref, rcu_ref;
   int ret = -1;
+  GstBitReader br = GST_BIT_READER_INIT (audio_config, len);
 
   g_return_val_if_fail (audio_config != NULL, NULL);
 
@@ -257,14 +349,10 @@ gst_codec_utils_aac_get_level (const guint8 * audio_config, guint len)
 
   GST_MEMDUMP ("audio config", audio_config, len);
 
-  profile = audio_config[0] >> 3;
-  /* FIXME: add support for sr_idx = 0xf */
-  sr_idx = ((audio_config[0] & 0x7) << 1) | ((audio_config[1] & 0x80) >> 7);
-  rate = gst_codec_utils_aac_get_sample_rate_from_index (sr_idx);
-  channel_config = (audio_config[1] & 0x7f) >> 3;
-
-  if (rate == 0)
+  if (!gst_codec_utils_aac_get_audio_object_type_full (&br, &audio_object_type,
+          &channel_config, &rate)) {
     return NULL;
+  }
 
   switch (channel_config) {
     case 0:
@@ -322,7 +410,7 @@ gst_codec_utils_aac_get_level (const guint8 * audio_config, guint len)
       return NULL;
   }
 
-  switch (profile) {
+  switch (audio_object_type) {
     case 0:                    /* NULL */
       GST_WARNING ("profile 0 is not a valid profile");
       return NULL;
@@ -362,7 +450,7 @@ gst_codec_utils_aac_get_level (const guint8 * audio_config, guint len)
 
   num_channels = num_sce + (2 * num_cpe) + num_lfe;
 
-  if (profile == 2) {
+  if (audio_object_type == 2) {
     /* AAC LC => return the level as per the 'AAC Profile' */
     if (num_channels <= 2 && rate <= 24000 && pcu <= 3 && rcu <= 5)
       ret = 1;
@@ -391,8 +479,8 @@ gst_codec_utils_aac_get_level (const guint8 * audio_config, guint len)
 
   if (ret == -1) {
     GST_WARNING ("couldn't determine level: profile=%u, rate=%u, "
-        "channel_config=%u, pcu=%d,rcu=%d", profile, rate, channel_config, pcu,
-        rcu);
+        "channel_config=%u, pcu=%d,rcu=%d", audio_object_type, rate,
+        channel_config, pcu, rcu);
     return NULL;
   } else {
     return digit_to_string (ret);
index 856b71e048462ae3b9a0dff8c07dc8773252fe9c..ac5aeb5f6e0b30cc8d088d41e47012b32454c07c 100644 (file)
@@ -24,6 +24,7 @@
 
 #include <gst/check/gstcheck.h>
 #include <gst/pbutils/pbutils.h>
+#include <gst/base/gstbitwriter.h>
 
 #include <stdio.h>
 #include <glib/gstdio.h>
@@ -803,6 +804,69 @@ GST_START_TEST (test_pb_utils_versions)
 
 GST_END_TEST;
 
+GST_START_TEST (test_pb_utils_aac_get_profile)
+{
+  const guint8 aac_config[] = { 0x11, 0x90, 0x56, 0xE5, 0x00 };
+  const guint8 aac_config_sre[] = { 0x17, 0x80, 0x91, 0xA2, 0x82, 0x00 };
+  const guint8 heaac_config[] = { 0x2B, 0x11, 0x88, 0x00, 0x06, 0x01, 0x02 };
+  const gchar *profile, *level;
+  guint sample_rate;
+  GstBitWriter *wr;
+  guint8 *buf;
+  guint buf_len;
+
+  profile = gst_codec_utils_aac_get_profile (aac_config, sizeof (aac_config));
+  fail_unless (profile != NULL);
+  fail_unless_equals_string (profile, "lc");
+
+  level = gst_codec_utils_aac_get_level (aac_config, sizeof (aac_config));
+  fail_unless_equals_string (level, "2");
+
+  sample_rate =
+      gst_codec_utils_aac_get_sample_rate (aac_config, sizeof (aac_config));
+  fail_unless_equals_int (sample_rate, 48000);
+
+  sample_rate =
+      gst_codec_utils_aac_get_sample_rate (aac_config_sre,
+      sizeof (aac_config_sre));
+  fail_unless_equals_int (sample_rate, 0x12345);
+
+  profile =
+      gst_codec_utils_aac_get_profile (heaac_config, sizeof (heaac_config));
+  fail_unless (profile != NULL);
+  fail_unless_equals_string (profile, "lc");
+
+  level = gst_codec_utils_aac_get_level (heaac_config, sizeof (heaac_config));
+  fail_unless_equals_string (level, "2");
+
+  sample_rate =
+      gst_codec_utils_aac_get_sample_rate (heaac_config, sizeof (heaac_config));
+  fail_unless_equals_int (sample_rate, 48000);
+
+  wr = gst_bit_writer_new ();
+  fail_if (wr == NULL);
+  gst_bit_writer_put_bits_uint8 (wr, 5, 5);     /* object_type = 5 (SBR) */
+  gst_bit_writer_put_bits_uint8 (wr, 3, 4);     /* freq_index = 3 (48KHz) */
+  gst_bit_writer_put_bits_uint8 (wr, 2, 4);     /* channel_config = 2 (L&R) */
+  gst_bit_writer_put_bits_uint8 (wr, 0x0f, 4);  /* freq_index extension */
+  gst_bit_writer_put_bits_uint32 (wr, 87654, 24);       /* freq */
+  gst_bit_writer_put_bits_uint8 (wr, 2, 5);     /* object_type = 2 (LC) */
+
+  buf = gst_bit_writer_get_data (wr);
+  buf_len = gst_bit_writer_get_size (wr);
+  profile = gst_codec_utils_aac_get_profile (buf, buf_len);
+  fail_unless (profile != NULL);
+  fail_unless_equals_string (profile, "lc");
+  level = gst_codec_utils_aac_get_level (buf, buf_len);
+  fail_unless (level != NULL);
+  fail_unless_equals_string (level, "5");
+  sample_rate = gst_codec_utils_aac_get_sample_rate (buf, buf_len);
+  fail_unless_equals_int (sample_rate, 87654);
+  gst_bit_writer_free (wr);
+}
+
+GST_END_TEST;
+
 static Suite *
 libgstpbutils_suite (void)
 {
@@ -817,6 +881,7 @@ libgstpbutils_suite (void)
   tcase_add_test (tc_chain, test_pb_utils_install_plugins);
   tcase_add_test (tc_chain, test_pb_utils_installer_details);
   tcase_add_test (tc_chain, test_pb_utils_versions);
+  tcase_add_test (tc_chain, test_pb_utils_aac_get_profile);
   return s;
 }