| /* GStreamer | |
| * Copyright (C) 2022 Seungha Yang <seungha@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. | |
| */ | |
| /** | |
| * SECTION:element-mfmp3dec | |
| * @title: mfmp3dec | |
| * | |
| * This element decodes MP3 compressed data into RAW audio data. | |
| * | |
| * Since: 1.22 | |
| * | |
| */ | |
| #ifdef HAVE_CONFIG_H | |
| #include "config.h" | |
| #endif | |
| #include <gst/gst.h> | |
| #include <gst/pbutils/pbutils.h> | |
| #include "gstmfaudiodecoder.h" | |
| #include "gstmfmp3dec.h" | |
| #include <wrl.h> | |
| #include <string.h> | |
| /* *INDENT-OFF* */ | |
| using namespace Microsoft::WRL; | |
| /* *INDENT-ON* */ | |
| GST_DEBUG_CATEGORY (gst_mf_mp3_dec_debug); | |
| #define GST_CAT_DEFAULT gst_mf_mp3_dec_debug | |
| static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", | |
| GST_PAD_SINK, | |
| GST_PAD_ALWAYS, | |
| GST_STATIC_CAPS ("audio/mpeg, " | |
| "mpegversion = (int) 1, layer = (int) 3, channels = (int) [1, 2], " | |
| "rate = (int) [8000, 48000]") | |
| ); | |
| 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, " | |
| "channels = (int) [1, 2], rate = (int) [8000, 48000]") | |
| ); | |
| typedef struct _GstMFMp3Dec | |
| { | |
| GstMFAudioDecoder parent; | |
| } GstMFMp3Dec; | |
| typedef struct _GstMFMp3DecClass | |
| { | |
| GstMFAudioDecoderClass parent_class; | |
| } GstMFMp3DecClass; | |
| static GTypeClass *parent_class = nullptr; | |
| static gboolean gst_mf_mp3_dec_set_format (GstMFAudioDecoder * decoder, | |
| GstMFTransform * transform, GstCaps * caps); | |
| static void | |
| gst_mf_mp3_dec_class_init (GstMFMp3DecClass * klass, gpointer data) | |
| { | |
| GstElementClass *element_class = GST_ELEMENT_CLASS (klass); | |
| GstMFAudioDecoderClass *decoder_class = GST_MF_AUDIO_DECODER_CLASS (klass); | |
| GstMFAudioDecoderClassData *cdata = (GstMFAudioDecoderClassData *) data; | |
| gchar *long_name; | |
| parent_class = (GTypeClass *) g_type_class_peek_parent (klass); | |
| long_name = g_strdup_printf ("Media Foundation %s", cdata->device_name); | |
| gst_element_class_set_metadata (element_class, long_name, | |
| "Codec/Decoder/Audio", | |
| "Microsoft Media Foundation MP3 Decoder", | |
| "Seungha Yang <seungha@centricular.com>"); | |
| g_free (long_name); | |
| gst_element_class_add_static_pad_template (element_class, &sink_template); | |
| gst_element_class_add_static_pad_template (element_class, &src_template); | |
| decoder_class->set_format = GST_DEBUG_FUNCPTR (gst_mf_mp3_dec_set_format); | |
| decoder_class->codec_id = MFAudioFormat_MP3; | |
| decoder_class->enum_flags = cdata->enum_flags; | |
| decoder_class->device_index = cdata->device_index; | |
| g_free (cdata->device_name); | |
| g_free (cdata); | |
| } | |
| static void | |
| gst_mf_mp3_dec_init (GstMFMp3Dec * self) | |
| { | |
| } | |
| static gboolean | |
| gst_mf_mp3_dec_set_format (GstMFAudioDecoder * decoder, | |
| GstMFTransform * transform, GstCaps * caps) | |
| { | |
| GstMFMp3Dec *self = (GstMFMp3Dec *) decoder; | |
| HRESULT hr; | |
| ComPtr < IMFMediaType > in_type; | |
| ComPtr < IMFMediaType > out_type; | |
| GstAudioInfo in_audio_info, out_audio_info; | |
| GList *output_list, *iter; | |
| GstCaps *out_caps; | |
| if (!gst_audio_info_from_caps (&in_audio_info, caps)) { | |
| GST_ERROR_OBJECT (self, "Failed to get audio info from caps"); | |
| return FALSE; | |
| } | |
| hr = MFCreateMediaType (&in_type); | |
| if (!gst_mf_result (hr)) | |
| return FALSE; | |
| hr = in_type->SetGUID (MF_MT_MAJOR_TYPE, MFMediaType_Audio); | |
| if (!gst_mf_result (hr)) | |
| return FALSE; | |
| hr = in_type->SetGUID (MF_MT_SUBTYPE, MFAudioFormat_MP3); | |
| if (!gst_mf_result (hr)) | |
| return FALSE; | |
| hr = in_type->SetUINT32 (MF_MT_AUDIO_NUM_CHANNELS, in_audio_info.channels); | |
| if (!gst_mf_result (hr)) | |
| return FALSE; | |
| hr = in_type->SetUINT32 (MF_MT_AUDIO_SAMPLES_PER_SECOND, in_audio_info.rate); | |
| if (!gst_mf_result (hr)) | |
| return FALSE; | |
| if (!gst_mf_transform_set_input_type (transform, in_type.Get ())) { | |
| GST_ERROR_OBJECT (self, "Failed to set format"); | |
| return FALSE; | |
| } | |
| if (!gst_mf_transform_get_output_available_types (transform, &output_list)) { | |
| GST_ERROR_OBJECT (self, "Failed to get output types"); | |
| return FALSE; | |
| } | |
| for (iter = output_list; iter; iter = g_list_next (iter)) { | |
| GUID guid; | |
| IMFMediaType *type = (IMFMediaType *) iter->data; | |
| UINT32 bps; | |
| hr = type->GetGUID (MF_MT_MAJOR_TYPE, &guid); | |
| if (!gst_mf_result (hr)) | |
| continue; | |
| if (!IsEqualGUID (guid, MFMediaType_Audio)) | |
| continue; | |
| hr = type->GetGUID (MF_MT_SUBTYPE, &guid); | |
| if (!gst_mf_result (hr)) | |
| continue; | |
| if (!IsEqualGUID (guid, MFAudioFormat_PCM)) | |
| continue; | |
| hr = type->GetUINT32 (MF_MT_AUDIO_BITS_PER_SAMPLE, &bps); | |
| if (!gst_mf_result (hr)) | |
| continue; | |
| if (bps != 16) | |
| continue; | |
| out_type = type; | |
| break; | |
| } | |
| g_list_free_full (output_list, (GDestroyNotify) gst_mf_media_type_release); | |
| if (!out_type) { | |
| GST_ERROR_OBJECT (self, "Failed to select output type"); | |
| return FALSE; | |
| } | |
| if (!gst_mf_transform_set_output_type (transform, out_type.Get ())) { | |
| GST_ERROR_OBJECT (self, "Failed to select output type"); | |
| return FALSE; | |
| } | |
| out_caps = gst_mf_media_type_to_caps (out_type.Get ()); | |
| if (!out_caps) { | |
| GST_ERROR_OBJECT (self, "Failed to get output caps"); | |
| return FALSE; | |
| } | |
| GST_DEBUG_OBJECT (self, "Output caps %" GST_PTR_FORMAT, out_caps); | |
| if (!gst_audio_info_from_caps (&out_audio_info, out_caps)) { | |
| GST_ERROR_OBJECT (self, | |
| "Failed to convert caps to audio info %" GST_PTR_FORMAT, out_caps); | |
| gst_caps_unref (out_caps); | |
| } | |
| gst_caps_unref (out_caps); | |
| return gst_audio_decoder_set_output_format (GST_AUDIO_DECODER (self), | |
| &out_audio_info); | |
| } | |
| static void | |
| gst_mf_mp3_dec_register (GstPlugin * plugin, guint rank, | |
| const gchar * device_name, guint32 enum_flags, guint device_index) | |
| { | |
| GType type; | |
| GstMFAudioDecoderClassData *cdata; | |
| GTypeInfo type_info = { | |
| sizeof (GstMFMp3DecClass), | |
| nullptr, | |
| nullptr, | |
| (GClassInitFunc) gst_mf_mp3_dec_class_init, | |
| nullptr, | |
| nullptr, | |
| sizeof (GstMFMp3Dec), | |
| 0, | |
| (GInstanceInitFunc) gst_mf_mp3_dec_init, | |
| }; | |
| cdata = g_new0 (GstMFAudioDecoderClassData, 1); | |
| cdata->device_name = g_strdup (device_name); | |
| cdata->enum_flags = enum_flags; | |
| cdata->device_index = device_index; | |
| type_info.class_data = cdata; | |
| type = g_type_register_static (GST_TYPE_MF_AUDIO_DECODER, "GstMFMp3Dec", | |
| &type_info, (GTypeFlags) 0); | |
| if (!gst_element_register (plugin, "mfmp3dec", rank, type)) | |
| GST_WARNING ("Failed to register plugin"); | |
| } | |
| static gboolean | |
| gst_mf_mp3_dec_plugin_init_internal (GstPlugin * plugin, guint rank, | |
| GstMFTransform * transform, guint device_index, guint32 enum_flags) | |
| { | |
| gchar *device_name = nullptr; | |
| if (!gst_mf_transform_open (transform)) | |
| return FALSE; | |
| g_object_get (transform, "device-name", &device_name, nullptr); | |
| if (!device_name) { | |
| GST_WARNING_OBJECT (transform, "Unknown device name"); | |
| return FALSE; | |
| } | |
| gst_mf_mp3_dec_register (plugin, rank, device_name, enum_flags, device_index); | |
| g_free (device_name); | |
| return TRUE; | |
| } | |
| void | |
| gst_mf_mp3_dec_plugin_init (GstPlugin * plugin, guint rank) | |
| { | |
| GstMFTransformEnumParams enum_params = { 0, }; | |
| MFT_REGISTER_TYPE_INFO input_type; | |
| GstMFTransform *transform; | |
| gint i; | |
| gboolean do_next; | |
| GST_DEBUG_CATEGORY_INIT (gst_mf_mp3_dec_debug, "mfmp3dec", 0, "mfmp3dec"); | |
| input_type.guidMajorType = MFMediaType_Audio; | |
| input_type.guidSubtype = MFAudioFormat_MP3; | |
| enum_params.category = MFT_CATEGORY_AUDIO_DECODER; | |
| enum_params.enum_flags = (MFT_ENUM_FLAG_SYNCMFT | | |
| MFT_ENUM_FLAG_SORTANDFILTER | MFT_ENUM_FLAG_SORTANDFILTER_APPROVED_ONLY); | |
| enum_params.input_typeinfo = &input_type; | |
| i = 0; | |
| do { | |
| enum_params.device_index = i++; | |
| transform = gst_mf_transform_new (&enum_params); | |
| do_next = TRUE; | |
| if (!transform) { | |
| do_next = FALSE; | |
| } else { | |
| if (gst_mf_mp3_dec_plugin_init_internal (plugin, rank, transform, | |
| enum_params.device_index, enum_params.enum_flags)) { | |
| do_next = FALSE; | |
| } | |
| gst_clear_object (&transform); | |
| } | |
| } while (do_next); | |
| } |