aacparse: Properly report in the CAPS query that we can convert ADTS<->RAW
[platform/upstream/gst-plugins-good.git] / gst / audioparsers / gstaacparse.c
index aaed5f8..2a37c37 100644 (file)
@@ -15,8 +15,8 @@
  *
  * 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.
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
  */
 
 /**
@@ -33,7 +33,7 @@
  * <refsect2>
  * <title>Example launch line</title>
  * |[
- * gst-launch filesrc location=abc.aac ! aacparse ! faad ! audioresample ! audioconvert ! alsasink
+ * gst-launch-1.0 filesrc location=abc.aac ! aacparse ! faad ! audioresample ! audioconvert ! alsasink
  * ]|
  * </refsect2>
  */
@@ -44,6 +44,8 @@
 
 #include <string.h>
 
+#include <gst/base/gstbitreader.h>
+#include <gst/pbutils/pbutils.h>
 #include "gstaacparse.h"
 
 
@@ -52,7 +54,7 @@ static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
     GST_PAD_ALWAYS,
     GST_STATIC_CAPS ("audio/mpeg, "
         "framed = (boolean) true, " "mpegversion = (int) { 2, 4 }, "
-        "stream-format = (string) { raw, adts, adif };"));
+        "stream-format = (string) { raw, adts, adif, loas };"));
 
 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
     GST_PAD_SINK,
@@ -65,46 +67,39 @@ GST_DEBUG_CATEGORY_STATIC (aacparse_debug);
 
 #define ADIF_MAX_SIZE 40        /* Should be enough */
 #define ADTS_MAX_SIZE 10        /* Should be enough */
+#define LOAS_MAX_SIZE 3         /* Should be enough */
 
+#define ADTS_HEADERS_LENGTH 7UL /* Total byte-length of fixed and variable
+                                   headers prepended during raw to ADTS
+                                   conversion */
 
 #define AAC_FRAME_DURATION(parse) (GST_SECOND/parse->frames_per_sec)
 
-gboolean gst_aac_parse_start (GstBaseParse * parse);
-gboolean gst_aac_parse_stop (GstBaseParse * parse);
+static const gint loas_sample_rate_table[32] = {
+  96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
+  16000, 12000, 11025, 8000, 7350, 0, 0, 0
+};
+
+static const gint loas_channels_table[32] = {
+  0, 1, 2, 3, 4, 5, 6, 8,
+  0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static gboolean gst_aac_parse_start (GstBaseParse * parse);
+static gboolean gst_aac_parse_stop (GstBaseParse * parse);
 
 static gboolean gst_aac_parse_sink_setcaps (GstBaseParse * parse,
     GstCaps * caps);
+static GstCaps *gst_aac_parse_sink_getcaps (GstBaseParse * parse,
+    GstCaps * filter);
 
-gboolean gst_aac_parse_check_valid_frame (GstBaseParse * parse,
-    GstBaseParseFrame * frame, guint * size, gint * skipsize);
-
-GstFlowReturn gst_aac_parse_parse_frame (GstBaseParse * parse,
+static GstFlowReturn gst_aac_parse_handle_frame (GstBaseParse * parse,
+    GstBaseParseFrame * frame, gint * skipsize);
+static GstFlowReturn gst_aac_parse_pre_push_frame (GstBaseParse * parse,
     GstBaseParseFrame * frame);
 
-gboolean gst_aac_parse_convert (GstBaseParse * parse,
-    GstFormat src_format,
-    gint64 src_value, GstFormat dest_format, gint64 * dest_value);
-
-gint gst_aac_parse_get_frame_overhead (GstBaseParse * parse,
-    GstBuffer * buffer);
-
-gboolean gst_aac_parse_event (GstBaseParse * parse, GstEvent * event);
-
 G_DEFINE_TYPE (GstAacParse, gst_aac_parse, GST_TYPE_BASE_PARSE);
 
-static inline gint
-gst_aac_parse_get_sample_rate_from_index (guint sr_idx)
-{
-  static const guint aac_sample_rates[] = { 96000, 88200, 64000, 48000, 44100,
-    32000, 24000, 22050, 16000, 12000, 11025, 8000
-  };
-
-  if (sr_idx < G_N_ELEMENTS (aac_sample_rates))
-    return aac_sample_rates[sr_idx];
-  GST_WARNING ("Invalid sample rate index %u", sr_idx);
-  return 0;
-}
-
 /**
  * gst_aac_parse_class_init:
  * @klass: #GstAacParseClass.
@@ -124,16 +119,17 @@ gst_aac_parse_class_init (GstAacParseClass * klass)
   gst_element_class_add_pad_template (element_class,
       gst_static_pad_template_get (&src_template));
 
-  gst_element_class_set_details_simple (element_class,
+  gst_element_class_set_static_metadata (element_class,
       "AAC audio stream parser", "Codec/Parser/Audio",
       "Advanced Audio Coding parser", "Stefan Kost <stefan.kost@nokia.com>");
 
   parse_class->start = GST_DEBUG_FUNCPTR (gst_aac_parse_start);
   parse_class->stop = GST_DEBUG_FUNCPTR (gst_aac_parse_stop);
   parse_class->set_sink_caps = GST_DEBUG_FUNCPTR (gst_aac_parse_sink_setcaps);
-  parse_class->parse_frame = GST_DEBUG_FUNCPTR (gst_aac_parse_parse_frame);
-  parse_class->check_valid_frame =
-      GST_DEBUG_FUNCPTR (gst_aac_parse_check_valid_frame);
+  parse_class->get_sink_caps = GST_DEBUG_FUNCPTR (gst_aac_parse_sink_getcaps);
+  parse_class->handle_frame = GST_DEBUG_FUNCPTR (gst_aac_parse_handle_frame);
+  parse_class->pre_push_frame =
+      GST_DEBUG_FUNCPTR (gst_aac_parse_pre_push_frame);
 }
 
 
@@ -147,6 +143,7 @@ static void
 gst_aac_parse_init (GstAacParse * aacparse)
 {
   GST_DEBUG ("initialized");
+  GST_PAD_SET_ACCEPT_INTERSECT (GST_BASE_PARSE_SINK_PAD (aacparse));
 }
 
 
@@ -164,19 +161,22 @@ static gboolean
 gst_aac_parse_set_src_caps (GstAacParse * aacparse, GstCaps * sink_caps)
 {
   GstStructure *s;
-  GstCaps *src_caps = NULL;
+  GstCaps *src_caps = NULL, *allowed;
   gboolean res = FALSE;
   const gchar *stream_format;
+  GstBuffer *codec_data;
+  guint16 codec_data_data;
 
   GST_DEBUG_OBJECT (aacparse, "sink caps: %" GST_PTR_FORMAT, sink_caps);
   if (sink_caps)
     src_caps = gst_caps_copy (sink_caps);
   else
-    src_caps = gst_caps_new_simple ("audio/mpeg", NULL);
+    src_caps = gst_caps_new_empty_simple ("audio/mpeg");
 
   gst_caps_set_simple (src_caps, "framed", G_TYPE_BOOLEAN, TRUE,
       "mpegversion", G_TYPE_INT, aacparse->mpegversion, NULL);
 
+  aacparse->output_header_type = aacparse->header_type;
   switch (aacparse->header_type) {
     case DSPAAC_HEADER_NONE:
       stream_format = "raw";
@@ -187,6 +187,9 @@ gst_aac_parse_set_src_caps (GstAacParse * aacparse, GstCaps * sink_caps)
     case DSPAAC_HEADER_ADIF:
       stream_format = "adif";
       break;
+    case DSPAAC_HEADER_LOAS:
+      stream_format = "loas";
+      break;
     default:
       stream_format = NULL;
   }
@@ -199,11 +202,65 @@ gst_aac_parse_set_src_caps (GstAacParse * aacparse, GstCaps * sink_caps)
   if (stream_format)
     gst_structure_set (s, "stream-format", G_TYPE_STRING, stream_format, NULL);
 
+  allowed = gst_pad_get_allowed_caps (GST_BASE_PARSE (aacparse)->srcpad);
+  if (!gst_caps_can_intersect (src_caps, allowed)) {
+    GST_DEBUG_OBJECT (GST_BASE_PARSE (aacparse)->srcpad,
+        "Caps can not intersect");
+    if (aacparse->header_type == DSPAAC_HEADER_ADTS) {
+      GST_DEBUG_OBJECT (GST_BASE_PARSE (aacparse)->srcpad,
+          "Input is ADTS, trying raw");
+      gst_caps_set_simple (src_caps, "stream-format", G_TYPE_STRING, "raw",
+          NULL);
+      if (gst_caps_can_intersect (src_caps, allowed)) {
+        GstMapInfo map;
+        int idx;
+
+        idx =
+            gst_codec_utils_aac_get_index_from_sample_rate
+            (aacparse->sample_rate);
+        if (idx < 0)
+          goto not_a_known_rate;
+
+        GST_DEBUG_OBJECT (GST_BASE_PARSE (aacparse)->srcpad,
+            "Caps can intersect, we will drop the ADTS layer");
+        aacparse->output_header_type = DSPAAC_HEADER_NONE;
+
+        /* The codec_data data is according to AudioSpecificConfig,
+           ISO/IEC 14496-3, 1.6.2.1 */
+        codec_data = gst_buffer_new_and_alloc (2);
+        gst_buffer_map (codec_data, &map, GST_MAP_WRITE);
+        codec_data_data =
+            (aacparse->object_type << 11) |
+            (idx << 7) | (aacparse->channels << 3);
+        GST_WRITE_UINT16_BE (map.data, codec_data_data);
+        gst_buffer_unmap (codec_data, &map);
+        gst_caps_set_simple (src_caps, "codec_data", GST_TYPE_BUFFER,
+            codec_data, NULL);
+      }
+    } else if (aacparse->header_type == DSPAAC_HEADER_NONE) {
+      GST_DEBUG_OBJECT (GST_BASE_PARSE (aacparse)->srcpad,
+          "Input is raw, trying ADTS");
+      gst_caps_set_simple (src_caps, "stream-format", G_TYPE_STRING, "adts",
+          NULL);
+      if (gst_caps_can_intersect (src_caps, allowed)) {
+        GST_DEBUG_OBJECT (GST_BASE_PARSE (aacparse)->srcpad,
+            "Caps can intersect, we will prepend ADTS headers");
+        aacparse->output_header_type = DSPAAC_HEADER_ADTS;
+      }
+    }
+  }
+  gst_caps_unref (allowed);
+
   GST_DEBUG_OBJECT (aacparse, "setting src caps: %" GST_PTR_FORMAT, src_caps);
 
   res = gst_pad_set_caps (GST_BASE_PARSE (aacparse)->srcpad, src_caps);
   gst_caps_unref (src_caps);
   return res;
