| /* |
| * gstmpegtsdescriptor.c - |
| * Copyright (C) 2013 Edward Hervey |
| * |
| * Authors: |
| * Edward Hervey <edward@collabora.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. |
| */ |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "mpegts.h" |
| #include "gstmpegts-private.h" |
| |
| /** |
| * SECTION:gstmpegtsdescriptor |
| * @title: Base MPEG-TS descriptors |
| * @short_description: Descriptors for ITU H.222.0 | ISO/IEC 13818-1 |
| * @include: gst/mpegts/mpegts.h |
| * |
| * These are the base descriptor types and methods. |
| * |
| * For more details, refer to the ITU H.222.0 or ISO/IEC 13818-1 specifications |
| * and other specifications mentionned in the documentation. |
| */ |
| |
| /* FIXME : Move this to proper file once we have a C file for ATSC/ISDB descriptors */ |
| /** |
| * SECTION:gst-atsc-descriptor |
| * @title: ATSC variants of MPEG-TS descriptors |
| * @short_description: Descriptors for the various ATSC specifications |
| * @include: gst/mpegts/mpegts.h |
| * |
| */ |
| |
| /** |
| * SECTION:gst-isdb-descriptor |
| * @title: ISDB variants of MPEG-TS descriptors |
| * @short_description: Descriptors for the various ISDB specifications |
| * @include: gst/mpegts/mpegts.h |
| * |
| */ |
| |
| |
| /* |
| * TODO |
| * |
| * * Add common validation code for data presence and minimum/maximum expected |
| * size. |
| * * Add parsing methods for the following descriptors that were previously |
| * handled in mpegtsbase: |
| * * GST_MTS_DESC_DVB_DATA_BROADCAST_ID |
| * * GST_MTS_DESC_DVB_CAROUSEL_IDENTIFIER |
| * * GST_MTS_DESC_DVB_FREQUENCY_LIST |
| */ |
| |
| #define MAX_KNOWN_ICONV 25 |
| |
| /* First column is the original encoding, |
| * second column is the target encoding */ |
| |
| static GIConv __iconvs[MAX_KNOWN_ICONV][MAX_KNOWN_ICONV]; |
| |
| /* All these conversions will be to UTF8 */ |
| typedef enum |
| { |
| _ICONV_UNKNOWN = -1, |
| _ICONV_ISO8859_1, |
| _ICONV_ISO8859_2, |
| _ICONV_ISO8859_3, |
| _ICONV_ISO8859_4, |
| _ICONV_ISO8859_5, |
| _ICONV_ISO8859_6, |
| _ICONV_ISO8859_7, |
| _ICONV_ISO8859_8, |
| _ICONV_ISO8859_9, |
| _ICONV_ISO8859_10, |
| _ICONV_ISO8859_11, |
| _ICONV_ISO8859_12, |
| _ICONV_ISO8859_13, |
| _ICONV_ISO8859_14, |
| _ICONV_ISO8859_15, |
| _ICONV_UCS_2BE, |
| _ICONV_EUC_KR, |
| _ICONV_GB2312, |
| _ICONV_UTF_16BE, |
| _ICONV_ISO10646_UTF8, |
| _ICONV_ISO6937, |
| _ICONV_UTF8, |
| /* Insert more here if needed */ |
| _ICONV_MAX |
| } LocalIconvCode; |
| |
| static const gchar *iconvtablename[] = { |
| "iso-8859-1", |
| "iso-8859-2", |
| "iso-8859-3", |
| "iso-8859-4", |
| "iso-8859-5", |
| "iso-8859-6", |
| "iso-8859-7", |
| "iso-8859-8", |
| "iso-8859-9", |
| "iso-8859-10", |
| "iso-8859-11", |
| "iso-8859-12", |
| "iso-8859-13", |
| "iso-8859-14", |
| "iso-8859-15", |
| "UCS-2BE", |
| "EUC-KR", |
| "GB2312", |
| "UTF-16BE", |
| "ISO-10646/UTF8", |
| "iso6937", |
| "utf-8" |
| /* Insert more here if needed */ |
| }; |
| |
| void |
| __initialize_descriptors (void) |
| { |
| guint i, j; |
| |
| /* Initialize converters */ |
| /* FIXME : How/when should we close them ??? */ |
| for (i = 0; i < MAX_KNOWN_ICONV; i++) { |
| for (j = 0; j < MAX_KNOWN_ICONV; j++) |
| __iconvs[i][j] = ((GIConv) - 1); |
| } |
| } |
| |
| /* |
| * @text: The text you want to get the encoding from |
| * @start_text: Location where the beginning of the actual text is stored |
| * @is_multibyte: Location where information whether it's a multibyte encoding |
| * or not is stored |
| * @returns: GIconv for conversion or NULL |
| */ |
| static LocalIconvCode |
| get_encoding (const gchar * text, guint * start_text, gboolean * is_multibyte) |
| { |
| LocalIconvCode encoding; |
| guint8 firstbyte; |
| |
| *is_multibyte = FALSE; |
| *start_text = 0; |
| |
| firstbyte = (guint8) text[0]; |
| |
| /* A wrong value */ |
| g_return_val_if_fail (firstbyte != 0x00, _ICONV_UNKNOWN); |
| |
| if (firstbyte <= 0x0B) { |
| /* 0x01 => iso 8859-5 */ |
| encoding = firstbyte + _ICONV_ISO8859_4; |
| *start_text = 1; |
| goto beach; |
| } |
| |
| /* ETSI EN 300 468, "Selection of character table" */ |
| switch (firstbyte) { |
| case 0x0C: |
| case 0x0D: |
| case 0x0E: |
| case 0x0F: |
| /* RESERVED */ |
| encoding = _ICONV_UNKNOWN; |
| break; |
| case 0x10: |
| { |
| guint16 table; |
| |
| table = GST_READ_UINT16_BE (text + 1); |
| |
| if (table < 17) |
| encoding = _ICONV_UNKNOWN + table; |
| else |
| encoding = _ICONV_UNKNOWN; |
| *start_text = 3; |
| break; |
| } |
| case 0x11: |
| encoding = _ICONV_UCS_2BE; |
| *start_text = 1; |
| *is_multibyte = TRUE; |
| break; |
| case 0x12: |
| /* EUC-KR implements KSX1001 */ |
| encoding = _ICONV_EUC_KR; |
| *start_text = 1; |
| *is_multibyte = TRUE; |
| break; |
| case 0x13: |
| encoding = _ICONV_GB2312; |
| *start_text = 1; |
| break; |
| case 0x14: |
| encoding = _ICONV_UTF_16BE; |
| *start_text = 1; |
| *is_multibyte = TRUE; |
| break; |
| case 0x15: |
| /* TODO : Where does this come from ?? */ |
| encoding = _ICONV_ISO10646_UTF8; |
| *start_text = 1; |
| break; |
| case 0x16: |
| case 0x17: |
| case 0x18: |
| case 0x19: |
| case 0x1A: |
| case 0x1B: |
| case 0x1C: |
| case 0x1D: |
| case 0x1E: |
| case 0x1F: |
| /* RESERVED */ |
| encoding = _ICONV_UNKNOWN; |
| break; |
| default: |
| encoding = _ICONV_ISO6937; |
| break; |
| } |
| |
| beach: |
| GST_DEBUG |
| ("Found encoding %d, first byte is 0x%02x, start_text: %u, is_multibyte: %d", |
| encoding, firstbyte, *start_text, *is_multibyte); |
| |
| return encoding; |
| } |
| |
| static GIConv |
| _get_iconv (LocalIconvCode from, LocalIconvCode to) |
| { |
| if (__iconvs[from][to] == (GIConv) - 1) |
| __iconvs[from][to] = g_iconv_open (iconvtablename[to], |
| iconvtablename[from]); |
| return __iconvs[from][to]; |
| } |
| |
| static void |
| _encode_control_codes (gchar * text, gsize length, gboolean is_multibyte) |
| { |
| gsize pos = 0; |
| |
| while (pos < length) { |
| if (is_multibyte) { |
| guint16 code = GST_READ_UINT16_BE (text + pos); |
| if (code == 0x000A) { |
| text[pos] = 0xE0; |
| text[pos + 1] = 0x8A; |
| } |
| pos += 2; |
| } else { |
| guint8 code = text[pos]; |
| if (code == 0x0A) |
| text[pos] = 0x8A; |
| pos++; |
| } |
| } |
| } |
| |
| /** |
| * dvb_text_from_utf8: |
| * @text: The text to convert. This should be in UTF-8 format |
| * @out_size: (out): the byte length of the new text |
| * |
| * Converts UTF-8 strings to text characters compliant with EN 300 468. |
| * The converted text can be used directly in DVB #GstMpegtsDescriptor |
| * |
| * The function will try different character maps until the string is |
| * completely converted. |
| * |
| * The function tries the default ISO 6937 character map first. |
| * |
| * If no character map that contains all characters could be found, the |
| * string is converted to ISO 6937 with unknown characters set to `?`. |
| * |
| * Returns: (transfer full): byte array of size @out_size |
| */ |
| guint8 * |
| dvb_text_from_utf8 (const gchar * text, gsize * out_size) |
| { |
| GError *error = NULL; |
| gchar *out_text; |
| guint8 *out_buffer; |
| guint encoding; |
| GIConv giconv = (GIConv) - 1; |
| |
| /* We test character maps one-by-one. Start with the default */ |
| encoding = _ICONV_ISO6937; |
| giconv = _get_iconv (_ICONV_UTF8, encoding); |
| out_text = g_convert_with_iconv (text, -1, giconv, NULL, out_size, &error); |
| |
| if (out_text) { |
| GST_DEBUG ("Using default ISO6937 encoding"); |
| goto out; |
| } |
| |
| g_clear_error (&error); |
| |
| for (encoding = _ICONV_ISO8859_1; encoding <= _ICONV_ISO10646_UTF8; |
| encoding++) { |
| giconv = _get_iconv (_ICONV_UTF8, encoding); |
| if (giconv == (GIConv) - 1) |
| continue; |
| out_text = g_convert_with_iconv (text, -1, giconv, NULL, out_size, &error); |
| |
| if (out_text) { |
| GST_DEBUG ("Found suitable character map - %s", iconvtablename[encoding]); |
| goto out; |
| } |
| |
| g_clear_error (&error); |
| } |
| |
| out_text = g_convert_with_fallback (text, -1, iconvtablename[_ICONV_ISO6937], |
| iconvtablename[_ICONV_UTF8], "?", NULL, out_size, &error); |
| |
| out: |
| |
| if (error) { |
| GST_WARNING ("Could not convert from utf-8: %s", error->message); |
| g_error_free (error); |
| g_free (out_text); |
| return NULL; |
| } |
| |
| switch (encoding) { |
| case _ICONV_ISO6937: |
| /* Default encoding contains no selection bytes. */ |
| _encode_control_codes (out_text, *out_size, FALSE); |
| return (guint8 *) out_text; |
| case _ICONV_ISO8859_1: |
| case _ICONV_ISO8859_2: |
| case _ICONV_ISO8859_3: |
| case _ICONV_ISO8859_4: |
| /* These character sets requires 3 selection bytes */ |
| _encode_control_codes (out_text, *out_size, FALSE); |
| out_buffer = g_malloc (*out_size + 3); |
| out_buffer[0] = 0x10; |
| out_buffer[1] = 0x00; |
| out_buffer[2] = encoding - _ICONV_ISO8859_1 + 1; |
| memcpy (out_buffer + 3, out_text, *out_size); |
| *out_size += 3; |
| g_free (out_text); |
| return out_buffer; |
| case _ICONV_ISO8859_5: |
| case _ICONV_ISO8859_6: |
| case _ICONV_ISO8859_7: |
| case _ICONV_ISO8859_8: |
| case _ICONV_ISO8859_9: |
| case _ICONV_ISO8859_10: |
| case _ICONV_ISO8859_11: |
| case _ICONV_ISO8859_12: |
| case _ICONV_ISO8859_13: |
| case _ICONV_ISO8859_14: |
| case _ICONV_ISO8859_15: |
| /* These character sets requires 1 selection byte */ |
| _encode_control_codes (out_text, *out_size, FALSE); |
| out_buffer = g_malloc (*out_size + 1); |
| out_buffer[0] = encoding - _ICONV_ISO8859_5 + 1; |
| memcpy (out_buffer + 1, out_text, *out_size); |
| *out_size += 1; |
| g_free (out_text); |
| return out_buffer; |
| case _ICONV_UCS_2BE: |
| case _ICONV_EUC_KR: |
| case _ICONV_UTF_16BE: |
| /* These character sets requires 1 selection byte */ |
| _encode_control_codes (out_text, *out_size, TRUE); |
| out_buffer = g_malloc (*out_size + 1); |
| out_buffer[0] = encoding - _ICONV_UCS_2BE + 0x11; |
| memcpy (out_buffer + 1, out_text, *out_size); |
| *out_size += 1; |
| g_free (out_text); |
| return out_buffer; |
| case _ICONV_GB2312: |
| case _ICONV_ISO10646_UTF8: |
| /* These character sets requires 1 selection byte */ |
| _encode_control_codes (out_text, *out_size, FALSE); |
| out_buffer = g_malloc (*out_size + 1); |
| out_buffer[0] = encoding - _ICONV_UCS_2BE + 0x11; |
| memcpy (out_buffer + 1, out_text, *out_size); |
| *out_size += 1; |
| g_free (out_text); |
| return out_buffer; |
| default: |
| g_free (out_text); |
| return NULL; |
| } |
| } |
| |
| /* |
| * @text: The text to convert. It may include pango markup (<b> and </b>) |
| * @length: The length of the string -1 if it's nul-terminated |
| * @start: Where to start converting in the text |
| * @encoding: The encoding of text |
| * @is_multibyte: Whether the encoding is a multibyte encoding |
| * @error: The location to store the error, or NULL to ignore errors |
| * @returns: UTF-8 encoded string |
| * |
| * Convert text to UTF-8. |
| */ |
| static gchar * |
| convert_to_utf8 (const gchar * text, gint length, guint start, |
| GIConv giconv, gboolean is_multibyte, GError ** error) |
| { |
| gchar *new_text; |
| gchar *tmp, *pos; |
| gint i; |
| |
| text += start; |
| |
| pos = tmp = g_malloc (length * 2); |
| |
| if (is_multibyte) { |
| if (length == -1) { |
| while (*text != '\0') { |
| guint16 code = GST_READ_UINT16_BE (text); |
| |
| switch (code) { |
| case 0xE086: /* emphasis on */ |
| case 0xE087: /* emphasis off */ |
| /* skip it */ |
| break; |
| case 0xE08A:{ |
| pos[0] = 0x00; /* 0x00 0x0A is a new line */ |
| pos[1] = 0x0A; |
| pos += 2; |
| break; |
| } |
| default: |
| pos[0] = text[0]; |
| pos[1] = text[1]; |
| pos += 2; |
| break; |
| } |
| |
| text += 2; |
| } |
| } else { |
| for (i = 0; i < length; i += 2) { |
| guint16 code = GST_READ_UINT16_BE (text); |
| |
| switch (code) { |
| case 0xE086: /* emphasis on */ |
| case 0xE087: /* emphasis off */ |
| /* skip it */ |
| break; |
| case 0xE08A:{ |
| pos[0] = 0x00; /* 0x00 0x0A is a new line */ |
| pos[1] = 0x0A; |
| pos += 2; |
| break; |
| } |
| default: |
| pos[0] = text[0]; |
| pos[1] = text[1]; |
| pos += 2; |
| break; |
| } |
| |
| text += 2; |
| } |
| } |
| } else { |
| if (length == -1) { |
| while (*text != '\0') { |
| guint8 code = (guint8) (*text); |
| |
| switch (code) { |
| case 0x86: /* emphasis on */ |
| case 0x87: /* emphasis off */ |
| /* skip it */ |
| break; |
| case 0x8A: |
| *pos = '\n'; |
| pos += 1; |
| break; |
| default: |
| *pos = *text; |
| pos += 1; |
| break; |
| } |
| |
| text++; |
| } |
| } else { |
| for (i = 0; i < length; i++) { |
| guint8 code = (guint8) (*text); |
| |
| switch (code) { |
| case 0x86: /* emphasis on */ |
| case 0x87: /* emphasis off */ |
| /* skip it */ |
| break; |
| case 0x8A: |
| *pos = '\n'; |
| pos += 1; |
| break; |
| default: |
| *pos = *text; |
| pos += 1; |
| break; |
| } |
| |
| text++; |
| } |
| } |
| } |
| |
| if (pos > tmp) { |
| gsize bread = 0; |
| new_text = |
| g_convert_with_iconv (tmp, pos - tmp, giconv, &bread, NULL, error); |
| GST_DEBUG ("Converted to : %s", new_text); |
| } else { |
| new_text = g_strdup (""); |
| } |
| |
| g_free (tmp); |
| |
| return new_text; |
| } |
| |
| gchar * |
| get_encoding_and_convert (const gchar * text, guint length) |
| { |
| GError *error = NULL; |
| gchar *converted_str; |
| guint start_text = 0; |
| gboolean is_multibyte; |
| LocalIconvCode encoding; |
| GIConv giconv = (GIConv) - 1; |
| |
| g_return_val_if_fail (text != NULL, NULL); |
| |
| if (text == NULL || length == 0) |
| return g_strdup (""); |
| |
| encoding = get_encoding (text, &start_text, &is_multibyte); |
| |
| if (encoding > _ICONV_UNKNOWN && encoding < _ICONV_MAX) { |
| GST_DEBUG ("Encoding %s", iconvtablename[encoding]); |
| giconv = _get_iconv (encoding, _ICONV_UTF8); |
| } else { |
| GST_FIXME ("Could not detect encoding. Returning NULL string"); |
| converted_str = NULL; |
| goto beach; |
| } |
| |
| converted_str = convert_to_utf8 (text, length - start_text, start_text, |
| giconv, is_multibyte, &error); |
| if (error != NULL) { |
| GST_WARNING ("Could not convert string: %s", error->message); |
| g_free (converted_str); |
| g_error_free (error); |
| error = NULL; |
| |
| if (encoding >= _ICONV_ISO8859_2 && encoding <= _ICONV_ISO8859_15) { |
| /* Sometimes using the standard 8859-1 set fixes issues */ |
| GST_DEBUG ("Encoding %s", iconvtablename[_ICONV_ISO8859_1]); |
| giconv = _get_iconv (_ICONV_ISO8859_1, _ICONV_UTF8); |
| |
| GST_INFO ("Trying encoding ISO 8859-1"); |
| converted_str = convert_to_utf8 (text, length, 1, giconv, FALSE, &error); |
| if (error != NULL) { |
| GST_WARNING |
| ("Could not convert string while assuming encoding ISO 8859-1: %s", |
| error->message); |
| g_error_free (error); |
| goto failed; |
| } |
| } else if (encoding == _ICONV_ISO6937) { |
| |
| /* The first part of ISO 6937 is identical to ISO 8859-9, but |
| * they differ in the second part. Some channels don't |
| * provide the first byte that indicates ISO 8859-9 encoding. |
| * If decoding from ISO 6937 failed, we try ISO 8859-9 here. |
| */ |
| giconv = _get_iconv (_ICONV_ISO8859_9, _ICONV_UTF8); |
| |
| GST_INFO ("Trying encoding ISO 8859-9"); |
| converted_str = convert_to_utf8 (text, length, 0, giconv, FALSE, &error); |
| if (error != NULL) { |
| GST_WARNING |
| ("Could not convert string while assuming encoding ISO 8859-9: %s", |
| error->message); |
| g_error_free (error); |
| goto failed; |
| } |
| } else |
| goto failed; |
| } |
| |
| beach: |
| return converted_str; |
| |
| failed: |
| { |
| text += start_text; |
| return g_strndup (text, length - start_text); |
| } |
| } |
| |
| gchar * |
| convert_lang_code (guint8 * data) |
| { |
| gchar *code; |
| /* the iso language code and country code is always 3 byte long */ |
| code = g_malloc0 (4); |
| memcpy (code, data, 3); |
| |
| return code; |
| } |
| |
| void |
| _packetize_descriptor_array (GPtrArray * array, guint8 ** out_data) |
| { |
| guint i; |
| GstMpegtsDescriptor *descriptor; |
| |
| g_return_if_fail (out_data != NULL); |
| g_return_if_fail (*out_data != NULL); |
| |
| if (array == NULL) |
| return; |
| |
| for (i = 0; i < array->len; i++) { |
| descriptor = g_ptr_array_index (array, i); |
| |
| memcpy (*out_data, descriptor->data, descriptor->length + 2); |
| *out_data += descriptor->length + 2; |
| } |
| } |
| |
| GstMpegtsDescriptor * |
| _new_descriptor (guint8 tag, guint8 length) |
| { |
| GstMpegtsDescriptor *descriptor; |
| guint8 *data; |
| |
| descriptor = g_slice_new (GstMpegtsDescriptor); |
| |
| descriptor->tag = tag; |
| descriptor->tag_extension = 0; |
| descriptor->length = length; |
| |
| descriptor->data = g_malloc (length + 2); |
| |
| data = descriptor->data; |
| |
| *data++ = descriptor->tag; |
| *data = descriptor->length; |
| |
| return descriptor; |
| } |
| |
| GstMpegtsDescriptor * |
| _new_descriptor_with_extension (guint8 tag, guint8 tag_extension, guint8 length) |
| { |
| GstMpegtsDescriptor *descriptor; |
| guint8 *data; |
| |
| descriptor = g_slice_new (GstMpegtsDescriptor); |
| |
| descriptor->tag = tag; |
| descriptor->tag_extension = tag_extension; |
| descriptor->length = length + 1; |
| |
| descriptor->data = g_malloc (length + 3); |
| |
| data = descriptor->data; |
| |
| *data++ = descriptor->tag; |
| *data++ = descriptor->length; |
| *data = descriptor->tag_extension; |
| |
| return descriptor; |
| } |
| |
| static GstMpegtsDescriptor * |
| _copy_descriptor (GstMpegtsDescriptor * desc) |
| { |
| GstMpegtsDescriptor *copy; |
| |
| copy = g_slice_dup (GstMpegtsDescriptor, desc); |
| copy->data = g_memdup (desc->data, desc->length + 2); |
| |
| return copy; |
| } |
| |
| /** |
| * gst_mpegts_descriptor_free: |
| * @desc: The descriptor to free |
| * |
| * Frees @desc |
| */ |
| void |
| gst_mpegts_descriptor_free (GstMpegtsDescriptor * desc) |
| { |
| g_free ((gpointer) desc->data); |
| g_slice_free (GstMpegtsDescriptor, desc); |
| } |
| |
| G_DEFINE_BOXED_TYPE (GstMpegtsDescriptor, gst_mpegts_descriptor, |
| (GBoxedCopyFunc) _copy_descriptor, |
| (GBoxedFreeFunc) gst_mpegts_descriptor_free); |
| |
| /** |
| * gst_mpegts_parse_descriptors: |
| * @buffer: (transfer none): descriptors to parse |
| * @buf_len: Size of @buffer |
| * |
| * Parses the descriptors present in @buffer and returns them as an |
| * array. |
| * |
| * Note: The data provided in @buffer will not be copied. |
| * |
| * Returns: (transfer full) (element-type GstMpegtsDescriptor): an |
| * array of the parsed descriptors or %NULL if there was an error. |
| * Release with #g_array_unref when done with it. |
| */ |
| GPtrArray * |
| gst_mpegts_parse_descriptors (guint8 * buffer, gsize buf_len) |
| { |
| GPtrArray *res; |
| guint8 length; |
| guint8 *data; |
| guint i, nb_desc = 0; |
| |
| /* fast-path */ |
| if (buf_len == 0) |
| return g_ptr_array_new (); |
| |
| data = buffer; |
| |
| GST_MEMDUMP ("Full descriptor array", buffer, buf_len); |
| |
| while (data - buffer < buf_len) { |
| data++; /* skip tag */ |
| length = *data++; |
| |
| if (data - buffer > buf_len) { |
| GST_WARNING ("invalid descriptor length %d now at %d max %" |
| G_GSIZE_FORMAT, length, (gint) (data - buffer), buf_len); |
| return NULL; |
| } |
| |
| data += length; |
| nb_desc++; |
| } |
| |
| GST_DEBUG ("Saw %d descriptors, read %" G_GSIZE_FORMAT " bytes", |
| nb_desc, (gsize) (data - buffer)); |
| |
| if (data - buffer != buf_len) { |
| GST_WARNING ("descriptors size %d expected %" G_GSIZE_FORMAT, |
| (gint) (data - buffer), buf_len); |
| return NULL; |
| } |
| |
| res = |
| g_ptr_array_new_full (nb_desc + 1, |
| (GDestroyNotify) gst_mpegts_descriptor_free); |
| |
| data = buffer; |
| |
| for (i = 0; i < nb_desc; i++) { |
| GstMpegtsDescriptor *desc = g_slice_new0 (GstMpegtsDescriptor); |
| |
| desc->data = data; |
| desc->tag = *data++; |
| desc->length = *data++; |
| /* Copy the data now that we known the size */ |
| desc->data = g_memdup (desc->data, desc->length + 2); |
| GST_LOG ("descriptor 0x%02x length:%d", desc->tag, desc->length); |
| GST_MEMDUMP ("descriptor", desc->data + 2, desc->length); |
| /* extended descriptors */ |
| if (G_UNLIKELY (desc->tag == 0x7f)) |
| desc->tag_extension = *data; |
| |
| data += desc->length; |
| |
| /* Set the descriptor in the array */ |
| g_ptr_array_index (res, i) = desc; |
| } |
| |
| res->len = nb_desc; |
| |
| return res; |
| } |
| |
| /** |
| * gst_mpegts_find_descriptor: |
| * @descriptors: (element-type GstMpegtsDescriptor) (transfer none): an array |
| * of #GstMpegtsDescriptor |
| * @tag: the tag to look for |
| * |
| * Finds the first descriptor of type @tag in the array. |
| * |
| * Note: To look for descriptors that can be present more than once in an |
| * array of descriptors, iterate the #GArray manually. |
| * |
| * Returns: (transfer none): the first descriptor matchin @tag, else %NULL. |
| */ |
| const GstMpegtsDescriptor * |
| gst_mpegts_find_descriptor (GPtrArray * descriptors, guint8 tag) |
| { |
| guint i, nb_desc; |
| |
| g_return_val_if_fail (descriptors != NULL, NULL); |
| |
| nb_desc = descriptors->len; |
| for (i = 0; i < nb_desc; i++) { |
| GstMpegtsDescriptor *desc = g_ptr_array_index (descriptors, i); |
| if (desc->tag == tag) |
| return (const GstMpegtsDescriptor *) desc; |
| } |
| return NULL; |
| } |
| |
| /* GST_MTS_DESC_REGISTRATION (0x05) */ |
| /** |
| * gst_mpegts_descriptor_from_registration: |
| * @format_identifier: (transfer none): a 4 character format identifier string |
| * @additional_info: (transfer none) (allow-none): pointer to optional additional info |
| * @additional_info_length: length of the optional @additional_info |
| * |
| * Creates a %GST_MTS_DESC_REGISTRATION #GstMpegtsDescriptor |
| * |
| * Return: #GstMpegtsDescriptor, %NULL on failure |
| */ |
| GstMpegtsDescriptor * |
| gst_mpegts_descriptor_from_registration (const gchar * format_identifier, |
| guint8 * additional_info, gsize additional_info_length) |
| { |
| GstMpegtsDescriptor *descriptor; |
| |
| g_return_val_if_fail (format_identifier != NULL, NULL); |
| |
| descriptor = _new_descriptor (GST_MTS_DESC_REGISTRATION, |
| 4 + additional_info_length); |
| |
| memcpy (descriptor->data + 2, format_identifier, 4); |
| if (additional_info && (additional_info_length > 0)) |
| memcpy (descriptor->data + 6, additional_info, additional_info_length); |
| |
| return descriptor; |
| } |
| |
| /* GST_MTS_DESC_CA (0x09) */ |
| |
| /** |
| * gst_mpegts_descriptor_parse_ca: |
| * @descriptor: a %GST_MTS_DESC_CA #GstMpegtsDescriptor |
| * @ca_system_id: (out): the type of CA system used |
| * @ca_pid: (out): The PID containing ECM or EMM data |
| * @private_data: (out) (allow-none): The private data |
| * @private_data_size: (out) (allow-none): The size of @private_data in bytes |
| * |
| * Extracts the Conditional Access information from @descriptor. |
| * |
| * Returns: %TRUE if parsing succeeded, else %FALSE. |
| */ |
| |
| gboolean |
| gst_mpegts_descriptor_parse_ca (GstMpegtsDescriptor * descriptor, |
| guint16 * ca_system_id, guint16 * ca_pid, |
| const guint8 ** private_data, gsize * private_data_size) |
| { |
| guint8 *data; |
| |
| g_return_val_if_fail (descriptor != NULL && ca_system_id != NULL |
| && ca_pid != NULL, FALSE); |
| /* The smallest CA is 4 bytes (though not having any private data |
| * sounds a bit ... weird) */ |
| __common_desc_checks (descriptor, GST_MTS_DESC_CA, 4, FALSE); |
| |
| data = (guint8 *) descriptor->data + 2; |
| *ca_system_id = GST_READ_UINT16_BE (data); |
| data += 2; |
| *ca_pid = GST_READ_UINT16_BE (data) & 0x1fff; |
| data += 2; |
| if (private_data && private_data_size) { |
| *private_data = data; |
| *private_data_size = descriptor->length - 4; |
| } |
| |
| return TRUE; |
| } |
| |
| /* GST_MTS_DESC_ISO_639_LANGUAGE (0x0A) */ |
| static GstMpegtsISO639LanguageDescriptor * |
| _gst_mpegts_iso_639_language_descriptor_copy (GstMpegtsISO639LanguageDescriptor |
| * source) |
| { |
| GstMpegtsISO639LanguageDescriptor *copy; |
| guint i; |
| |
| copy = g_slice_dup (GstMpegtsISO639LanguageDescriptor, source); |
| |
| for (i = 0; i < source->nb_language; i++) { |
| copy->language[i] = g_strdup (source->language[i]); |
| } |
| |
| return copy; |
| } |
| |
| void |
| gst_mpegts_iso_639_language_descriptor_free (GstMpegtsISO639LanguageDescriptor |
| * desc) |
| { |
| guint i; |
| |
| for (i = 0; i < desc->nb_language; i++) { |
| g_free (desc->language[i]); |
| } |
| g_slice_free (GstMpegtsISO639LanguageDescriptor, desc); |
| } |
| |
| G_DEFINE_BOXED_TYPE (GstMpegtsISO639LanguageDescriptor, |
| gst_mpegts_iso_639_language, |
| (GBoxedCopyFunc) _gst_mpegts_iso_639_language_descriptor_copy, |
| (GFreeFunc) gst_mpegts_iso_639_language_descriptor_free); |
| |
| /** |
| * gst_mpegts_descriptor_parse_iso_639_language: |
| * @descriptor: a %GST_MTS_DESC_ISO_639_LANGUAGE #GstMpegtsDescriptor |
| * @res: (out) (transfer full): the #GstMpegtsISO639LanguageDescriptor to fill |
| * |
| * Extracts the iso 639-2 language information from @descriptor. |
| * |
| * Note: Use #gst_tag_get_language_code if you want to get the the |
| * ISO 639-1 language code from the returned ISO 639-2 one. |
| * |
| * Returns: %TRUE if parsing succeeded, else %FALSE. |
| */ |
| gboolean |
| gst_mpegts_descriptor_parse_iso_639_language (const GstMpegtsDescriptor * |
| descriptor, GstMpegtsISO639LanguageDescriptor ** desc) |
| { |
| guint i; |
| guint8 *data; |
| GstMpegtsISO639LanguageDescriptor *res; |
| |
| g_return_val_if_fail (descriptor != NULL && desc != NULL, FALSE); |
| /* This descriptor can be empty, no size check needed */ |
| __common_desc_check_base (descriptor, GST_MTS_DESC_ISO_639_LANGUAGE, FALSE); |
| |
| data = (guint8 *) descriptor->data + 2; |
| |
| res = g_slice_new0 (GstMpegtsISO639LanguageDescriptor); |
| |
| /* Each language is 3 + 1 bytes */ |
| res->nb_language = descriptor->length / 4; |
| for (i = 0; i < res->nb_language; i++) { |
| res->language[i] = convert_lang_code (data); |
| res->audio_type[i] = data[3]; |
| data += 4; |
| } |
| |
| *desc = res; |
| |
| return TRUE; |
| |
| } |
| |
| /** |
| * gst_mpegts_descriptor_parse_iso_639_language_idx: |
| * @descriptor: a %GST_MTS_DESC_ISO_639_LANGUAGE #GstMpegtsDescriptor |
| * @idx: Table id of the language to parse |
| * @lang: (out) (transfer full): 4-byte gchar array to hold the language code |
| * @audio_type: (out) (transfer none) (allow-none): the #GstMpegtsIso639AudioType to set |
| * |
| * Extracts the iso 639-2 language information from specific table id in @descriptor. |
| * |
| * Note: Use #gst_tag_get_language_code if you want to get the the |
| * ISO 639-1 language code from the returned ISO 639-2 one. |
| * |
| * Returns: %TRUE if parsing succeeded, else %FALSE. |
| */ |
| gboolean |
| gst_mpegts_descriptor_parse_iso_639_language_idx (const GstMpegtsDescriptor * |
| descriptor, guint idx, gchar ** lang, GstMpegtsIso639AudioType * audio_type) |
| { |
| guint8 *data; |
| |
| g_return_val_if_fail (descriptor != NULL && lang != NULL, FALSE); |
| /* This descriptor can be empty, no size check needed */ |
| __common_desc_check_base (descriptor, GST_MTS_DESC_ISO_639_LANGUAGE, FALSE); |
| |
| if (descriptor->length / 4 <= idx) |
| return FALSE; |
| |
| data = (guint8 *) descriptor->data + 2 + idx * 4; |
| |
| *lang = convert_lang_code (data); |
| |
| data += 3; |
| |
| if (audio_type) |
| *audio_type = *data; |
| |
| return TRUE; |
| } |
| |
| /** |
| * gst_mpegts_descriptor_parse_iso_639_language_nb: |
| * @descriptor: a %GST_MTS_DESC_ISO_639_LANGUAGE #GstMpegtsDescriptor |
| * |
| * Returns: The number of languages in @descriptor |
| */ |
| guint |
| gst_mpegts_descriptor_parse_iso_639_language_nb (const GstMpegtsDescriptor * |
| descriptor) |
| { |
| g_return_val_if_fail (descriptor != NULL, 0); |
| /* This descriptor can be empty, no size check needed */ |
| __common_desc_check_base (descriptor, GST_MTS_DESC_ISO_639_LANGUAGE, FALSE); |
| |
| return descriptor->length / 4; |
| } |
| |
| /** |
| * gst_mpegts_descriptor_from_iso_639_language: |
| * @language: (transfer none): ISO-639-2 language 3-char code |
| * |
| * Creates a %GST_MTS_DESC_ISO_639_LANGUAGE #GstMpegtsDescriptor with |
| * a single language |
| * |
| * Return: #GstMpegtsDescriptor, %NULL on failure |
| */ |
| GstMpegtsDescriptor * |
| gst_mpegts_descriptor_from_iso_639_language (const gchar * language) |
| { |
| GstMpegtsDescriptor *descriptor; |
| |
| g_return_val_if_fail (language != NULL, NULL); |
| |
| descriptor = _new_descriptor (GST_MTS_DESC_ISO_639_LANGUAGE, 4 + 4); /* a language takes 4 bytes */ |
| |
| memcpy (descriptor->data + 2, language, 3); |
| descriptor->data[2 + 3] = 0; /* set audio type to undefined */ |
| |
| return descriptor; |
| } |
| |
| /** |
| * gst_mpegts_descriptor_parse_logical_channel: |
| * @descriptor: a %GST_MTS_DESC_DTG_LOGICAL_CHANNEL #GstMpegtsDescriptor |
| * @res: (out) (transfer none): the #GstMpegtsLogicalChannelDescriptor to fill |
| * |
| * Extracts the logical channels from @descriptor. |
| * |
| * Returns: %TRUE if parsing succeeded, else %FALSE. |
| */ |
| gboolean |
| gst_mpegts_descriptor_parse_logical_channel (const GstMpegtsDescriptor * |
| descriptor, GstMpegtsLogicalChannelDescriptor * res) |
| { |
| guint i; |
| guint8 *data; |
| |
| g_return_val_if_fail (descriptor != NULL && res != NULL, FALSE); |
| /* This descriptor loop can be empty, no size check required */ |
| __common_desc_check_base (descriptor, GST_MTS_DESC_DTG_LOGICAL_CHANNEL, |
| FALSE); |
| |
| data = (guint8 *) descriptor->data + 2; |
| |
| res->nb_channels = descriptor->length / 4; |
| |
| for (i = 0; i < res->nb_channels; i++) { |
| res->channels[i].service_id = GST_READ_UINT16_BE (data); |
| data += 2; |
| res->channels[i].visible_service = *data >> 7; |
| res->channels[i].logical_channel_number = |
| GST_READ_UINT16_BE (data) & 0x03ff; |
| data += 2; |
| } |
| |
| return TRUE; |
| } |
| |
| /** |
| * gst_mpegts_descriptor_from_custom: |
| * @tag: descriptor tag |
| * @data: (transfer none): descriptor data (after tag and length field) |
| * @length: length of @data |
| * |
| * Creates a #GstMpegtsDescriptor with custom @tag and @data |
| * |
| * Returns: #GstMpegtsDescriptor |
| */ |
| GstMpegtsDescriptor * |
| gst_mpegts_descriptor_from_custom (guint8 tag, const guint8 * data, |
| gsize length) |
| { |
| GstMpegtsDescriptor *descriptor; |
| |
| descriptor = _new_descriptor (tag, length); |
| |
| if (data && (length > 0)) |
| memcpy (descriptor->data + 2, data, length); |
| |
| return descriptor; |
| } |
| |
| /** |
| * gst_mpegts_descriptor_from_custom_with_extension: |
| * @tag: descriptor tag |
| * @tag_extension: descriptor tag extension |
| * @data: (transfer none): descriptor data (after tag and length field) |
| * @length: length of @data |
| * |
| * Creates a #GstMpegtsDescriptor with custom @tag, @tag_extension and @data |
| * |
| * Returns: #GstMpegtsDescriptor |
| */ |
| GstMpegtsDescriptor * |
| gst_mpegts_descriptor_from_custom_with_extension (guint8 tag, |
| guint8 tag_extension, const guint8 * data, gsize length) |
| { |
| GstMpegtsDescriptor *descriptor; |
| |
| descriptor = _new_descriptor_with_extension (tag, tag_extension, length); |
| |
| if (data && (length > 0)) |
| memcpy (descriptor->data + 3, data, length); |
| |
| return descriptor; |
| } |