| /* |
| * Copyright (C) 2016 Sebastian Dröge <sebastian@centricular.com> |
| * |
| * 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., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "gstfdkaacdec.h" |
| |
| #include <gst/pbutils/pbutils.h> |
| |
| #include <string.h> |
| |
| /* TODO: |
| * - LOAS / LATM support |
| * - Error concealment |
| */ |
| |
| static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("audio/mpeg, " |
| "mpegversion = (int) 4, " |
| "stream-format = (string) { adts, adif, raw }") |
| ); |
| |
| static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("audio/x-raw, " |
| "format = (string) " GST_AUDIO_NE (S16) ", " |
| "layout = (string) interleaved, " |
| "rate = (int) [8000, 96000], " "channels = (int) [1, 8]") |
| ); |
| |
| GST_DEBUG_CATEGORY_STATIC (gst_fdkaacdec_debug); |
| #define GST_CAT_DEFAULT gst_fdkaacdec_debug |
| |
| static gboolean gst_fdkaacdec_start (GstAudioDecoder * dec); |
| static gboolean gst_fdkaacdec_stop (GstAudioDecoder * dec); |
| static gboolean gst_fdkaacdec_set_format (GstAudioDecoder * dec, |
| GstCaps * caps); |
| static GstFlowReturn gst_fdkaacdec_handle_frame (GstAudioDecoder * dec, |
| GstBuffer * in_buf); |
| static void gst_fdkaacdec_flush (GstAudioDecoder * dec, gboolean hard); |
| |
| G_DEFINE_TYPE (GstFdkAacDec, gst_fdkaacdec, GST_TYPE_AUDIO_DECODER); |
| |
| static gboolean |
| gst_fdkaacdec_start (GstAudioDecoder * dec) |
| { |
| GstFdkAacDec *self = GST_FDKAACDEC (dec); |
| |
| GST_DEBUG_OBJECT (self, "start"); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_fdkaacdec_stop (GstAudioDecoder * dec) |
| { |
| GstFdkAacDec *self = GST_FDKAACDEC (dec); |
| |
| GST_DEBUG_OBJECT (self, "stop"); |
| |
| g_free (self->decode_buffer); |
| self->decode_buffer = NULL; |
| |
| if (self->dec) |
| aacDecoder_Close (self->dec); |
| self->dec = NULL; |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_fdkaacdec_set_format (GstAudioDecoder * dec, GstCaps * caps) |
| { |
| GstFdkAacDec *self = GST_FDKAACDEC (dec); |
| TRANSPORT_TYPE transport_format; |
| GstStructure *s; |
| const gchar *stream_format; |
| AAC_DECODER_ERROR err; |
| |
| if (self->dec) { |
| /* drain */ |
| gst_fdkaacdec_handle_frame (dec, NULL); |
| aacDecoder_Close (self->dec); |
| self->dec = NULL; |
| } |
| |
| s = gst_caps_get_structure (caps, 0); |
| stream_format = gst_structure_get_string (s, "stream-format"); |
| if (strcmp (stream_format, "raw") == 0) { |
| transport_format = TT_MP4_RAW; |
| } else if (strcmp (stream_format, "adif") == 0) { |
| transport_format = TT_MP4_ADIF; |
| } else if (strcmp (stream_format, "adts") == 0) { |
| transport_format = TT_MP4_ADTS; |
| } else { |
| g_assert_not_reached (); |
| } |
| |
| self->dec = aacDecoder_Open (transport_format, 1); |
| if (!self->dec) { |
| GST_ERROR_OBJECT (self, "Failed to open decoder"); |
| return FALSE; |
| } |
| |
| if (transport_format == TT_MP4_RAW) { |
| GstBuffer *codec_data = NULL; |
| GstMapInfo map; |
| guint8 *data; |
| guint size; |
| |
| gst_structure_get (s, "codec_data", GST_TYPE_BUFFER, &codec_data, NULL); |
| |
| if (!codec_data) { |
| GST_ERROR_OBJECT (self, "Raw AAC without codec_data not supported"); |
| return FALSE; |
| } |
| |
| gst_buffer_map (codec_data, &map, GST_MAP_READ); |
| data = map.data; |
| size = map.size; |
| |
| if ((err = aacDecoder_ConfigRaw (self->dec, &data, &size)) != AAC_DEC_OK) { |
| gst_buffer_unmap (codec_data, &map); |
| gst_buffer_unref (codec_data); |
| GST_ERROR_OBJECT (self, "Invalid codec_data: %d", err); |
| return FALSE; |
| } |
| |
| gst_buffer_unmap (codec_data, &map); |
| gst_buffer_unref (codec_data); |
| } |
| |
| if ((err = |
| aacDecoder_SetParam (self->dec, AAC_PCM_OUTPUT_CHANNEL_MAPPING, |
| 0)) != AAC_DEC_OK) { |
| GST_ERROR_OBJECT (self, "Failed to set output channel mapping: %d", err); |
| return FALSE; |
| } |
| |
| if ((err = |
| aacDecoder_SetParam (self->dec, AAC_PCM_OUTPUT_INTERLEAVED, |
| 1)) != AAC_DEC_OK) { |
| GST_ERROR_OBJECT (self, "Failed to set interleaved output: %d", err); |
| return FALSE; |
| } |
| |
| /* 8 channels * 2 bytes per sample * 2048 samples */ |
| if (!self->decode_buffer) { |
| self->decode_buffer_size = 8 * 2048; |
| self->decode_buffer = g_new (gint16, self->decode_buffer_size); |
| } |
| |
| return TRUE; |
| } |
| |
| static GstFlowReturn |
| gst_fdkaacdec_handle_frame (GstAudioDecoder * dec, GstBuffer * inbuf) |
| { |
| GstFdkAacDec *self = GST_FDKAACDEC (dec); |
| GstFlowReturn ret = GST_FLOW_OK; |
| GstBuffer *outbuf; |
| GstMapInfo imap; |
| AAC_DECODER_ERROR err; |
| guint size, valid; |
| CStreamInfo *stream_info; |
| GstAudioInfo info; |
| guint flags = 0, i; |
| GstAudioChannelPosition pos[64], gst_pos[64]; |
| gboolean need_reorder; |
| |
| if (inbuf) { |
| gst_buffer_ref (inbuf); |
| gst_buffer_map (inbuf, &imap, GST_MAP_READ); |
| valid = size = imap.size; |
| |
| if ((err = |
| aacDecoder_Fill (self->dec, (guint8 **) & imap.data, &size, |
| &valid)) != AAC_DEC_OK) { |
| GST_AUDIO_DECODER_ERROR (self, 1, STREAM, DECODE, (NULL), |
| ("filling error: %d", err), ret); |
| goto out; |
| } |
| |
| if (GST_BUFFER_IS_DISCONT (inbuf)) |
| flags |= AACDEC_INTR; |
| } else { |
| flags |= AACDEC_FLUSH; |
| } |
| |
| if ((err = |
| aacDecoder_DecodeFrame (self->dec, self->decode_buffer, |
| self->decode_buffer_size, flags)) != AAC_DEC_OK) { |
| if (err == AAC_DEC_TRANSPORT_SYNC_ERROR) { |
| ret = GST_FLOW_OK; |
| outbuf = NULL; |
| goto finish; |
| } |
| GST_AUDIO_DECODER_ERROR (self, 1, STREAM, DECODE, (NULL), |
| ("decoding error: %d", err), ret); |
| goto out; |
| } |
| |
| stream_info = aacDecoder_GetStreamInfo (self->dec); |
| if (!stream_info) { |
| GST_AUDIO_DECODER_ERROR (self, 1, STREAM, DECODE, (NULL), |
| ("failed to get stream info"), ret); |
| goto out; |
| } |
| |
| /* FIXME: Don't recalculate this on every buffer */ |
| if (stream_info->numChannels == 1) { |
| pos[0] = GST_AUDIO_CHANNEL_POSITION_MONO; |
| } else { |
| gint n_front = 0, n_side = 0, n_back = 0, n_lfe = 0; |
| |
| /* FIXME: Can this be simplified somehow? */ |
| for (i = 0; i < stream_info->numChannels; i++) { |
| if (stream_info->pChannelType[i] == ACT_FRONT) { |
| n_front++; |
| } else if (stream_info->pChannelType[i] == ACT_SIDE) { |
| n_side++; |
| } else if (stream_info->pChannelType[i] == ACT_BACK) { |
| n_back++; |
| } else if (stream_info->pChannelType[i] == ACT_LFE) { |
| n_lfe++; |
| } else { |
| GST_ERROR_OBJECT (self, "Channel type %d not supported", |
| stream_info->pChannelType[i]); |
| ret = GST_FLOW_NOT_NEGOTIATED; |
| goto out; |
| } |
| } |
| |
| for (i = 0; i < stream_info->numChannels; i++) { |
| if (stream_info->pChannelType[i] == ACT_FRONT) { |
| if (stream_info->pChannelIndices[i] == 0) { |
| if (n_front & 1) |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER; |
| else if (n_front > 2) |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; |
| else |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT; |
| } else if (stream_info->pChannelIndices[i] == 1) { |
| if ((n_front & 1) && n_front > 3) |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; |
| else if (n_front & 1) |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT; |
| else if (n_front > 2) |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; |
| else |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT; |
| } else if (stream_info->pChannelIndices[i] == 2) { |
| if ((n_front & 1) && n_front > 3) |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; |
| else if (n_front & 1) |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT; |
| else if (n_front > 2) |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT; |
| else |
| g_assert_not_reached (); |
| } else if (stream_info->pChannelIndices[i] == 3) { |
| if ((n_front & 1) && n_front > 3) |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT; |
| else if (n_front & 1) |
| g_assert_not_reached (); |
| else if (n_front > 2) |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT; |
| else |
| g_assert_not_reached (); |
| } else if (stream_info->pChannelIndices[i] == 4) { |
| if ((n_front & 1) && n_front > 2) |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT; |
| else if (n_front & 1) |
| g_assert_not_reached (); |
| else if (n_front > 2) |
| g_assert_not_reached (); |
| else |
| g_assert_not_reached (); |
| } else { |
| GST_ERROR_OBJECT (self, "Front channel index %d not supported", |
| stream_info->pChannelIndices[i]); |
| ret = GST_FLOW_NOT_NEGOTIATED; |
| goto out; |
| } |
| } else if (stream_info->pChannelType[i] == ACT_SIDE) { |
| if (n_side & 1) { |
| GST_ERROR_OBJECT (self, "Odd number of side channels not supported"); |
| ret = GST_FLOW_NOT_NEGOTIATED; |
| goto out; |
| } else if (stream_info->pChannelIndices[i] == 0) { |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT; |
| } else if (stream_info->pChannelIndices[i] == 1) { |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT; |
| } else { |
| GST_ERROR_OBJECT (self, "Side channel index %d not supported", |
| stream_info->pChannelIndices[i]); |
| ret = GST_FLOW_NOT_NEGOTIATED; |
| goto out; |
| } |
| } else if (stream_info->pChannelType[i] == ACT_BACK) { |
| if (stream_info->pChannelIndices[i] == 0) { |
| if (n_back & 1) |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_REAR_CENTER; |
| else |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_REAR_LEFT; |
| } else if (stream_info->pChannelIndices[i] == 1) { |
| if (n_back & 1) |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_REAR_LEFT; |
| else |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT; |
| } else if (stream_info->pChannelIndices[i] == 2) { |
| if (n_back & 1) |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT; |
| else |
| g_assert_not_reached (); |
| } else { |
| GST_ERROR_OBJECT (self, "Side channel index %d not supported", |
| stream_info->pChannelIndices[i]); |
| ret = GST_FLOW_NOT_NEGOTIATED; |
| goto out; |
| } |
| } else if (stream_info->pChannelType[i] == ACT_LFE) { |
| if (stream_info->pChannelIndices[i] == 0) { |
| pos[i] = GST_AUDIO_CHANNEL_POSITION_LFE1; |
| } else { |
| GST_ERROR_OBJECT (self, "LFE channel index %d not supported", |
| stream_info->pChannelIndices[i]); |
| ret = GST_FLOW_NOT_NEGOTIATED; |
| goto out; |
| } |
| } else { |
| GST_ERROR_OBJECT (self, "Channel type %d not supported", |
| stream_info->pChannelType[i]); |
| ret = GST_FLOW_NOT_NEGOTIATED; |
| goto out; |
| } |
| } |
| } |
| |
| memcpy (gst_pos, pos, |
| sizeof (GstAudioChannelPosition) * stream_info->numChannels); |
| if (!gst_audio_channel_positions_to_valid_order (gst_pos, |
| stream_info->numChannels)) { |
| ret = GST_FLOW_NOT_NEGOTIATED; |
| goto out; |
| } |
| |
| need_reorder = |
| memcmp (pos, gst_pos, |
| sizeof (GstAudioChannelPosition) * stream_info->numChannels) != 0; |
| |
| gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S16, |
| stream_info->sampleRate, stream_info->numChannels, gst_pos); |
| if (!gst_audio_decoder_set_output_format (dec, &info)) { |
| GST_ERROR_OBJECT (self, "Failed to set output format"); |
| ret = GST_FLOW_NOT_NEGOTIATED; |
| goto out; |
| } |
| |
| outbuf = |
| gst_audio_decoder_allocate_output_buffer (dec, |
| stream_info->frameSize * GST_AUDIO_INFO_BPF (&info)); |
| gst_buffer_fill (outbuf, 0, self->decode_buffer, |
| gst_buffer_get_size (outbuf)); |
| |
| if (need_reorder) { |
| gst_audio_buffer_reorder_channels (outbuf, GST_AUDIO_INFO_FORMAT (&info), |
| GST_AUDIO_INFO_CHANNELS (&info), pos, gst_pos); |
| } |
| |
| finish: |
| ret = gst_audio_decoder_finish_frame (dec, outbuf, 1); |
| |
| out: |
| |
| if (inbuf) { |
| gst_buffer_unmap (inbuf, &imap); |
| gst_buffer_unref (inbuf); |
| } |
| |
| return ret; |
| } |
| |
| static void |
| gst_fdkaacdec_flush (GstAudioDecoder * dec, gboolean hard) |
| { |
| GstFdkAacDec *self = GST_FDKAACDEC (dec); |
| |
| if (self->dec) { |
| AAC_DECODER_ERROR err; |
| if ((err = |
| aacDecoder_DecodeFrame (self->dec, self->decode_buffer, |
| self->decode_buffer_size, AACDEC_FLUSH)) != AAC_DEC_OK) { |
| GST_ERROR_OBJECT (self, "flushing error: %d", err); |
| } |
| } |
| } |
| |
| static void |
| gst_fdkaacdec_init (GstFdkAacDec * self) |
| { |
| self->dec = NULL; |
| |
| gst_audio_decoder_set_drainable (GST_AUDIO_DECODER (self), TRUE); |
| gst_audio_decoder_set_needs_format (GST_AUDIO_DECODER (self), TRUE); |
| } |
| |
| static void |
| gst_fdkaacdec_class_init (GstFdkAacDecClass * klass) |
| { |
| GstElementClass *element_class = GST_ELEMENT_CLASS (klass); |
| GstAudioDecoderClass *base_class = GST_AUDIO_DECODER_CLASS (klass); |
| |
| base_class->start = GST_DEBUG_FUNCPTR (gst_fdkaacdec_start); |
| base_class->stop = GST_DEBUG_FUNCPTR (gst_fdkaacdec_stop); |
| base_class->set_format = GST_DEBUG_FUNCPTR (gst_fdkaacdec_set_format); |
| base_class->handle_frame = GST_DEBUG_FUNCPTR (gst_fdkaacdec_handle_frame); |
| base_class->flush = GST_DEBUG_FUNCPTR (gst_fdkaacdec_flush); |
| |
| gst_element_class_add_static_pad_template (element_class, &sink_template); |
| gst_element_class_add_static_pad_template (element_class, &src_template); |
| |
| gst_element_class_set_static_metadata (element_class, "FDK AAC audio decoder", |
| "Codec/Decoder/Audio", "FDK AAC audio decoder", |
| "Sebastian Dröge <sebastian@centricular.com>"); |
| |
| GST_DEBUG_CATEGORY_INIT (gst_fdkaacdec_debug, "fdkaacdec", 0, |
| "fdkaac decoder"); |
| } |