+
+not_a_known_rate:
+  gst_caps_unref (allowed);
+  gst_caps_unref (src_caps);
+  return FALSE;
 }
 
 
@@ -239,20 +296,20 @@ gst_aac_parse_sink_setcaps (GstBaseParse * parse, GstCaps * caps)
     GstBuffer *buf = gst_value_get_buffer (value);
 
     if (buf) {
-      guint8 *data;
-      gsize size;
+      GstMapInfo map;
       guint sr_idx;
 
-      data = gst_buffer_map (buf, &size, NULL, GST_MAP_READ);
+      gst_buffer_map (buf, &map, GST_MAP_READ);
 
-      sr_idx = ((data[0] & 0x07) << 1) | ((data[1] & 0x80) >> 7);
-      aacparse->object_type = (data[0] & 0xf8) >> 3;
-      aacparse->sample_rate = gst_aac_parse_get_sample_rate_from_index (sr_idx);
-      aacparse->channels = (data[1] & 0x78) >> 3;
+      sr_idx = ((map.data[0] & 0x07) << 1) | ((map.data[1] & 0x80) >> 7);
+      aacparse->object_type = (map.data[0] & 0xf8) >> 3;
+      aacparse->sample_rate =
+          gst_codec_utils_aac_get_sample_rate_from_index (sr_idx);
+      aacparse->channels = (map.data[1] & 0x78) >> 3;
       aacparse->header_type = DSPAAC_HEADER_NONE;
       aacparse->mpegversion = 4;
-      aacparse->frame_samples = (data[1] & 4) ? 960 : 1024;
-      gst_buffer_unmap (buf, data, size);
+      aacparse->frame_samples = (map.data[1] & 4) ? 960 : 1024;
+      gst_buffer_unmap (buf, &map);
 
       GST_DEBUG ("codec_data: object_type=%d, sample_rate=%d, channels=%d, "
           "samples=%d", aacparse->object_type, aacparse->sample_rate,
@@ -260,7 +317,8 @@ gst_aac_parse_sink_setcaps (GstBaseParse * parse, GstCaps * caps)
 
       /* arrange for metadata and get out of the way */
       gst_aac_parse_set_src_caps (aacparse, caps);
-      gst_base_parse_set_passthrough (parse, TRUE);
+      if (aacparse->header_type == aacparse->output_header_type)
+        gst_base_parse_set_passthrough (parse, TRUE);
     } else
       return FALSE;
 
@@ -268,6 +326,9 @@ gst_aac_parse_sink_setcaps (GstBaseParse * parse, GstCaps * caps)
     gst_structure_get_int (structure, "rate", &aacparse->sample_rate);
     gst_structure_get_int (structure, "channels", &aacparse->channels);
   } else {
+    aacparse->sample_rate = 0;
+    aacparse->channels = 0;
+    aacparse->header_type = DSPAAC_HEADER_NOT_PARSED;
     gst_base_parse_set_passthrough (parse, FALSE);
   }
 
