| /* GStreamer base utils library codec-specific utility functions |
| * Copyright (C) 2010 Arun Raghavan <arun.raghavan@collabora.co.uk> |
| * 2013 Sreerenj Balachandran <sreerenj.balachandran@intel.com> |
| * 2010 Collabora Multimedia |
| * 2010 Nokia Corporation |
| * 2013 Intel Corporation |
| * 2015 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. |
| */ |
| |
| /** |
| * SECTION:gstpbutilscodecutils |
| * @title: Codec utilities |
| * @short_description: Miscellaneous codec-specific utility functions |
| * |
| * Provides codec-specific ulility functions such as functions to provide the |
| * codec profile and level in human-readable string form from header data. |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "pbutils.h" |
| #include <gst/base/base.h> |
| #include <gst/tag/tag.h> |
| |
| #include <string.h> |
| |
| #define GST_SIMPLE_CAPS_HAS_NAME(caps,name) \ |
| gst_structure_has_name(gst_caps_get_structure((caps),0),(name)) |
| |
| #define GST_SIMPLE_CAPS_HAS_FIELD(caps,field) \ |
| gst_structure_has_field(gst_caps_get_structure((caps),0),(field)) |
| |
| static const guint aac_sample_rates[] = { 96000, 88200, 64000, 48000, 44100, |
| 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 |
| }; |
| |
| static const gchar * |
| digit_to_string (guint digit) |
| { |
| static const char itoa[][2] = { |
| "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" |
| }; |
| |
| if (G_LIKELY (digit < 10)) |
| return itoa[digit]; |
| else |
| return NULL; |
| } |
| |
| /** |
| * gst_codec_utils_aac_get_sample_rate_from_index: |
| * @sr_idx: Sample rate index as from the AudioSpecificConfig (MPEG-4 |
| * container) or ADTS frame header |
| * |
| * Translates the sample rate index found in AAC headers to the actual sample |
| * rate. |
| * |
| * Returns: The sample rate if @sr_idx is valid, 0 otherwise. |
| */ |
| guint |
| gst_codec_utils_aac_get_sample_rate_from_index (guint sr_idx) |
| { |
| if (G_LIKELY (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_codec_utils_aac_get_index_from_sample_rate: |
| * @rate: Sample rate |
| * |
| * Translates the sample rate to the index corresponding to it in AAC spec. |
| * |
| * Returns: The AAC index for this sample rate, -1 if the rate is not a |
| * valid AAC sample rate. |
| */ |
| gint |
| gst_codec_utils_aac_get_index_from_sample_rate (guint rate) |
| { |
| guint n; |
| |
| for (n = 0; n < G_N_ELEMENTS (aac_sample_rates); n++) |
| if (aac_sample_rates[n] == rate) |
| return n; |
| |
| GST_WARNING ("Invalid sample rate %u", rate); |
| return -1; |
| } |
| |
| /** |
| * gst_codec_utils_aac_get_sample_rate: |
| * @audio_config: (array length=len): a pointer to the AudioSpecificConfig |
| * as specified in the Elementary Stream Descriptor (esds) |
| * in ISO/IEC 14496-1. |
| * @len: Length of @audio_config |
| * |
| * Translates the sample rate index found in AAC headers to the actual sample |
| * rate. |
| * |
| * Returns: The sample rate if sr_idx is valid, 0 otherwise. |
| * |
| * Since 1.10 |
| */ |
| guint |
| gst_codec_utils_aac_get_sample_rate (const guint8 * audio_config, guint len) |
| { |
| guint rate_index; |
| |
| 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_channels: |
| * @audio_config: (array length=len): a pointer to the AudioSpecificConfig |
| * as specified in the Elementary Stream Descriptor (esds) |
| * in ISO/IEC 14496-1. |
| * @len: Length of @audio_config in bytes |
| * |
| * Returns the channels of the given AAC stream. |
| * |
| * Returns: The channels or 0 if the channel could not be determined. |
| * |
| * Since 1.10 |
| */ |
| guint |
| gst_codec_utils_aac_get_channels (const guint8 * audio_config, guint len) |
| { |
| guint channels; |
| |
| if (len < 2) |
| return 0; |
| |
| channels = (audio_config[1] & 0x7f) >> 3; |
| if (channels > 0 && channels < 7) |
| return channels; |
| else if (channels == 7) |
| return 8; |
| else |
| return 0; |
| } |
| |
| /** |
| * gst_codec_utils_aac_get_profile: |
| * @audio_config: (array length=len): a pointer to the AudioSpecificConfig |
| * as specified in the Elementary Stream Descriptor (esds) |
| * in ISO/IEC 14496-1. |
| * @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. |
| * |
| * Returns: The profile as a const string and %NULL if the profile could not be |
| * determined. |
| */ |
| const gchar * |
| gst_codec_utils_aac_get_profile (const guint8 * audio_config, guint len) |
| { |
| guint profile; |
| |
| if (len < 1) |
| return NULL; |
| |
| GST_MEMDUMP ("audio config", audio_config, len); |
| |
| profile = audio_config[0] >> 3; |
| switch (profile) { |
| case 1: |
| return "main"; |
| case 2: |
| return "lc"; |
| case 3: |
| return "ssr"; |
| case 4: |
| return "ltp"; |
| default: |
| break; |
| } |
| |
| GST_DEBUG ("Invalid profile idx: %u", profile); |
| return NULL; |
| } |
| |
| /** |
| * gst_codec_utils_aac_get_level: |
| * @audio_config: (array length=len): a pointer to the AudioSpecificConfig |
| * as specified in the Elementary Stream Descriptor (esds) |
| * in ISO/IEC 14496-1. |
| * @len: Length of @audio_config in bytes |
| * |
| * Determines the level of a stream as defined in ISO/IEC 14496-3. For AAC LC |
| * streams, the constraints from the AAC audio profile are applied. For AAC |
| * Main, LTP, SSR and others, the Main profile is used. |
| * |
| * 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 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; |
| /* 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; |
| int ret = -1; |
| |
| g_return_val_if_fail (audio_config != NULL, NULL); |
| |
| if (len < 2) |
| return NULL; |
| |
| 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) |
| return NULL; |
| |
| 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 NULL; |
| 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: |
| case 12: |
| case 14: |
| /* 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; |
| case 11: |
| num_sce = 2; |
| num_cpe = 2; |
| num_lfe = 1; |
| break; |
| default: |
| GST_WARNING ("Unknown channel config in header: %d", channel_config); |
| return NULL; |
| } |
| |
| switch (profile) { |
| case 0: /* NULL */ |
| GST_WARNING ("profile 0 is not a valid profile"); |
| return NULL; |
| 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) |
| ret = 1; |
| else if (num_channels <= 2 && rate <= 48000 && pcu <= 6 && rcu <= 5) |
| ret = 2; |
| /* There is no level 3 for the AAC Profile */ |
| else if (num_channels <= 5 && rate <= 48000 && pcu <= 19 && rcu <= 15) |
| ret = 4; |
| else if (num_channels <= 5 && rate <= 96000 && pcu <= 38 && rcu <= 15) |
| ret = 5; |
| else if (num_channels <= 7 && rate <= 48000 && pcu <= 25 && rcu <= 19) |
| ret = 6; |
| else if (num_channels <= 7 && rate <= 96000 && pcu <= 50 && rcu <= 19) |
| ret = 7; |
| } else { |
| /* Return the level as per the 'Main Profile' */ |
| if (pcu < 40 && rcu < 20) |
| ret = 1; |
| else if (pcu < 80 && rcu < 64) |
| ret = 2; |
| else if (pcu < 160 && rcu < 128) |
| ret = 3; |
| else if (pcu < 320 && rcu < 256) |
| ret = 4; |
| } |
| |
| 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); |
| return NULL; |
| } else { |
| return digit_to_string (ret); |
| } |
| } |
| |
| /** |
| * gst_codec_utils_aac_caps_set_level_and_profile: |
| * @caps: the #GstCaps to which level and profile fields are to be added |
| * @audio_config: (array length=len): a pointer to the AudioSpecificConfig |
| * as specified in the Elementary Stream Descriptor (esds) |
| * in ISO/IEC 14496-1. (See below for more details) |
| * @len: Length of @audio_config in bytes |
| * |
| * Sets the level and profile on @caps if it can be determined from |
| * @audio_config. See gst_codec_utils_aac_get_level() and |
| * gst_codec_utils_aac_get_profile() for more details on the parameters. |
| * @caps must be audio/mpeg caps with an "mpegversion" field of either 2 or 4. |
| * If mpegversion is 4, the "base-profile" field is also set in @caps. |
| * |
| * Returns: %TRUE if the level and profile could be set, %FALSE otherwise. |
| */ |
| gboolean |
| gst_codec_utils_aac_caps_set_level_and_profile (GstCaps * caps, |
| const guint8 * audio_config, guint len) |
| { |
| GstStructure *s; |
| const gchar *level, *profile; |
| int mpegversion = 0; |
| |
| g_return_val_if_fail (GST_IS_CAPS (caps), FALSE); |
| g_return_val_if_fail (GST_CAPS_IS_SIMPLE (caps), FALSE); |
| g_return_val_if_fail (GST_SIMPLE_CAPS_HAS_NAME (caps, "audio/mpeg"), FALSE); |
| g_return_val_if_fail (GST_SIMPLE_CAPS_HAS_FIELD (caps, "mpegversion"), FALSE); |
| g_return_val_if_fail (audio_config != NULL, FALSE); |
| |
| s = gst_caps_get_structure (caps, 0); |
| |
| gst_structure_get_int (s, "mpegversion", &mpegversion); |
| g_return_val_if_fail (mpegversion == 2 || mpegversion == 4, FALSE); |
| |
| level = gst_codec_utils_aac_get_level (audio_config, len); |
| |
| if (level != NULL) |
| gst_structure_set (s, "level", G_TYPE_STRING, level, NULL); |
| |
| profile = gst_codec_utils_aac_get_profile (audio_config, len); |
| |
| if (profile != NULL) { |
| if (mpegversion == 4) { |
| gst_structure_set (s, "base-profile", G_TYPE_STRING, profile, |
| "profile", G_TYPE_STRING, profile, NULL); |
| } else { |
| gst_structure_set (s, "profile", G_TYPE_STRING, profile, NULL); |
| } |
| } |
| |
| GST_LOG ("profile : %s", (profile) ? profile : "---"); |
| GST_LOG ("level : %s", (level) ? level : "---"); |
| |
| return (level != NULL && profile != NULL); |
| } |
| |
| /** |
| * gst_codec_utils_h264_get_profile: |
| * @sps: (array length=len): Pointer to the sequence parameter set for the stream. |
| * @len: Length of the data available in @sps. |
| * |
| * Converts the profile indication (profile_idc) in the stream's |
| * sequence parameter set into a string. The SPS is expected to have the |
| * following format, as defined in the H.264 specification. The SPS is viewed |
| * as a bitstream here, with bit 0 being the most significant bit of the first |
| * byte. |
| * |
| * * Bit 0:7 - Profile indication |
| * * Bit 8 - constraint_set0_flag |
| * * Bit 9 - constraint_set1_flag |
| * * Bit 10 - constraint_set2_flag |
| * * Bit 11 - constraint_set3_flag |
| * * Bit 12 - constraint_set3_flag |
| * * Bit 13:15 - Reserved |
| * * Bit 16:24 - Level indication |
| * |
| * Returns: The profile as a const string, or %NULL if there is an error. |
| */ |
| const gchar * |
| gst_codec_utils_h264_get_profile (const guint8 * sps, guint len) |
| { |
| const gchar *profile = NULL; |
| gint csf1, csf3, csf5; |
| |
| g_return_val_if_fail (sps != NULL, NULL); |
| |
| if (len < 2) |
| return NULL; |
| |
| GST_MEMDUMP ("SPS", sps, len); |
| |
| csf1 = (sps[1] & 0x40) >> 6; |
| csf3 = (sps[1] & 0x10) >> 4; |
| csf5 = (sps[1] & 0x04) >> 2; |
| |
| switch (sps[0]) { |
| case 66: |
| if (csf1) |
| profile = "constrained-baseline"; |
| else |
| profile = "baseline"; |
| break; |
| case 77: |
| profile = "main"; |
| break; |
| case 88: |
| profile = "extended"; |
| break; |
| case 100: |
| profile = "high"; |
| break; |
| case 110: |
| if (csf3) |
| profile = "high-10-intra"; |
| else |
| profile = "high-10"; |
| break; |
| case 122: |
| if (csf3) |
| profile = "high-4:2:2-intra"; |
| else |
| profile = "high-4:2:2"; |
| break; |
| case 244: |
| if (csf3) |
| profile = "high-4:4:4-intra"; |
| else |
| profile = "high-4:4:4"; |
| break; |
| case 44: |
| profile = "cavlc-4:4:4-intra"; |
| break; |
| case 118: |
| profile = "multiview-high"; |
| break; |
| case 128: |
| profile = "stereo-high"; |
| break; |
| case 83: |
| if (csf5) |
| profile = "scalable-constrained-baseline"; |
| else |
| profile = "scalable-baseline"; |
| break; |
| case 86: |
| if (csf3) |
| profile = "scalable-high-intra"; |
| else if (csf5) |
| profile = "scalable-constrained-high"; |
| else |
| profile = "scalable-high"; |
| break; |
| default: |
| return NULL; |
| } |
| |
| return profile; |
| } |
| |
| /** |
| * gst_codec_utils_h264_get_level: |
| * @sps: (array length=len): Pointer to the sequence parameter set for the stream. |
| * @len: Length of the data available in @sps. |
| * |
| * Converts the level indication (level_idc) in the stream's |
| * sequence parameter set into a string. The SPS is expected to have the |
| * same format as for gst_codec_utils_h264_get_profile(). |
| * |
| * Returns: The level as a const string, or %NULL if there is an error. |
| */ |
| const gchar * |
| gst_codec_utils_h264_get_level (const guint8 * sps, guint len) |
| { |
| gint csf3; |
| |
| g_return_val_if_fail (sps != NULL, NULL); |
| |
| if (len < 3) |
| return NULL; |
| |
| GST_MEMDUMP ("SPS", sps, len); |
| |
| csf3 = (sps[1] & 0x10) >> 4; |
| |
| if (sps[2] == 0) |
| return NULL; |
| else if ((sps[2] == 11 && csf3) || sps[2] == 9) |
| return "1b"; |
| else if (sps[2] % 10 == 0) |
| return digit_to_string (sps[2] / 10); |
| else { |
| switch (sps[2]) { |
| case 11: |
| return "1.1"; |
| case 12: |
| return "1.2"; |
| case 13: |
| return "1.3"; |
| case 21: |
| return "2.1"; |
| case 22: |
| return "2.2"; |
| case 31: |
| return "3.1"; |
| case 32: |
| return "3.2"; |
| case 41: |
| return "4.1"; |
| case 42: |
| return "4.2"; |
| case 51: |
| return "5.1"; |
| case 52: |
| return "5.2"; |
| default: |
| return NULL; |
| } |
| } |
| } |
| |
| /** |
| * gst_codec_utils_h264_get_level_idc: |
| * @level: A level string from caps |
| * |
| * Transform a level string from the caps into the level_idc |
| * |
| * Returns: the level_idc or 0 if the level is unknown |
| */ |
| guint8 |
| gst_codec_utils_h264_get_level_idc (const gchar * level) |
| { |
| g_return_val_if_fail (level != NULL, 0); |
| |
| if (!strcmp (level, "1")) |
| return 10; |
| else if (!strcmp (level, "1b")) |
| return 9; |
| else if (!strcmp (level, "1.1")) |
| return 11; |
| else if (!strcmp (level, "1.2")) |
| return 12; |
| else if (!strcmp (level, "1.3")) |
| return 13; |
| else if (!strcmp (level, "2")) |
| return 20; |
| else if (!strcmp (level, "2.1")) |
| return 21; |
| else if (!strcmp (level, "2.2")) |
| return 22; |
| else if (!strcmp (level, "3")) |
| return 30; |
| else if (!strcmp (level, "3.1")) |
| return 31; |
| else if (!strcmp (level, "3.2")) |
| return 32; |
| else if (!strcmp (level, "4")) |
| return 40; |
| else if (!strcmp (level, "4.1")) |
| return 41; |
| else if (!strcmp (level, "4.2")) |
| return 42; |
| else if (!strcmp (level, "5")) |
| return 50; |
| else if (!strcmp (level, "5.1")) |
| return 51; |
| else if (!strcmp (level, "5.2")) |
| return 52; |
| |
| GST_WARNING ("Invalid level %s", level); |
| return 0; |
| } |
| |
| /** |
| * gst_codec_utils_h264_caps_set_level_and_profile: |
| * @caps: the #GstCaps to which the level and profile are to be added |
| * @sps: (array length=len): Pointer to the sequence parameter set for the stream. |
| * @len: Length of the data available in @sps. |
| * |
| * Sets the level and profile in @caps if it can be determined from @sps. See |
| * gst_codec_utils_h264_get_level() and gst_codec_utils_h264_get_profile() |
| * for more details on the parameters. |
| * |
| * Returns: %TRUE if the level and profile could be set, %FALSE otherwise. |
| */ |
| gboolean |
| gst_codec_utils_h264_caps_set_level_and_profile (GstCaps * caps, |
| const guint8 * sps, guint len) |
| { |
| const gchar *level, *profile; |
| |
| g_return_val_if_fail (GST_IS_CAPS (caps), FALSE); |
| g_return_val_if_fail (GST_CAPS_IS_SIMPLE (caps), FALSE); |
| g_return_val_if_fail (GST_SIMPLE_CAPS_HAS_NAME (caps, "video/x-h264"), FALSE); |
| g_return_val_if_fail (sps != NULL, FALSE); |
| |
| level = gst_codec_utils_h264_get_level (sps, len); |
| |
| if (level != NULL) |
| gst_caps_set_simple (caps, "level", G_TYPE_STRING, level, NULL); |
| |
| profile = gst_codec_utils_h264_get_profile (sps, len); |
| |
| if (profile != NULL) |
| gst_caps_set_simple (caps, "profile", G_TYPE_STRING, profile, NULL); |
| |
| GST_LOG ("profile : %s", (profile) ? profile : "---"); |
| GST_LOG ("level : %s", (level) ? level : "---"); |
| |
| return (level != NULL && profile != NULL); |
| } |
| |
| /** |
| * gst_codec_utils_h265_get_profile: |
| * @profile_tier_level: (array length=len): Pointer to the profile_tier_level |
| * structure for the stream. |
| * @len: Length of the data available in @profile_tier_level |
| * |
| * Converts the profile indication (general_profile_idc) in the stream's |
| * profile_level_tier structure into a string. The profile_tier_level is |
| * expected to have the following format, as defined in the H.265 |
| * specification. The profile_tier_level is viewed as a bitstream here, |
| * with bit 0 being the most significant bit of the first byte. |
| * |
| * * Bit 0:1 - general_profile_space |
| * * Bit 2 - general_tier_flag |
| * * Bit 3:7 - general_profile_idc |
| * * Bit 8:39 - gernal_profile_compatibility_flags |
| * * Bit 40 - general_progressive_source_flag |
| * * Bit 41 - general_interlaced_source_flag |
| * * Bit 42 - general_non_packed_constraint_flag |
| * * Bit 43 - general_frame_only_constraint_flag |
| * * Bit 44:87 - general_reserved_zero_44bits |
| * * Bit 88:95 - general_level_idc |
| * |
| * Returns: The profile as a const string, or %NULL if there is an error. |
| * |
| * Since 1.4 |
| */ |
| const gchar * |
| gst_codec_utils_h265_get_profile (const guint8 * profile_tier_level, guint len) |
| { |
| const gchar *profile = NULL; |
| gint profile_idc; |
| |
| g_return_val_if_fail (profile_tier_level != NULL, NULL); |
| |
| if (len < 2) |
| return NULL; |
| |
| GST_MEMDUMP ("ProfileTierLevel", profile_tier_level, len); |
| |
| profile_idc = (profile_tier_level[0] & 0x1f); |
| |
| if (profile_idc == 1) |
| profile = "main"; |
| else if (profile_idc == 2) |
| profile = "main-10"; |
| else if (profile_idc == 3) |
| profile = "main-still-picture"; |
| else |
| profile = NULL; |
| |
| return profile; |
| } |
| |
| /** |
| * gst_codec_utils_h265_get_tier: |
| * @profile_tier_level: (array length=len): Pointer to the profile_tier_level |
| * for the stream. |
| * @len: Length of the data available in @profile_tier_level. |
| * |
| * Converts the tier indication (general_tier_flag) in the stream's |
| * profile_tier_level structure into a string. The profile_tier_level |
| * is expected to have the same format as for gst_codec_utils_h264_get_profile(). |
| * |
| * Returns: The tier as a const string, or %NULL if there is an error. |
| * |
| * Since 1.4 |
| */ |
| const gchar * |
| gst_codec_utils_h265_get_tier (const guint8 * profile_tier_level, guint len) |
| { |
| const gchar *tier = NULL; |
| gint tier_flag = 0; |
| |
| g_return_val_if_fail (profile_tier_level != NULL, NULL); |
| |
| if (len < 1) |
| return NULL; |
| |
| GST_MEMDUMP ("ProfileTierLevel", profile_tier_level, len); |
| |
| tier_flag = (profile_tier_level[0] & 0x20) >> 5; |
| |
| if (tier_flag) |
| tier = "high"; |
| else |
| tier = "main"; |
| |
| return tier; |
| } |
| |
| /** |
| * gst_codec_utils_h265_get_level: |
| * @profile_tier_level: (array length=len): Pointer to the profile_tier_level |
| * for the stream |
| * @len: Length of the data available in @profile_tier_level. |
| * |
| * Converts the level indication (general_level_idc) in the stream's |
| * profile_tier_level structure into a string. The profiel_tier_level is |
| * expected to have the same format as for gst_codec_utils_h264_get_profile(). |
| * |
| * Returns: The level as a const string, or %NULL if there is an error. |
| * |
| * Since 1.4 |
| */ |
| const gchar * |
| gst_codec_utils_h265_get_level (const guint8 * profile_tier_level, guint len) |
| { |
| g_return_val_if_fail (profile_tier_level != NULL, NULL); |
| |
| if (len < 12) |
| return NULL; |
| |
| GST_MEMDUMP ("ProfileTierLevel", profile_tier_level, len); |
| |
| if (profile_tier_level[11] == 0) |
| return NULL; |
| else if (profile_tier_level[11] % 30 == 0) |
| return digit_to_string (profile_tier_level[11] / 30); |
| else { |
| switch (profile_tier_level[11]) { |
| case 63: |
| return "2.1"; |
| break; |
| case 93: |
| return "3.1"; |
| break; |
| case 123: |
| return "4.1"; |
| break; |
| case 153: |
| return "5.1"; |
| break; |
| case 156: |
| return "5.2"; |
| break; |
| case 183: |
| return "6.1"; |
| break; |
| case 186: |
| return "6.2"; |
| break; |
| default: |
| return NULL; |
| } |
| } |
| } |
| |
| /** |
| * gst_codec_utils_h265_get_level_idc: |
| * @level: A level string from caps |
| * |
| * Transform a level string from the caps into the level_idc |
| * |
| * Returns: the level_idc or 0 if the level is unknown |
| * |
| * Since 1.4 |
| */ |
| guint8 |
| gst_codec_utils_h265_get_level_idc (const gchar * level) |
| { |
| g_return_val_if_fail (level != NULL, 0); |
| |
| if (!strcmp (level, "1")) |
| return 30; |
| else if (!strcmp (level, "2")) |
| return 60; |
| else if (!strcmp (level, "2.1")) |
| return 63; |
| else if (!strcmp (level, "3")) |
| return 90; |
| else if (!strcmp (level, "3.1")) |
| return 93; |
| else if (!strcmp (level, "4")) |
| return 120; |
| else if (!strcmp (level, "4.1")) |
| return 123; |
| else if (!strcmp (level, "5")) |
| return 150; |
| else if (!strcmp (level, "5.1")) |
| return 153; |
| else if (!strcmp (level, "5.2")) |
| return 156; |
| else if (!strcmp (level, "6")) |
| return 180; |
| else if (!strcmp (level, "6.1")) |
| return 183; |
| else if (!strcmp (level, "6.2")) |
| return 186; |
| |
| GST_WARNING ("Invalid level %s", level); |
| return 0; |
| } |
| |
| /** |
| * gst_codec_utils_h265_caps_set_level_tier_and_profile: |
| * @caps: the #GstCaps to which the level, tier and profile are to be added |
| * @profile_tier_level: (array length=len): Pointer to the profile_tier_level |
| * struct |
| * @len: Length of the data available in @profile_tier_level. |
| * |
| * Sets the level, tier and profile in @caps if it can be determined from |
| * @profile_tier_level. See gst_codec_utils_h265_get_level(), |
| * gst_codec_utils_h265_get_tier() and gst_codec_utils_h265_get_profile() |
| * for more details on the parameters. |
| * |
| * Returns: %TRUE if the level, tier, profile could be set, %FALSE otherwise. |
| * |
| * Since 1.4 |
| */ |
| gboolean |
| gst_codec_utils_h265_caps_set_level_tier_and_profile (GstCaps * caps, |
| const guint8 * profile_tier_level, guint len) |
| { |
| const gchar *level, *tier, *profile; |
| |
| g_return_val_if_fail (GST_IS_CAPS (caps), FALSE); |
| g_return_val_if_fail (GST_CAPS_IS_SIMPLE (caps), FALSE); |
| g_return_val_if_fail (GST_SIMPLE_CAPS_HAS_NAME (caps, "video/x-h265"), FALSE); |
| g_return_val_if_fail (profile_tier_level != NULL, FALSE); |
| |
| level = gst_codec_utils_h265_get_level (profile_tier_level, len); |
| if (level != NULL) |
| gst_caps_set_simple (caps, "level", G_TYPE_STRING, level, NULL); |
| |
| tier = gst_codec_utils_h265_get_tier (profile_tier_level, len); |
| if (tier != NULL) |
| gst_caps_set_simple (caps, "tier", G_TYPE_STRING, tier, NULL); |
| |
| profile = gst_codec_utils_h265_get_profile (profile_tier_level, len); |
| if (profile != NULL) |
| gst_caps_set_simple (caps, "profile", G_TYPE_STRING, profile, NULL); |
| |
| GST_LOG ("profile : %s", (profile) ? profile : "---"); |
| GST_LOG ("tier : %s", (tier) ? tier : "---"); |
| GST_LOG ("level : %s", (level) ? level : "---"); |
| |
| return (level != NULL && tier != NULL && profile != NULL); |
| } |
| |
| /** |
| * gst_codec_utils_mpeg4video_get_profile: |
| * @vis_obj_seq: (array length=len): Pointer to the visual object |
| * sequence for the stream. |
| * @len: Length of the data available in @sps. |
| * |
| * Converts the profile indication in the stream's visual object sequence into |
| * a string. @vis_obj_seq is expected to be the data following the visual |
| * object sequence start code. Only the first byte |
| * (profile_and_level_indication) is used. |
| * |
| * Returns: The profile as a const string, or NULL if there is an error. |
| */ |
| const gchar * |
| gst_codec_utils_mpeg4video_get_profile (const guint8 * vis_obj_seq, guint len) |
| { |
| /* The profile/level codes are from 14496-2, table G-1, and the Wireshark |
| * sources: epan/dissectors/packet-mp4ves.c */ |
| |
| /* These are a direct mapping from the integer profile id -> string. Profiles |
| * 0x6, 0xe and 0xf can correspond to more than one profile depending on the |
| * second 4 bits of vis_obj_seq[0], so they are handled separately. */ |
| static const char *profiles[] = { "simple", "simple-scalable", "core", |
| "main", "n-bit", "scalable", NULL, "basic-animated-texture", "hybrid", |
| "advanced-real-time-simple", "core-scalable", "advanced-coding-efficiency", |
| "advanced-core", "advanced-scalable-texture", |
| }; |
| int profile_id, level_id; |
| |
| g_return_val_if_fail (vis_obj_seq != NULL, NULL); |
| |
| if (len < 1) |
| return NULL; |
| |
| GST_MEMDUMP ("VOS", vis_obj_seq, len); |
| |
| profile_id = vis_obj_seq[0] >> 4; |
| level_id = vis_obj_seq[0] & 0xf; |
| |
| GST_LOG ("profile_id = %d, level_id = %d", profile_id, level_id); |
| |
| if (profile_id != 6 && profile_id < 0xe) |
| return profiles[profile_id]; |
| |
| if (profile_id != 0xf && level_id == 0) |
| return NULL; |
| |
| switch (profile_id) { |
| case 0x6: |
| if (level_id < 3) |
| return "simple-face"; |
| else if (level_id < 5) |
| return "simple-fba"; |
| break; |
| |
| case 0xe: |
| if (level_id < 5) |
| return "simple-studio"; |
| else if (level_id < 9) |
| return "core-studio"; |
| break; |
| |
| case 0xf: |
| if (level_id < 6) |
| return "advanced-simple"; |
| else if (level_id > 7 && level_id < 0xe) |
| return "fine-granularity-scalable"; |
| break; |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * gst_codec_utils_mpeg4video_get_level: |
| * @vis_obj_seq: (array length=len): Pointer to the visual object |
| * sequence for the stream. |
| * @len: Length of the data available in @sps. |
| * |
| * Converts the level indication in the stream's visual object sequence into |
| * a string. @vis_obj_seq is expected to be the data following the visual |
| * object sequence start code. Only the first byte |
| * (profile_and_level_indication) is used. |
| * |
| * Returns: The level as a const string, or NULL if there is an error. |
| */ |
| const gchar * |
| gst_codec_utils_mpeg4video_get_level (const guint8 * vis_obj_seq, guint len) |
| { |
| /* The profile/level codes are from 14496-2, table G-1, the Wireshark |
| * sources: epan/dissectors/packet-mp4ves.c and the Xvid Sources: |
| * src/xvid.h. |
| * Levels 4a and 5 for SP were added in Amendment 2, level 6 in Amendment 4 |
| * (see Xvid sources vfw/config.c) |
| * |
| * Each profile has a different maximum level it defines. Some of them still |
| * need special case handling, because not all levels start from 1, and the |
| * Simple profile defines an intermediate level as well. */ |
| static const int level_max[] = { 6, 2, 2, 4, 2, 1, 2, 2, 2, 4, 3, 4, 2, 3, 4, |
| 5 |
| }; |
| int profile_id, level_id; |
| |
| g_return_val_if_fail (vis_obj_seq != NULL, NULL); |
| |
| if (len < 1) |
| return NULL; |
| |
| GST_MEMDUMP ("VOS", vis_obj_seq, len); |
| |
| profile_id = vis_obj_seq[0] >> 4; |
| level_id = vis_obj_seq[0] & 0xf; |
| |
| GST_LOG ("profile_id = %d, level_id = %d", profile_id, level_id); |
| |
| if (profile_id != 0xf && level_id == 0) |
| return NULL; |
| |
| /* Let's do some validation of the level */ |
| switch (profile_id) { |
| case 0x3: |
| if (level_id == 1) |
| return NULL; |
| break; |
| |
| case 0x4: |
| if (level_id != 2) |
| return NULL; |
| break; |
| |
| case 0x6: |
| if (level_id > 5) |
| return NULL; |
| break; |
| |
| case 0xe: |
| if (level_id > 9) |
| return NULL; |
| break; |
| |
| case 0xf: |
| if (level_id == 6 || level_id == 7 || level_id > 0xd) |
| return NULL; |
| break; |
| } |
| |
| if (profile_id == 0 && level_id == 8) |
| /* Simple Profile / Level 0 */ |
| return "0"; |
| else if (profile_id == 0 && level_id == 9) |
| /* Simple Profile / Level 0b */ |
| return "0b"; |
| else if (profile_id == 0 && level_id == 4) |
| /* Simple Profile / Level 4a */ |
| return "4a"; |
| else if (profile_id == 0xf && level_id > 7) |
| /* Fine Granularity Scalable Profile */ |
| return digit_to_string (level_id - 8); |
| else if (level_id <= level_max[profile_id]) |
| /* Levels for all other cases */ |
| return digit_to_string (level_id); |
| |
| return NULL; |
| } |
| |
| /** |
| * gst_codec_utils_mpeg4video_caps_set_level_and_profile: |
| * @caps: the #GstCaps to which the level and profile are to be added |
| * @vis_obj_seq: (array length=len): Pointer to the visual object |
| * sequence for the stream. |
| * @len: Length of the data available in @sps. |
| * |
| * Sets the level and profile in @caps if it can be determined from |
| * @vis_obj_seq. See gst_codec_utils_mpeg4video_get_level() and |
| * gst_codec_utils_mpeg4video_get_profile() for more details on the |
| * parameters. |
| * |
| * Returns: %TRUE if the level and profile could be set, %FALSE otherwise. |
| */ |
| gboolean |
| gst_codec_utils_mpeg4video_caps_set_level_and_profile (GstCaps * caps, |
| const guint8 * vis_obj_seq, guint len) |
| { |
| const gchar *profile, *level; |
| |
| g_return_val_if_fail (GST_IS_CAPS (caps), FALSE); |
| g_return_val_if_fail (GST_CAPS_IS_SIMPLE (caps), FALSE); |
| g_return_val_if_fail (vis_obj_seq != NULL, FALSE); |
| |
| profile = gst_codec_utils_mpeg4video_get_profile (vis_obj_seq, len); |
| |
| if (profile != NULL) |
| gst_caps_set_simple (caps, "profile", G_TYPE_STRING, profile, NULL); |
| |
| level = gst_codec_utils_mpeg4video_get_level (vis_obj_seq, len); |
| |
| if (level != NULL) |
| gst_caps_set_simple (caps, "level", G_TYPE_STRING, level, NULL); |
| |
| GST_LOG ("profile : %s", (profile) ? profile : "---"); |
| GST_LOG ("level : %s", (level) ? level : "---"); |
| |
| return (profile != NULL && level != NULL); |
| } |
| |
| /** |
| * gst_codec_utils_opus_parse_caps: |
| * @caps: the #GstCaps to parse the data from |
| * @rate: (out): the sample rate |
| * @channels: (out): the number of channels |
| * @channel_mapping_family: (out): the channel mapping family |
| * @stream_count: (out): the number of independent streams |
| * @coupled_count: (out): the number of stereo streams |
| * @channel_mapping: (out) (array fixed-size=256): the mapping between the streams |
| * |
| * Parses Opus caps and fills the different fields with defaults if possible. |
| * |
| * Returns: %TRUE if parsing was successful, %FALSE otherwise. |
| * |
| * Since: 1.8 |
| */ |
| gboolean |
| gst_codec_utils_opus_parse_caps (GstCaps * caps, |
| guint32 * rate, |
| guint8 * channels, |
| guint8 * channel_mapping_family, |
| guint8 * stream_count, guint8 * coupled_count, guint8 channel_mapping[256]) |
| { |
| GstStructure *s; |
| gint c, f, sc, cc; |
| const GValue *va, *v; |
| |
| g_return_val_if_fail (caps != NULL, FALSE); |
| g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE); |
| g_return_val_if_fail (!gst_caps_is_empty (caps), FALSE); |
| |
| s = gst_caps_get_structure (caps, 0); |
| |
| g_return_val_if_fail (gst_structure_has_name (s, "audio/x-opus"), FALSE); |
| g_return_val_if_fail (gst_structure_has_field_typed (s, |
| "channel-mapping-family", G_TYPE_INT), FALSE); |
| |
| if (rate) { |
| gint r; |
| |
| if (gst_structure_get_int (s, "rate", &r)) |
| *rate = r; |
| else |
| *rate = 48000; |
| } |
| |
| gst_structure_get_int (s, "channel-mapping-family", &f); |
| if (channel_mapping_family) |
| *channel_mapping_family = f; |
| |
| if (!gst_structure_get_int (s, "channels", &c)) { |
| if (f == 0) |
| c = 2; |
| else |
| return FALSE; |
| } |
| |
| if (channels) |
| *channels = c; |
| |
| /* RTP mapping */ |
| if (f == 0) { |
| if (c > 2) |
| return FALSE; |
| |
| if (stream_count) |
| *stream_count = 1; |
| if (coupled_count) |
| *coupled_count = c == 2 ? 1 : 0; |
| |
| if (channel_mapping) { |
| channel_mapping[0] = 0; |
| channel_mapping[1] = 1; |
| } |
| |
| return TRUE; |
| } |
| |
| if (!gst_structure_get_int (s, "stream-count", &sc)) |
| return FALSE; |
| if (stream_count) |
| *stream_count = sc; |
| |
| if (!gst_structure_get_int (s, "coupled-count", &cc)) |
| return FALSE; |
| if (coupled_count) |
| *coupled_count = cc; |
| |
| va = gst_structure_get_value (s, "channel-mapping"); |
| if (!va || !G_VALUE_HOLDS (va, GST_TYPE_ARRAY)) |
| return FALSE; |
| |
| if (gst_value_array_get_size (va) != c) |
| return FALSE; |
| |
| if (channel_mapping) { |
| gint i; |
| |
| for (i = 0; i < c; i++) { |
| gint cm; |
| |
| v = gst_value_array_get_value (va, i); |
| |
| if (!G_VALUE_HOLDS (v, G_TYPE_INT)) |
| return FALSE; |
| |
| cm = g_value_get_int (v); |
| if (cm < 0 || cm > 255) |
| return FALSE; |
| |
| channel_mapping[i] = cm; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| /** |
| * gst_codec_utils_opus_create_caps: |
| * @rate: the sample rate |
| * @channels: the number of channels |
| * @channel_mapping_family: the channel mapping family |
| * @stream_count: the number of independent streams |
| * @coupled_count: the number of stereo streams |
| * @channel_mapping: (allow-none) (array): the mapping between the streams |
| * |
| * Creates Opus caps from the given parameters. |
| * |
| * Returns: The #GstCaps, or %NULL if the parameters would lead to |
| * invalid Opus caps. |
| * |
| * Since: 1.8 |
| */ |
| GstCaps * |
| gst_codec_utils_opus_create_caps (guint32 rate, |
| guint8 channels, |
| guint8 channel_mapping_family, |
| guint8 stream_count, guint8 coupled_count, const guint8 * channel_mapping) |
| { |
| GstCaps *caps = NULL; |
| GValue va = G_VALUE_INIT; |
| GValue v = G_VALUE_INIT; |
| gint i; |
| |
| if (rate == 0) |
| rate = 48000; |
| |
| if (channel_mapping_family == 0) { |
| if (channels > 2) { |
| GST_ERROR ("Invalid channels count for channel_mapping_family 0: %d", |
| channels); |
| goto done; |
| } |
| |
| if (stream_count > 1) { |
| GST_ERROR ("Invalid stream count for channel_mapping_family 0: %d", |
| stream_count); |
| goto done; |
| } |
| |
| if (coupled_count > 1) { |
| GST_ERROR ("Invalid coupled count for channel_mapping_family 0: %d", |
| coupled_count); |
| goto done; |
| } |
| |
| if (channels == 0) |
| channels = 2; |
| |
| if (stream_count == 0) |
| stream_count = 1; |
| |
| if (coupled_count == 0) |
| coupled_count = channels == 2 ? 1 : 0; |
| |
| return gst_caps_new_simple ("audio/x-opus", |
| "rate", G_TYPE_INT, rate, |
| "channels", G_TYPE_INT, channels, |
| "channel-mapping-family", G_TYPE_INT, channel_mapping_family, |
| "stream-count", G_TYPE_INT, stream_count, |
| "coupled-count", G_TYPE_INT, coupled_count, NULL); |
| } |
| |
| if (channels == 0) { |
| GST_ERROR ("Invalid channels count: %d", channels); |
| goto done; |
| } |
| |
| if (stream_count == 0) { |
| GST_ERROR ("Invalid stream count: %d", stream_count); |
| goto done; |
| } |
| |
| if (coupled_count > stream_count) { |
| GST_ERROR ("Coupled count %d > stream count: %d", coupled_count, |
| stream_count); |
| goto done; |
| } |
| |
| if (channel_mapping == NULL) { |
| GST_ERROR |
| ("A non NULL channel-mapping is needed for channel_mapping_family != 0"); |
| goto done; |
| } |
| |
| caps = gst_caps_new_simple ("audio/x-opus", |
| "rate", G_TYPE_INT, rate, |
| "channels", G_TYPE_INT, channels, |
| "channel-mapping-family", G_TYPE_INT, channel_mapping_family, |
| "stream-count", G_TYPE_INT, stream_count, |
| "coupled-count", G_TYPE_INT, coupled_count, NULL); |
| |
| g_value_init (&va, GST_TYPE_ARRAY); |
| g_value_init (&v, G_TYPE_INT); |
| for (i = 0; i < channels; i++) { |
| g_value_set_int (&v, channel_mapping[i]); |
| gst_value_array_append_value (&va, &v); |
| } |
| gst_structure_set_value (gst_caps_get_structure (caps, 0), "channel-mapping", |
| &va); |
| g_value_unset (&va); |
| g_value_unset (&v); |
| |
| done: |
| return caps; |
| } |
| |
| /* |
| * (really really) FIXME: move into core (dixit tpm) |
| */ |
| /* |
| * _gst_caps_set_buffer_array: |
| * @caps: (transfer full): a #GstCaps |
| * @field: field in caps to set |
| * @buf: header buffers |
| * |
| * Adds given buffers to an array of buffers set as the given @field |
| * on the given @caps. List of buffer arguments must be NULL-terminated. |
| * |
| * Returns: (transfer full): input caps with a streamheader field added, or NULL |
| * if some error occurred |
| */ |
| static GstCaps * |
| _gst_caps_set_buffer_array (GstCaps * caps, const gchar * field, |
| GstBuffer * buf, ...) |
| { |
| GstStructure *structure = NULL; |
| va_list va; |
| GValue array = { 0 }; |
| GValue value = { 0 }; |
| |
| g_return_val_if_fail (caps != NULL, NULL); |
| g_return_val_if_fail (gst_caps_is_fixed (caps), NULL); |
| g_return_val_if_fail (field != NULL, NULL); |
| |
| caps = gst_caps_make_writable (caps); |
| structure = gst_caps_get_structure (caps, 0); |
| |
| g_value_init (&array, GST_TYPE_ARRAY); |
| |
| va_start (va, buf); |
| /* put buffers in a fixed list */ |
| while (buf) { |
| g_assert (gst_buffer_is_writable (buf)); |
| |
| /* mark buffer */ |
| GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER); |
| |
| g_value_init (&value, GST_TYPE_BUFFER); |
| buf = gst_buffer_copy (buf); |
| GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER); |
| gst_value_set_buffer (&value, buf); |
| gst_buffer_unref (buf); |
| gst_value_array_append_value (&array, &value); |
| g_value_unset (&value); |
| |
| buf = va_arg (va, GstBuffer *); |
| } |
| va_end (va); |
| |
| gst_structure_set_value (structure, field, &array); |
| g_value_unset (&array); |
| |
| return caps; |
| } |
| |
| /** |
| * gst_codec_utils_opus_create_caps_from_header: |
| * @header: OpusHead header |
| * @comments: (allow-none): Comment header or NULL |
| * |
| * Creates Opus caps from the given OpusHead @header and comment header |
| * @comments. |
| * |
| * Returns: The #GstCaps. |
| * |
| * Since: 1.8 |
| */ |
| GstCaps * |
| gst_codec_utils_opus_create_caps_from_header (GstBuffer * header, |
| GstBuffer * comments) |
| { |
| GstCaps *caps; |
| guint32 rate; |
| guint8 channels; |
| guint8 channel_mapping_family; |
| guint8 stream_count; |
| guint8 coupled_count; |
| guint8 channel_mapping[256]; |
| GstBuffer *dummy_comments = NULL; |
| |
| g_return_val_if_fail (GST_IS_BUFFER (header), NULL); |
| g_return_val_if_fail (comments == NULL || GST_IS_BUFFER (comments), NULL); |
| |
| if (!gst_codec_utils_opus_parse_header (header, &rate, &channels, |
| &channel_mapping_family, &stream_count, &coupled_count, |
| channel_mapping, NULL, NULL)) |
| return NULL; |
| |
| if (!(caps = |
| gst_codec_utils_opus_create_caps (rate, channels, |
| channel_mapping_family, stream_count, coupled_count, |
| channel_mapping))) |
| return NULL; |
| |
| if (!comments) { |
| GstTagList *tags = gst_tag_list_new_empty (); |
| dummy_comments = |
| gst_tag_list_to_vorbiscomment_buffer (tags, (const guint8 *) "OpusTags", |
| 8, NULL); |
| gst_tag_list_unref (tags); |
| } |
| _gst_caps_set_buffer_array (caps, "streamheader", header, |
| comments ? comments : dummy_comments, NULL); |
| |
| if (dummy_comments) |
| gst_buffer_unref (dummy_comments); |
| |
| return caps; |
| } |
| |
| /** |
| * gst_codec_utils_opus_create_header: |
| * @rate: the sample rate |
| * @channels: the number of channels |
| * @channel_mapping_family: the channel mapping family |
| * @stream_count: the number of independent streams |
| * @coupled_count: the number of stereo streams |
| * @channel_mapping: (allow-none) (array): the mapping between the streams |
| * @pre_skip: Pre-skip in 48kHz samples or 0 |
| * @output_gain: Output gain or 0 |
| * |
| * Creates OpusHead header from the given parameters. |
| * |
| * Returns: The #GstBuffer containing the OpusHead. |
| * |
| * Since: 1.8 |
| */ |
| GstBuffer * |
| gst_codec_utils_opus_create_header (guint32 rate, |
| guint8 channels, |
| guint8 channel_mapping_family, |
| guint8 stream_count, |
| guint8 coupled_count, |
| const guint8 * channel_mapping, guint16 pre_skip, gint16 output_gain) |
| { |
| GstBuffer *buffer; |
| GstByteWriter bw; |
| gboolean hdl = TRUE; |
| |
| if (rate == 0) |
| rate = 48000; |
| |
| if (channel_mapping_family == 0) { |
| g_return_val_if_fail (channels <= 2, NULL); |
| if (channels == 0) |
| channels = 2; |
| |
| g_return_val_if_fail (stream_count == 0 || stream_count == 1, NULL); |
| if (stream_count == 0) |
| stream_count = 1; |
| |
| g_return_val_if_fail (coupled_count == 0 || coupled_count == 1, NULL); |
| if (coupled_count == 0) |
| coupled_count = channels == 2 ? 1 : 0; |
| |
| channel_mapping = NULL; |
| } else { |
| g_return_val_if_fail (channels > 0, NULL); |
| g_return_val_if_fail (stream_count > 0, NULL); |
| g_return_val_if_fail (coupled_count <= stream_count, NULL); |
| g_return_val_if_fail (channel_mapping != NULL, NULL); |
| } |
| |
| gst_byte_writer_init (&bw); |
| /* See http://wiki.xiph.org/OggOpus */ |
| hdl &= gst_byte_writer_put_data (&bw, (const guint8 *) "OpusHead", 8); |
| hdl &= gst_byte_writer_put_uint8 (&bw, 0x01); /* version number */ |
| hdl &= gst_byte_writer_put_uint8 (&bw, channels); |
| hdl &= gst_byte_writer_put_uint16_le (&bw, pre_skip); |
| hdl &= gst_byte_writer_put_uint32_le (&bw, rate); |
| hdl &= gst_byte_writer_put_uint16_le (&bw, output_gain); |
| hdl &= gst_byte_writer_put_uint8 (&bw, channel_mapping_family); |
| if (channel_mapping_family > 0) { |
| hdl &= gst_byte_writer_put_uint8 (&bw, stream_count); |
| hdl &= gst_byte_writer_put_uint8 (&bw, coupled_count); |
| hdl &= gst_byte_writer_put_data (&bw, channel_mapping, channels); |
| } |
| |
| if (!hdl) { |
| GST_WARNING ("Error creating header"); |
| return NULL; |
| } |
| |
| buffer = gst_byte_writer_reset_and_get_buffer (&bw); |
| GST_BUFFER_OFFSET (buffer) = 0; |
| GST_BUFFER_OFFSET_END (buffer) = 0; |
| |
| return buffer; |
| } |
| |
| /** |
| * gst_codec_utils_opus_parse_header: |
| * @header: the OpusHead #GstBuffer |
| * @rate: (out): the sample rate |
| * @channels: (out): the number of channels |
| * @channel_mapping_family: (out): the channel mapping family |
| * @stream_count: (out): the number of independent streams |
| * @coupled_count: (out): the number of stereo streams |
| * @channel_mapping: (out) (array fixed-size=256): the mapping between the streams |
| * @pre_skip: (out): Pre-skip in 48kHz samples or 0 |
| * @output_gain: (out): Output gain or 0 |
| * |
| * Parses the OpusHead header. |
| * |
| * Returns: %TRUE if parsing was successful, %FALSE otherwise. |
| * |
| * Since: 1.8 |
| */ |
| gboolean |
| gst_codec_utils_opus_parse_header (GstBuffer * header, |
| guint32 * rate, |
| guint8 * channels, |
| guint8 * channel_mapping_family, |
| guint8 * stream_count, |
| guint8 * coupled_count, |
| guint8 channel_mapping[256], guint16 * pre_skip, gint16 * output_gain) |
| { |
| GstByteReader br; |
| GstMapInfo map; |
| gboolean ret = TRUE; |
| guint8 c, f, version; |
| |
| g_return_val_if_fail (GST_IS_BUFFER (header), FALSE); |
| g_return_val_if_fail (gst_buffer_get_size (header) >= 19, FALSE); |
| |
| if (!gst_buffer_map (header, &map, GST_MAP_READ)) |
| return FALSE; |
| gst_byte_reader_init (&br, map.data, map.size); |
| /* See http://wiki.xiph.org/OggOpus */ |
| if (memcmp (gst_byte_reader_get_data_unchecked (&br, 8), "OpusHead", 8) != 0) { |
| ret = FALSE; |
| goto done; |
| } |
| version = gst_byte_reader_get_uint8_unchecked (&br); |
| if (version == 0x00) |
| GST_ERROR ("Opus Header version is wrong, should be 0x01 and not 0x00"); |
| else if (version != 0x01) { |
| ret = FALSE; |
| goto done; |
| } |
| |
| c = gst_byte_reader_get_uint8_unchecked (&br); |
| if (channels) |
| *channels = c; |
| |
| if (pre_skip) |
| *pre_skip = gst_byte_reader_get_uint16_le_unchecked (&br); |
| else |
| gst_byte_reader_skip_unchecked (&br, 2); |
| |
| if (rate) |
| *rate = gst_byte_reader_get_uint32_le_unchecked (&br); |
| else |
| gst_byte_reader_skip_unchecked (&br, 4); |
| |
| if (output_gain) |
| *output_gain = gst_byte_reader_get_uint16_le_unchecked (&br); |
| else |
| gst_byte_reader_skip_unchecked (&br, 2); |
| |
| f = gst_byte_reader_get_uint8_unchecked (&br); |
| if (channel_mapping_family) |
| *channel_mapping_family = f; |
| if (f == 0 && c <= 2) { |
| if (stream_count) |
| *stream_count = 1; |
| if (coupled_count) |
| *coupled_count = c == 2 ? 1 : 0; |
| if (channel_mapping) { |
| channel_mapping[0] = 0; |
| channel_mapping[1] = 1; |
| } |
| |
| goto done; |
| } |
| |
| if (gst_byte_reader_get_remaining (&br) < 2 + c) { |
| ret = FALSE; |
| goto done; |
| } |
| |
| if (stream_count) |
| *stream_count = gst_byte_reader_get_uint8_unchecked (&br); |
| else |
| gst_byte_reader_skip_unchecked (&br, 1); |
| |
| if (coupled_count) |
| *coupled_count = gst_byte_reader_get_uint8_unchecked (&br); |
| else |
| gst_byte_reader_skip_unchecked (&br, 1); |
| |
| if (channel_mapping) |
| memcpy (channel_mapping, gst_byte_reader_get_data_unchecked (&br, c), c); |
| |
| done: |
| gst_buffer_unmap (header, &map); |
| |
| return ret; |
| } |