@@ -326,12 +387,39 @@ gst_aac_parse_check_adts_frame (GstAacParse * aacparse,
     const guint8 * data, const guint avail, gboolean drain,
     guint * framesize, guint * needed_data)
 {
-  if (G_UNLIKELY (avail < 2))
+  guint crc_size;
+
+  *needed_data = 0;
+
+  /* Absolute minimum to perform the ADTS syncword,
+     layer and sampling frequency tests */
+  if (G_UNLIKELY (avail < 3))
     return FALSE;
 
+  /* Syncword and layer tests */
   if ((data[0] == 0xff) && ((data[1] & 0xf6) == 0xf0)) {
+
+    /* Sampling frequency test */
+    if (G_UNLIKELY ((data[2] & 0x3C) >> 2 == 15))
+      return FALSE;
+
+    /* This looks like an ADTS frame header but
+       we need at least 6 bytes to proceed */
+    if (G_UNLIKELY (avail < 6)) {
+      *needed_data = 6;
+      return FALSE;
+    }
+
     *framesize = gst_aac_parse_adts_get_frame_len (data);
 
+    /* If frame has CRC, it needs 2 bytes
+       for it at the end of the header */
+    crc_size = (data[1] & 0x01) ? 0 : 2;
+
+    /* CRC size test */
+    if (*framesize < 7 + crc_size)
+      return FALSE;
+
     /* In EOS mode this is enough. No need to examine the data further.
        We also relax the check when we have sync, on the assumption that
        if we're not looking at random data, we have a much higher chance
@@ -364,6 +452,294 @@ gst_aac_parse_check_adts_frame (GstAacParse * aacparse,
   return FALSE;
 }
 
+static gboolean
+gst_aac_parse_latm_get_value (GstAacParse * aacparse, GstBitReader * br,
+    guint32 * value)
+{
+  guint8 bytes, i, byte;
+
+  *value = 0;
+  if (!gst_bit_reader_get_bits_uint8 (br, &bytes, 2))
+    return FALSE;
+  for (i = 0; i < bytes; ++i) {
+    *value <<= 8;
+    if (!gst_bit_reader_get_bits_uint8 (br, &byte, 8))
+      return FALSE;
+    *value += byte;
+  }
+  return TRUE;
+}
+
+static gboolean
+gst_aac_parse_get_audio_object_type (GstAacParse * aacparse, GstBitReader * br,
+    guint8 * audio_object_type)
+{
+  if (!gst_bit_reader_get_bits_uint8 (br, audio_object_type, 5))
+    return FALSE;
+  if (*audio_object_type == 31) {
+    if (!gst_bit_reader_get_bits_uint8 (br, audio_object_type, 6))
+      return FALSE;
+    *audio_object_type += 32;
+  }
+  GST_LOG_OBJECT (aacparse, "audio object type %u", *audio_object_type);
+  return TRUE;
+}
+
+static gboolean
+gst_aac_parse_get_audio_sample_rate (GstAacParse * aacparse, GstBitReader * br,
+    gint * sample_rate)
+{
+  guint8 sampling_frequency_index;
+  if (!gst_bit_reader_get_bits_uint8 (br, &sampling_frequency_index, 4))
+    return FALSE;
+  GST_LOG_OBJECT (aacparse, "sampling_frequency_index: %u",
+      sampling_frequency_index);
+  if (sampling_frequency_index == 0xf) {
+    guint32 sampling_rate;
+    if (!gst_bit_reader_get_bits_uint32 (br, &sampling_rate, 24))
+      return FALSE;
+    *sample_rate = sampling_rate;
+  } else {
+    *sample_rate = loas_sample_rate_table[sampling_frequency_index];
+    if (!*sample_rate)
+      return FALSE;
+  }
+  return TRUE;
+}
+
+/* See table 1.13 in ISO/IEC 14496-3 */
+static gboolean
+gst_aac_parse_read_loas_audio_specific_config (GstAacParse * aacparse,
+    GstBitReader * br, gint * sample_rate, gint * channels, guint32 * bits)
+{
+  guint8 audio_object_type, channel_configuration;
+
+  if (!gst_aac_parse_get_audio_object_type (aacparse, br, &audio_object_type))
+    return FALSE;
+
+  if (!gst_aac_parse_get_audio_sample_rate (aacparse, br, sample_rate))
+    return FALSE;
+
+  if (!gst_bit_reader_get_bits_uint8 (br, &channel_configuration, 4))
+    return FALSE;
+  GST_LOG_OBJECT (aacparse, "channel_configuration: %d", channel_configuration);
+  *channels = loas_channels_table[channel_configuration];
+  if (!*channels)
+    return FALSE;
+
+  if (audio_object_type == 5) {
+    GST_LOG_OBJECT (aacparse,
+        "Audio object type 5, so rereading sampling rate...");
+    if (!gst_aac_parse_get_audio_sample_rate (aacparse, br, sample_rate))
+      return FALSE;
+  }
+
+  GST_INFO_OBJECT (aacparse, "Found LOAS config: %d Hz, %d channels",
+      *sample_rate, *channels);
+
+  /* There's LOTS of stuff next, but we ignore it for now as we have
+     what we want (sample rate and number of channels */
+  GST_DEBUG_OBJECT (aacparse,
+      "Need more code to parse humongous LOAS data, currently ignored");
+  if (bits)
+    *bits = 0;
+  return TRUE;
+}
+
+
+static gboolean
+gst_aac_parse_read_loas_config (GstAacParse * aacparse, const guint8 * data,
+    guint avail, gint * sample_rate, gint * channels, gint * version)
+{
+  GstBitReader br;
+  guint8 u8, v, vA;
+
+  /* No version in the bitstream, but the spec has LOAS in the MPEG-4 section */
+  if (version)
+    *version = 4;
+
+  gst_bit_reader_init (&br, data, avail);
+
+  /* skip sync word (11 bits) and size (13 bits) */
+  if (!gst_bit_reader_skip (&br, 11 + 13))
+    return FALSE;
+
+  /* First bit is "use last config" */
+  if (!gst_bit_reader_get_bits_uint8 (&br, &u8, 1))
+    return FALSE;
+  if (u8) {
+    GST_DEBUG_OBJECT (aacparse, "Frame uses previous config");
+    if (!aacparse->sample_rate || !aacparse->channels) {
+      GST_WARNING_OBJECT (aacparse, "No previous config to use");
+    }
+    *sample_rate = aacparse->sample_rate;
+    *channels = aacparse->channels;
+    return TRUE;
+  }
+
+  GST_DEBUG_OBJECT (aacparse, "Frame contains new config");
+
+  if (!gst_bit_reader_get_bits_uint8 (&br, &v, 1))
+    return FALSE;
+  if (v) {
+    if (!gst_bit_reader_get_bits_uint8 (&br, &vA, 1))
+      return FALSE;
+  } else
+    vA = 0;
+
+  GST_LOG_OBJECT (aacparse, "v %d, vA %d", v, vA);
+  if (vA == 0) {
+    guint8 same_time, subframes, num_program, prog;
+    if (v == 1) {
+      guint32 value;
+      if (!gst_aac_parse_latm_get_value (aacparse, &br, &value))
+        return FALSE;
+    }
+    if (!gst_bit_reader_get_bits_uint8 (&br, &same_time, 1))
+      return FALSE;
+    if (!gst_bit_reader_get_bits_uint8 (&br, &subframes, 6))
+      return FALSE;
+    if (!gst_bit_reader_get_bits_uint8 (&br, &num_program, 4))
+      return FALSE;
+    GST_LOG_OBJECT (aacparse, "same_time %d, subframes %d, num_program %d",
+        same_time, subframes, num_program);
+
+    for (prog = 0; prog <= num_program; ++prog) {
+      guint8 num_layer, layer;
+      if (!gst_bit_reader_get_bits_uint8 (&br, &num_layer, 3))
+        return FALSE;
+      GST_LOG_OBJECT (aacparse, "Program %d: %d layers", prog, num_layer);
+
+      for (layer = 0; layer <= num_layer; ++layer) {
+        guint8 use_same_config;
+        if (prog == 0 && layer == 0) {
+          use_same_config = 0;
+        } else {
+          if (!gst_bit_reader_get_bits_uint8 (&br, &use_same_config, 1))
+            return FALSE;
+        }
+        if (!use_same_config) {
+          if (v == 0) {
+            if (!gst_aac_parse_read_loas_audio_specific_config (aacparse, &br,
+                    sample_rate, channels, NULL))
+              return FALSE;
+          } else {
+            guint32 bits, asc_len;
+            if (!gst_aac_parse_latm_get_value (aacparse, &br, &asc_len))
+              return FALSE;
+            if (!gst_aac_parse_read_loas_audio_specific_config (aacparse, &br,
+                    sample_rate, channels, &bits))
+              return FALSE;
+            asc_len -= bits;
+            if (!gst_bit_reader_skip (&br, asc_len))
+              return FALSE;
+          }
+        }
+      }
+    }
+    GST_LOG_OBJECT (aacparse, "More data ignored");
+  } else {
+    GST_WARNING_OBJECT (aacparse, "Spec says \"TBD\"...");
+  }
+  return TRUE;
+}
+
+/**
+ * gst_aac_parse_loas_get_frame_len:
+ * @data: block of data containing a LOAS header.
+ *
+ * This function calculates LOAS frame length from the given header.
+ *
+ * Returns: size of the LOAS frame.
+ */
+static inline guint
+gst_aac_parse_loas_get_frame_len (const guint8 * data)
+{
+  return (((data[1] & 0x1f) << 8) | data[2]) + 3;
+}
+
+
+/**
+ * gst_aac_parse_check_loas_frame:
+ * @aacparse: #GstAacParse.
+ * @data: Data to be checked.
+ * @avail: Amount of data passed.
+ * @framesize: If valid LOAS frame was found, this will be set to tell the
+ *             found frame size in bytes.
+ * @needed_data: If frame was not found, this may be set to tell how much
+ *               more data is needed in the next round to detect the frame
+ *               reliably. This may happen when a frame header candidate
+ *               is found but it cannot be guaranteed to be the header without
+ *               peeking the following data.
+ *
+ * Check if the given data contains contains LOAS frame. The algorithm
+ * will examine LOAS frame header and calculate the frame size. Also, another
+ * consecutive LOAS frame header need to be present after the found frame.
+ * Otherwise the data is not considered as a valid LOAS frame. However, this
+ * "extra check" is omitted when EOS has been received. In this case it is
+ * enough when data[0] contains a valid LOAS header.
+ *
+ * This function may set the #needed_data to indicate that a possible frame
+ * candidate has been found, but more data (#needed_data bytes) is needed to
+ * be absolutely sure. When this situation occurs, FALSE will be returned.
+ *
+ * When a valid frame is detected, this function will use
+ * gst_base_parse_set_min_frame_size() function from #GstBaseParse class
+ * to set the needed bytes for next frame.This way next data chunk is already
+ * of correct size.
+ *
+ * LOAS can have three different formats, if I read the spec correctly. Only
+ * one of them is supported here, as the two samples I have use this one.
+ *
+ * Returns: TRUE if the given data contains a valid LOAS header.
+ */
+static gboolean
+gst_aac_parse_check_loas_frame (GstAacParse * aacparse,
+    const guint8 * data, const guint avail, gboolean drain,
+    guint * framesize, guint * needed_data)
+{
+  *needed_data = 0;
+
+  /* 3 byte header */
+  if (G_UNLIKELY (avail < 3))
+    return FALSE;
+
+  if ((data[0] == 0x56) && ((data[1] & 0xe0) == 0xe0)) {
+    *framesize = gst_aac_parse_loas_get_frame_len (data);
+    GST_DEBUG_OBJECT (aacparse, "Found %u byte LOAS frame", *framesize);
+
+    /* In EOS mode this is enough. No need to examine the data further.
+       We also relax the check when we have sync, on the assumption that
+       if we're not looking at random data, we have a much higher chance
+       to get the correct sync, and this avoids losing two frames when
+       a single bit corruption happens. */
+    if (drain || !GST_BASE_PARSE_LOST_SYNC (aacparse)) {
+      return TRUE;
+    }
+
+    if (*framesize + LOAS_MAX_SIZE > avail) {
+      /* We have found a possible frame header candidate, but can't be
+         sure since we don't have enough data to check the next frame */
+      GST_DEBUG ("NEED MORE DATA: we need %d, available %d",
+          *framesize + LOAS_MAX_SIZE, avail);
+      *needed_data = *framesize + LOAS_MAX_SIZE;
+      gst_base_parse_set_min_frame_size (GST_BASE_PARSE (aacparse),
+          *framesize + LOAS_MAX_SIZE);
+      return FALSE;
+    }
+
+    if ((data[*framesize] == 0x56) && ((data[*framesize + 1] & 0xe0) == 0xe0)) {
+      guint nextlen = gst_aac_parse_loas_get_frame_len (data + (*framesize));
+
+      GST_LOG ("LOAS frame found, len: %d bytes", *framesize);
+      gst_base_parse_set_min_frame_size (GST_BASE_PARSE (aacparse),
+          nextlen + LOAS_MAX_SIZE);
+      return TRUE;
+    }
+  }
+  return FALSE;
+}
+
 /* caller ensure sufficient data */
 static inline void
 gst_aac_parse_parse_adts_header (GstAacParse * aacparse, const guint8 * data,
@@ -373,7 +749,7 @@ gst_aac_parse_parse_adts_header (GstAacParse * aacparse, const guint8 * data,
   if (rate) {
     gint sr_idx = (data[2] & 0x3c) >> 2;
 
-    *rate = gst_aac_parse_get_sample_rate_from_index (sr_idx);
+    *rate = gst_codec_utils_aac_get_sample_rate_from_index (sr_idx);
   }
   if (channels)
     *channels = ((data[2] & 0x01) << 2) | ((data[3] & 0xc0) >> 6);
@@ -381,7 +757,7 @@ gst_aac_parse_parse_adts_header (GstAacParse * aacparse, const guint8 * data,
   if (version)
     *version = (data[1] & 0x08) ? 2 : 4;
   if (object)
-    *object = (data[2] & 0xc0) >> 6;
+    *object = ((data[2] & 0xc0) >> 6) + 1;
 }
 
 /**
@@ -409,7 +785,7 @@ gst_aac_parse_detect_stream (GstAacParse * aacparse,
     guint * framesize, gint * skipsize)
 {
   gboolean found = FALSE;
-  guint need_data = 0;
+  guint need_data_adts = 0, need_data_loas;
   guint i = 0;
 
   GST_DEBUG_OBJECT (aacparse, "Parsing header data");
@@ -418,12 +794,16 @@ gst_aac_parse_detect_stream (GstAacParse * aacparse,
      stream */
 
   /* Can we even parse the header? */
-  if (avail < ADTS_MAX_SIZE)
+  if (avail < MAX (ADTS_MAX_SIZE, LOAS_MAX_SIZE)) {
+    GST_DEBUG_OBJECT (aacparse, "Not enough data to check");
     return FALSE;
+  }
 
   for (i = 0; i < avail - 4; i++) {
     if (((data[i] == 0xff) && ((data[i + 1] & 0xf6) == 0xf0)) ||
+        ((data[0] == 0x56) && ((data[1] & 0xe0) == 0xe0)) ||
         strncmp ((char *) data + i, "ADIF", 4) == 0) {
+      GST_DEBUG_OBJECT (aacparse, "Found signature at offset %u", i);
       found = TRUE;
 
       if (i) {
@@ -443,15 +823,20 @@ gst_aac_parse_detect_stream (GstAacParse * aacparse,
   }
 
   if (gst_aac_parse_check_adts_frame (aacparse, data, avail, drain,
-          framesize, &need_data)) {
+          framesize, &need_data_adts)) {
     gint rate, channels;
 
     GST_INFO ("ADTS ID: %d, framesize: %d", (data[1] & 0x08) >> 3, *framesize);
 
-    aacparse->header_type = DSPAAC_HEADER_ADTS;
     gst_aac_parse_parse_adts_header (aacparse, data, &rate, &channels,
         &aacparse->object_type, &aacparse->mpegversion);
 
+    if (!channels || !framesize) {
+      GST_DEBUG_OBJECT (aacparse, "impossible ADTS configuration");
+      return FALSE;
+    }
+
+    aacparse->header_type = DSPAAC_HEADER_ADTS;
     gst_base_parse_set_frame_rate (GST_BASE_PARSE (aacparse), rate,
         aacparse->frame_samples, 2, 2);
 
@@ -461,7 +846,38 @@ gst_aac_parse_detect_stream (GstAacParse * aacparse,
     gst_base_parse_set_syncable (GST_BASE_PARSE (aacparse), TRUE);
 
     return TRUE;
-  } else if (need_data) {
+  }
+
+  if (gst_aac_parse_check_loas_frame (aacparse, data, avail, drain,
+          framesize, &need_data_loas)) {
+    gint rate, channels;
+
+    GST_INFO ("LOAS, framesize: %d", *framesize);
+
+    aacparse->header_type = DSPAAC_HEADER_LOAS;
+
+    if (!gst_aac_parse_read_loas_config (aacparse, data, avail, &rate,
+            &channels, &aacparse->mpegversion)) {
+      GST_WARNING_OBJECT (aacparse, "Error reading LOAS config");
+      return FALSE;
+    }
+
+    if (rate && channels) {
+      gst_base_parse_set_frame_rate (GST_BASE_PARSE (aacparse), rate,
+          aacparse->frame_samples, 2, 2);
+
+      GST_DEBUG ("LOAS: samplerate %d, channels %d, objtype %d, version %d",
+          rate, channels, aacparse->object_type, aacparse->mpegversion);
+      aacparse->sample_rate = rate;
+      aacparse->channels = channels;
+    }
+
+    gst_base_parse_set_syncable (GST_BASE_PARSE (aacparse), TRUE);
+
+    return TRUE;
+  }
+
+  if (need_data_adts || need_data_loas) {
     /* This tells the parent class not to skip any data */
     *skipsize = 0;
     return FALSE;
@@ -524,7 +940,8 @@ gst_aac_parse_detect_stream (GstAacParse * aacparse,
 
     /* FIXME: This gives totally wrong results. Duration calculation cannot
        be based on this */
-    aacparse->sample_rate = gst_aac_parse_get_sample_rate_from_index (sr_idx);
+    aacparse->sample_rate =
+        gst_codec_utils_aac_get_sample_rate_from_index (sr_idx);
 
     /* baseparse is not given any fps,
      * so it will give up on timestamps, seeking, etc */
@@ -540,7 +957,8 @@ gst_aac_parse_detect_stream (GstAacParse * aacparse,
     /* arrange for metadata and get out of the way */
     sinkcaps = gst_pad_get_current_caps (GST_BASE_PARSE_SINK_PAD (aacparse));
     gst_aac_parse_set_src_caps (aacparse, sinkcaps);
-    gst_caps_unref (sinkcaps);
+    if (sinkcaps)
+      gst_caps_unref (sinkcaps);
 
     /* not syncable, not easily seekable (unless we push data from start */
     gst_base_parse_set_syncable (GST_BASE_PARSE_CAST (aacparse), FALSE);
@@ -555,53 +973,257 @@ gst_aac_parse_detect_stream (GstAacParse * aacparse,
   return FALSE;
 }
 
+/**
+ * gst_aac_parse_get_audio_profile_object_type
+ * @aacparse: #GstAacParse.
+ *
+ * Gets the MPEG-2 profile or the MPEG-4 object type value corresponding to the
+ * mpegversion and profile of @aacparse's src pad caps, according to the
+ * values defined by table 1.A.11 in ISO/IEC 14496-3.
+ *
+ * Returns: the profile or object type value corresponding to @aacparse's src
+ * pad caps, if such a value exists; otherwise G_MAXUINT8.
+ */
+static guint8
+gst_aac_parse_get_audio_profile_object_type (GstAacParse * aacparse)
+{
+  GstCaps *srccaps;
+  GstStructure *srcstruct;
+  const gchar *profile;
+  guint8 ret;
+
+  srccaps = gst_pad_get_current_caps (GST_BASE_PARSE_SRC_PAD (aacparse));
+  srcstruct = gst_caps_get_structure (srccaps, 0);
+  profile = gst_structure_get_string (srcstruct, "profile");
+  if (G_UNLIKELY (profile == NULL)) {
+    gst_caps_unref (srccaps);
+    return G_MAXUINT8;
+  }
+
+  if (g_strcmp0 (profile, "main") == 0) {
+    ret = (guint8) 0U;
+  } else if (g_strcmp0 (profile, "lc") == 0) {
+    ret = (guint8) 1U;
+  } else if (g_strcmp0 (profile, "ssr") == 0) {
+    ret = (guint8) 2U;
+  } else if (g_strcmp0 (profile, "ltp") == 0) {
+    if (G_LIKELY (aacparse->mpegversion == 4))
+      ret = (guint8) 3U;
+    else
+      ret = G_MAXUINT8;         /* LTP Object Type allowed only for MPEG-4 */
+  } else {
+    ret = G_MAXUINT8;
+  }
+
+  gst_caps_unref (srccaps);
+  return ret;
+}
+
+/**
+ * gst_aac_parse_get_audio_channel_configuration
+ * @num_channels: number of audio channels.
+ *
+ * Gets the Channel Configuration value, as defined by table 1.19 in ISO/IEC
+ * 14496-3, for a given number of audio channels.
+ *
+ * Returns: the Channel Configuration value corresponding to @num_channels, if
+ * such a value exists; otherwise G_MAXUINT8.
+ */
+static guint8
+gst_aac_parse_get_audio_channel_configuration (gint num_channels)
+{
+  if (num_channels >= 1 && num_channels <= 6)   /* Mono up to & including 5.1 */
+    return (guint8) num_channels;
+  else if (num_channels == 8)   /* 7.1 */
+    return (guint8) 7U;
+  else
+    return G_MAXUINT8;
+}
+
+/**
+ * gst_aac_parse_get_audio_sampling_frequency_index:
+ * @sample_rate: audio sampling rate.
+ *
+ * Gets the Sampling Frequency Index value, as defined by table 1.18 in ISO/IEC
+ * 14496-3, for a given sampling rate.
+ *
+ * Returns: the Sampling Frequency Index value corresponding to @sample_rate,
+ * if such a value exists; otherwise G_MAXUINT8.
+ */
+static guint8
+gst_aac_parse_get_audio_sampling_frequency_index (gint sample_rate)
+{
+  switch (sample_rate) {
+    case 96000:
+      return 0x0U;
+    case 88200:
+      return 0x1U;
+    case 64000:
+      return 0x2U;
+    case 48000:
+      return 0x3U;
+    case 44100:
+      return 0x4U;
+    case 32000:
+      return 0x5U;
+    case 24000:
+      return 0x6U;
+    case 22050:
+      return 0x7U;
+    case 16000:
+      return 0x8U;
+    case 12000:
+      return 0x9U;
+    case 11025:
+      return 0xAU;
+    case 8000:
+      return 0xBU;
+    case 7350:
+      return 0xCU;
+    default:
+      return G_MAXUINT8;
+  }
+}
+
+/**
+ * gst_aac_parse_prepend_adts_headers:
+ * @aacparse: #GstAacParse.
+ * @frame: raw AAC frame to which ADTS headers shall be prepended.
+ *
+ * Prepends ADTS headers to a raw AAC audio frame.
+ *
+ * Returns: TRUE if ADTS headers were successfully prepended; FALSE otherwise.
+ */
+static gboolean
+gst_aac_parse_prepend_adts_headers (GstAacParse * aacparse,
+    GstBaseParseFrame * frame)
+{
+  GstMemory *mem;
+  guint8 *adts_headers;
+  gsize buf_size;
+  gsize frame_size;
+  guint8 id, profile, channel_configuration, sampling_frequency_index;
+
+  id = (aacparse->mpegversion == 4) ? 0x0U : 0x1U;
+  profile = gst_aac_parse_get_audio_profile_object_type (aacparse);
+  if (profile == G_MAXUINT8) {
+    GST_ERROR_OBJECT (aacparse, "Unsupported audio profile or object type");
+    return FALSE;
+  }
+  channel_configuration =
+      gst_aac_parse_get_audio_channel_configuration (aacparse->channels);
+  if (channel_configuration == G_MAXUINT8) {
+    GST_ERROR_OBJECT (aacparse, "Unsupported number of channels");
+    return FALSE;
+  }
+  sampling_frequency_index =
+      gst_aac_parse_get_audio_sampling_frequency_index (aacparse->sample_rate);
+  if (sampling_frequency_index == G_MAXUINT8) {
+    GST_ERROR_OBJECT (aacparse, "Unsupported sampling frequency");
+    return FALSE;
+  }
+
+  frame->out_buffer = gst_buffer_copy (frame->buffer);
+  buf_size = gst_buffer_get_size (frame->out_buffer);
+  frame_size = buf_size + ADTS_HEADERS_LENGTH;
+
+  if (G_UNLIKELY (frame_size >= 0x4000)) {
+    GST_ERROR_OBJECT (aacparse, "Frame size is too big for ADTS");
+    return FALSE;
+  }
+
+  adts_headers = (guint8 *) g_malloc0 (ADTS_HEADERS_LENGTH);
+
+  /* Note: no error correction bits are added to the resulting ADTS frames */
+  adts_headers[0] = 0xFFU;
+  adts_headers[1] = 0xF0U | (id << 3) | 0x1U;
+  adts_headers[2] = (profile << 6) | (sampling_frequency_index << 2) | 0x2U |
+      (channel_configuration & 0x4U);
+  adts_headers[3] = ((channel_configuration & 0x3U) << 6) | 0x30U |
+      (guint8) (frame_size >> 11);
+  adts_headers[4] = (guint8) ((frame_size >> 3) & 0x00FF);
+  adts_headers[5] = (guint8) (((frame_size & 0x0007) << 5) + 0x1FU);
+  adts_headers[6] = 0xFCU;
+
+  mem = gst_memory_new_wrapped (0, adts_headers, ADTS_HEADERS_LENGTH, 0,
+      ADTS_HEADERS_LENGTH, NULL, NULL);
+  gst_buffer_prepend_memory (frame->out_buffer, mem);
+
+  return TRUE;
+}
 
 /**
  * gst_aac_parse_check_valid_frame:
  * @parse: #GstBaseParse.
- * @buffer: #GstBuffer.
- * @framesize: If the buffer contains a valid frame, its size will be put here
+ * @frame: #GstBaseParseFrame.
  * @skipsize: How much data parent class should skip in order to find the
  *            frame header.
  *
- * Implementation of "check_valid_frame" vmethod in #GstBaseParse class.
+ * Implementation of "handle_frame" vmethod in #GstBaseParse class.
  *
- * Returns: TRUE if buffer contains a valid frame.
+ * Also determines frame overhead.
+ * ADTS streams have a 7 byte header in each frame. MP4 and ADIF streams don't have
+ * a per-frame header. LOAS has 3 bytes.
+ *
+ * We're making a couple of simplifying assumptions:
+ *
+ * 1. We count Program Configuration Elements rather than searching for them
+ *    in the streams to discount them - the overhead is negligible.
+ *
+ * 2. We ignore CRC. This has a worst-case impact of (num_raw_blocks + 1)*16
+ *    bits, which should still not be significant enough to warrant the
+ *    additional parsing through the headers
+ *
+ * Returns: a #GstFlowReturn.
  */
-gboolean
-gst_aac_parse_check_valid_frame (GstBaseParse * parse,
-    GstBaseParseFrame * frame, guint * framesize, gint * skipsize)
+static GstFlowReturn
+gst_aac_parse_handle_frame (GstBaseParse * parse,
+    GstBaseParseFrame * frame, gint * skipsize)
 {
-  guint8 *data;
-  gsize size;
+  GstMapInfo map;
   GstAacParse *aacparse;
   gboolean ret = FALSE;
   gboolean lost_sync;
   GstBuffer *buffer;
+  guint framesize;
+  gint rate, channels;
 
   aacparse = GST_AAC_PARSE (parse);
   buffer = frame->buffer;
 
-  data = gst_buffer_map (buffer, &size, NULL, GST_MAP_READ);
+  gst_buffer_map (buffer, &map, GST_MAP_READ);
 
+  *skipsize = -1;
   lost_sync = GST_BASE_PARSE_LOST_SYNC (parse);
 
   if (aacparse->header_type == DSPAAC_HEADER_ADIF ||
       aacparse->header_type == DSPAAC_HEADER_NONE) {
     /* There is nothing to parse */
-    *framesize = size;
+    framesize = map.size;
     ret = TRUE;
 
   } else if (aacparse->header_type == DSPAAC_HEADER_NOT_PARSED || lost_sync) {
 
-    ret = gst_aac_parse_detect_stream (aacparse, data, size,
-        GST_BASE_PARSE_DRAINING (parse), framesize, skipsize);
+    ret = gst_aac_parse_detect_stream (aacparse, map.data, map.size,
+        GST_BASE_PARSE_DRAINING (parse), &framesize, skipsize);
 
   } else if (aacparse->header_type == DSPAAC_HEADER_ADTS) {
     guint needed_data = 1024;
 
-    ret = gst_aac_parse_check_adts_frame (aacparse, data, size,
-        GST_BASE_PARSE_DRAINING (parse), framesize, &needed_data);
+    ret = gst_aac_parse_check_adts_frame (aacparse, map.data, map.size,
+        GST_BASE_PARSE_DRAINING (parse), &framesize, &needed_data);
+
+    if (!ret) {
+      GST_DEBUG ("buffer didn't contain valid frame");
+      gst_base_parse_set_min_frame_size (GST_BASE_PARSE (aacparse),
+          needed_data);
+    }
+
+  } else if (aacparse->header_type == DSPAAC_HEADER_LOAS) {
+    guint needed_data = 1024;
+
+    ret = gst_aac_parse_check_loas_frame (aacparse, map.data,
+        map.size, GST_BASE_PARSE_DRAINING (parse), &framesize, &needed_data);
 
     if (!ret) {
       GST_DEBUG ("buffer didn't contain valid frame");
@@ -614,81 +1236,130 @@ gst_aac_parse_check_valid_frame (GstBaseParse * parse,
     gst_base_parse_set_min_frame_size (GST_BASE_PARSE (aacparse),
         ADTS_MAX_SIZE);
   }
-  gst_buffer_unmap (buffer, data, size);
-
-  return ret;
-}
 
+  if (G_UNLIKELY (!ret))
+    goto exit;
 
-/**
- * gst_aac_parse_parse_frame:
- * @parse: #GstBaseParse.
- * @buffer: #GstBuffer.
- *
- * Implementation of "parse_frame" vmethod in #GstBaseParse class.
- *
- * Also determines frame overhead.
- * ADTS streams have a 7 byte header in each frame. MP4 and ADIF streams don't have
- * a per-frame header.
- *
- * We're making a couple of simplifying assumptions:
- *
- * 1. We count Program Configuration Elements rather than searching for them
- *    in the streams to discount them - the overhead is negligible.
- *
- * 2. We ignore CRC. This has a worst-case impact of (num_raw_blocks + 1)*16
- *    bits, which should still not be significant enough to warrant the
- *    additional parsing through the headers
- *
- * Returns: GST_FLOW_OK if frame was successfully parsed and can be pushed
- *          forward. Otherwise appropriate error is returned.
- */
-GstFlowReturn
-gst_aac_parse_parse_frame (GstBaseParse * parse, GstBaseParseFrame * frame)
-{
-  GstAacParse *aacparse;
-  GstBuffer *buffer;
-  GstFlowReturn ret = GST_FLOW_OK;
-  gint rate, channels;
-  guint8 *data;
-  gsize size;
+  if (aacparse->header_type == DSPAAC_HEADER_ADTS) {
+    /* see above */
+    frame->overhead = 7;
 
-  aacparse = GST_AAC_PARSE (parse);
-  buffer = frame->buffer;
+    gst_aac_parse_parse_adts_header (aacparse, map.data,
+        &rate, &channels, NULL, NULL);
 
-  if (G_UNLIKELY (aacparse->header_type != DSPAAC_HEADER_ADTS))
-    return ret;
+    GST_LOG_OBJECT (aacparse, "rate: %d, chans: %d", rate, channels);
 
-  /* see above */
-  frame->overhead = 7;
+    if (G_UNLIKELY (rate != aacparse->sample_rate
+            || channels != aacparse->channels)) {
+      aacparse->sample_rate = rate;
+      aacparse->channels = channels;
 
-  data = gst_buffer_map (buffer, &size, NULL, GST_MAP_READ);
-  gst_aac_parse_parse_adts_header (aacparse, data,
-      &rate, &channels, NULL, NULL);
-  gst_buffer_unmap (buffer, data, size);
+      if (!gst_aac_parse_set_src_caps (aacparse, NULL)) {
+        /* If linking fails, we need to return appropriate error */
+        ret = GST_FLOW_NOT_LINKED;
+      }
 
-  GST_LOG_OBJECT (aacparse, "rate: %d, chans: %d", rate, channels);
+      gst_base_parse_set_frame_rate (GST_BASE_PARSE (aacparse),
+          aacparse->sample_rate, aacparse->frame_samples, 2, 2);
+    }
+  } else if (aacparse->header_type == DSPAAC_HEADER_LOAS) {
+    gboolean setcaps = FALSE;
+
+    /* see above */
+    frame->overhead = 3;
+
+    if (!gst_aac_parse_read_loas_config (aacparse, map.data, map.size, &rate,
+            &channels, NULL)) {
+      GST_WARNING_OBJECT (aacparse, "Error reading LOAS config");
+    } else if (G_UNLIKELY (rate != aacparse->sample_rate
+            || channels != aacparse->channels)) {
+      aacparse->sample_rate = rate;
+      aacparse->channels = channels;
+      setcaps = TRUE;
+      GST_INFO_OBJECT (aacparse, "New LOAS config: %d Hz, %d channels", rate,
+          channels);
+    }
 
-  if (G_UNLIKELY (rate != aacparse->sample_rate
-          || channels != aacparse->channels)) {
-    GstCaps *sinkcaps;
+    /* We want to set caps both at start, and when rate/channels change.
+       Since only some LOAS frames have that info, we may receive frames
+       before knowing about rate/channels. */
+    if (setcaps
+        || !gst_pad_has_current_caps (GST_BASE_PARSE_SRC_PAD (aacparse))) {
+      if (!gst_aac_parse_set_src_caps (aacparse, NULL)) {
+        /* If linking fails, we need to return appropriate error */
+        ret = GST_FLOW_NOT_LINKED;
+      }
 
-    aacparse->sample_rate = rate;
-    aacparse->channels = channels;
+      gst_base_parse_set_frame_rate (GST_BASE_PARSE (aacparse),
+          aacparse->sample_rate, aacparse->frame_samples, 2, 2);
+    }
+  }
 
-    sinkcaps = gst_pad_get_current_caps (GST_BASE_PARSE (aacparse)->sinkpad);
-    if (!gst_aac_parse_set_src_caps (aacparse, sinkcaps)) {
-      /* If linking fails, we need to return appropriate error */
-      gst_caps_unref (sinkcaps);
-      ret = GST_FLOW_NOT_LINKED;
+  if (aacparse->header_type == DSPAAC_HEADER_NONE
+      && aacparse->output_header_type == DSPAAC_HEADER_ADTS) {
+    if (!gst_aac_parse_prepend_adts_headers (aacparse, frame)) {
+      GST_ERROR_OBJECT (aacparse, "Failed to prepend ADTS headers to frame");
+      ret = GST_FLOW_ERROR;
     }
-    gst_caps_unref (sinkcaps);
+  }
+
+exit:
+  gst_buffer_unmap (buffer, &map);
 
-    gst_base_parse_set_frame_rate (GST_BASE_PARSE (aacparse),
-        aacparse->sample_rate, aacparse->frame_samples, 2, 2);
+  if (ret) {
+    /* found, skip if needed */
+    if (*skipsize > 0)
+      return GST_FLOW_OK;
+    *skipsize = 0;
+  } else {
+    if (*skipsize < 0)
+      *skipsize = 1;
   }
 
-  return ret;
+  if (ret && framesize <= map.size) {
+    return gst_base_parse_finish_frame (parse, frame, framesize);
+  }
+
+  return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_aac_parse_pre_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame)
+{
+  GstAacParse *aacparse = GST_AAC_PARSE (parse);
+
+  if (!aacparse->sent_codec_tag) {
+    GstTagList *taglist;
+    GstCaps *caps;
+
+    taglist = gst_tag_list_new_empty ();
+
+    /* codec tag */
+    caps = gst_pad_get_current_caps (GST_BASE_PARSE_SRC_PAD (parse));
+    gst_pb_utils_add_codec_description_to_tag_list (taglist,
+        GST_TAG_AUDIO_CODEC, caps);
+    gst_caps_unref (caps);
+
+    gst_pad_push_event (GST_BASE_PARSE_SRC_PAD (aacparse),
+        gst_event_new_tag (taglist));
+
+    /* also signals the end of first-frame processing */
+    aacparse->sent_codec_tag = TRUE;
+  }
+
+  /* As a special case, we can remove the ADTS framing and output raw AAC. */
+  if (aacparse->header_type == DSPAAC_HEADER_ADTS
+      && aacparse->output_header_type == DSPAAC_HEADER_NONE) {
+    guint header_size;
+    GstMapInfo map;
+    gst_buffer_map (frame->buffer, &map, GST_MAP_READ);
+    header_size = (map.data[1] & 1) ? 7 : 9;    /* optional CRC */
+    gst_buffer_unmap (frame->buffer, &map);
+    gst_buffer_resize (frame->buffer, header_size,
+        gst_buffer_get_size (frame->buffer) - header_size);
+  }
+
+  return GST_FLOW_OK;
 }
 
 
@@ -700,7 +1371,7 @@ gst_aac_parse_parse_frame (GstBaseParse * parse, GstBaseParseFrame * frame)
  *
  * Returns: TRUE if startup succeeded.
  */
-gboolean
+static gboolean
 gst_aac_parse_start (GstBaseParse * parse)
 {
   GstAacParse *aacparse;
@@ -709,6 +1380,7 @@ gst_aac_parse_start (GstBaseParse * parse)
   GST_DEBUG ("start");
   aacparse->frame_samples = 1024;
   gst_base_parse_set_min_frame_size (GST_BASE_PARSE (aacparse), ADTS_MAX_SIZE);
+  aacparse->sent_codec_tag = FALSE;
   return TRUE;
 }
 
@@ -721,9 +1393,138 @@ gst_aac_parse_start (GstBaseParse * parse)
  *
  * Returns: TRUE is stopping succeeded.
  */
-gboolean
+static gboolean
 gst_aac_parse_stop (GstBaseParse * parse)
 {
   GST_DEBUG ("stop");
   return TRUE;
 }
+
+static void
+remove_fields (GstCaps * caps)
+{
+  guint i, n;
+
+  n = gst_caps_get_size (caps);
+  for (i = 0; i < n; i++) {
+    GstStructure *s = gst_caps_get_structure (caps, i);
+
+    gst_structure_remove_field (s, "framed");
+  }
+}
+
+static void
+add_conversion_fields (GstCaps * caps)
+{
+  guint i, n;
+
+  n = gst_caps_get_size (caps);
+  for (i = 0; i < n; i++) {
+    GstStructure *s = gst_caps_get_structure (caps, i);
+
+    if (gst_structure_has_field (s, "stream-format")) {
+      const GValue *v = gst_structure_get_value (s, "stream-format");
+
+      if (G_VALUE_HOLDS_STRING (v)) {
+        const gchar *str = g_value_get_string (v);
+
+        if (strcmp (str, "adts") == 0 || strcmp (str, "raw") == 0) {
+          GValue va = G_VALUE_INIT;
+          GValue vs = G_VALUE_INIT;
+
+          g_value_init (&va, GST_TYPE_LIST);
+          g_value_init (&vs, G_TYPE_STRING);
+          g_value_set_string (&vs, "adts");
+          gst_value_list_append_value (&va, &vs);
+          g_value_set_string (&vs, "raw");
+          gst_value_list_append_value (&va, &vs);
+          gst_structure_set_value (s, "stream-format", &va);
+          g_value_unset (&va);
+          g_value_unset (&vs);
+        }
+      } else if (GST_VALUE_HOLDS_LIST (v)) {
+        gboolean contains_raw = FALSE;
+        gboolean contains_adts = FALSE;
+        guint m = gst_value_list_get_size (v), j;
+
+        for (j = 0; j < m; j++) {
+          const GValue *ve = gst_value_list_get_value (v, j);
+          const gchar *str;
+
+          if (G_VALUE_HOLDS_STRING (ve) && (str = g_value_get_string (ve))) {
+            if (strcmp (str, "adts") == 0)
+              contains_adts = TRUE;
+            else if (strcmp (str, "raw") == 0)
+              contains_raw = TRUE;
+          }
+        }
+
+        if (contains_adts || contains_raw) {
+          GValue va = G_VALUE_INIT;
+          GValue vs = G_VALUE_INIT;
+
+          g_value_init (&va, GST_TYPE_LIST);
+          g_value_init (&vs, G_TYPE_STRING);
+          g_value_copy (v, &va);
+
+          if (!contains_raw) {
+            g_value_set_string (&vs, "raw");
+            gst_value_list_append_value (&va, &vs);
+          }
+          if (!contains_adts) {
+            g_value_set_string (&vs, "adts");
+            gst_value_list_append_value (&va, &vs);
+          }
+
+          gst_structure_set_value (s, "stream-format", &va);
+
+          g_value_unset (&vs);
+          g_value_unset (&va);
+        }
+      }
+    }
+  }
+}
+
+static GstCaps *
+gst_aac_parse_sink_getcaps (GstBaseParse * parse, GstCaps * filter)
+{
+  GstCaps *peercaps, *templ;
+  GstCaps *res;
+
+  templ = gst_pad_get_pad_template_caps (GST_BASE_PARSE_SINK_PAD (parse));
+
+  if (filter) {
+    GstCaps *fcopy = gst_caps_copy (filter);
+    /* Remove the fields we convert */
+    remove_fields (fcopy);
+    add_conversion_fields (fcopy);
+    peercaps = gst_pad_peer_query_caps (GST_BASE_PARSE_SRC_PAD (parse), fcopy);
+    gst_caps_unref (fcopy);
+  } else
+    peercaps = gst_pad_peer_query_caps (GST_BASE_PARSE_SRC_PAD (parse), NULL);
+
+  if (peercaps) {
+    peercaps = gst_caps_make_writable (peercaps);
+    /* Remove the fields we convert */
+    remove_fields (peercaps);
+    add_conversion_fields (peercaps);
+
+    res = gst_caps_intersect_full (peercaps, templ, GST_CAPS_INTERSECT_FIRST);
+    gst_caps_unref (peercaps);
+    gst_caps_unref (templ);
+  } else {
+    res = templ;
+  }
+
+  if (filter) {
+    GstCaps *intersection;
+
+    intersection =
+        gst_caps_intersect_full (filter, res, GST_CAPS_INTERSECT_FIRST);
+    gst_caps_unref (res);
+    res = intersection;
+  }
+
+  return res;
+}