/* * Copyright 2006 BBC and Fluendo S.A. * * This library is licensed under 3 different licenses and you * can choose to use it under the terms of any one of them. The * three licenses are the MPL 1.1, the LGPL, and the MIT license. * * MPL: * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/. * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * LGPL: * * 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. * * MIT: * * Unless otherwise indicated, Source Code is licensed under MIT license. * See further explanation attached in License Statement (distributed in the file * LICENSE). * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * SPDX-License-Identifier: MPL-1.1 OR MIT OR LGPL-2.0-or-later */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "tsmuxcommon.h" #include "tsmuxstream.h" #define GST_CAT_DEFAULT gst_base_ts_mux_debug static guint8 tsmux_stream_pes_header_length (TsMuxStream * stream); static void tsmux_stream_write_pes_header (TsMuxStream * stream, guint8 * data); static void tsmux_stream_find_pts_dts_within (TsMuxStream * stream, guint bound, gint64 * pts, gint64 * dts); struct TsMuxStreamBuffer { guint8 *data; guint32 size; /* PTS & DTS associated with the contents of this buffer */ gint64 pts; gint64 dts; /* data represents random access point */ gboolean random_access; /* user_data for release function */ void *user_data; }; /** * tsmux_stream_new: * @pid: a PID * @stream_type: the stream type * * Create a new stream with PID of @pid and @stream_type. * * Returns: a new #TsMuxStream. */ TsMuxStream * tsmux_stream_new (guint16 pid, guint stream_type) { TsMuxStream *stream = g_slice_new0 (TsMuxStream); stream->state = TSMUX_STREAM_STATE_HEADER; stream->pi.pid = pid; stream->stream_type = stream_type; stream->pes_payload_size = 0; stream->cur_pes_payload_size = 0; stream->pes_bytes_written = 0; stream->program_array_index = -1; switch (stream_type) { case TSMUX_ST_VIDEO_MPEG1: case TSMUX_ST_VIDEO_MPEG2: case TSMUX_ST_VIDEO_MPEG4: case TSMUX_ST_VIDEO_H264: case TSMUX_ST_VIDEO_HEVC: /* FIXME: Assign sequential IDs? */ stream->id = 0xE0; stream->pi.flags |= TSMUX_PACKET_FLAG_PES_FULL_HEADER; stream->is_video_stream = TRUE; break; case TSMUX_ST_VIDEO_JP2K: stream->id = 0xBD; stream->pi.flags |= TSMUX_PACKET_FLAG_PES_FULL_HEADER; stream->is_video_stream = TRUE; break; case TSMUX_ST_AUDIO_AAC: case TSMUX_ST_AUDIO_MPEG1: case TSMUX_ST_AUDIO_MPEG2: /* FIXME: Assign sequential IDs? */ stream->is_audio = TRUE; stream->id = 0xC0; stream->pi.flags |= TSMUX_PACKET_FLAG_PES_FULL_HEADER; break; case TSMUX_ST_VIDEO_DIRAC: case TSMUX_ST_PS_AUDIO_LPCM: case TSMUX_ST_PS_AUDIO_AC3: case TSMUX_ST_PS_AUDIO_DTS: stream->id = 0xFD; /* FIXME: assign sequential extended IDs? */ switch (stream_type) { case TSMUX_ST_VIDEO_DIRAC: stream->id_extended = 0x60; stream->is_video_stream = TRUE; break; case TSMUX_ST_PS_AUDIO_LPCM: stream->is_audio = TRUE; stream->id_extended = 0x80; break; case TSMUX_ST_PS_AUDIO_AC3: stream->is_audio = TRUE; stream->id_extended = 0x71; break; case TSMUX_ST_PS_AUDIO_DTS: stream->is_audio = TRUE; stream->id_extended = 0x82; break; default: break; } stream->pi.flags |= TSMUX_PACKET_FLAG_PES_FULL_HEADER | TSMUX_PACKET_FLAG_PES_EXT_STREAMID; break; case TSMUX_ST_PS_TELETEXT: /* needs fixes PES header length */ stream->pi.pes_header_length = 36; /* fall through */ case TSMUX_ST_PS_DVB_SUBPICTURE: /* private stream 1 */ stream->id = 0xBD; stream->is_dvb_sub = TRUE; stream->stream_type = TSMUX_ST_PRIVATE_DATA; stream->pi.flags |= TSMUX_PACKET_FLAG_PES_FULL_HEADER | TSMUX_PACKET_FLAG_PES_DATA_ALIGNMENT; break; case TSMUX_ST_PS_KLV: /* FIXME: assign sequential extended IDs? */ stream->id = 0xBD; stream->stream_type = TSMUX_ST_PRIVATE_DATA; stream->is_meta = TRUE; stream->pi.flags |= TSMUX_PACKET_FLAG_PES_FULL_HEADER | TSMUX_PACKET_FLAG_PES_DATA_ALIGNMENT; break; case TSMUX_ST_PS_OPUS: /* FIXME: assign sequential extended IDs? */ stream->id = 0xBD; stream->is_audio = TRUE; stream->stream_type = TSMUX_ST_PRIVATE_DATA; stream->is_opus = TRUE; stream->pi.flags |= TSMUX_PACKET_FLAG_PES_FULL_HEADER; break; default: /* Might be a custom stream type implemented by a subclass */ break; } stream->first_ts = GST_CLOCK_STIME_NONE; stream->last_pts = GST_CLOCK_STIME_NONE; stream->last_dts = GST_CLOCK_STIME_NONE; stream->pcr_ref = 0; stream->next_pcr = -1; stream->get_es_descrs = (TsMuxStreamGetESDescriptorsFunc) tsmux_stream_default_get_es_descrs; stream->get_es_descrs_data = NULL; return stream; } /** * tsmux_stream_get_pid: * @stream: a #TsMuxStream * * Get the PID of @stream. * * Returns: The PID of @stream. 0xffff on error. */ guint16 tsmux_stream_get_pid (TsMuxStream * stream) { g_return_val_if_fail (stream != NULL, G_MAXUINT16); return stream->pi.pid; } /** * tsmux_stream_free: * @stream: a #TsMuxStream * * Free the resources of @stream. */ void tsmux_stream_free (TsMuxStream * stream) { GList *cur; g_return_if_fail (stream != NULL); /* free buffers */ for (cur = stream->buffers; cur; cur = cur->next) { TsMuxStreamBuffer *tmbuf = (TsMuxStreamBuffer *) cur->data; if (stream->buffer_release) stream->buffer_release (tmbuf->data, tmbuf->user_data); g_slice_free (TsMuxStreamBuffer, tmbuf); } g_list_free (stream->buffers); g_slice_free (TsMuxStream, stream); } /** * tsmux_stream_set_buffer_release_func: * @stream: a #TsMuxStream * @func: the new #TsMuxStreamBufferReleaseFunc * * Set the function that will be called when a a piece of data fed to @stream * with tsmux_stream_add_data() can be freed. @func will be called with user * data as provided with the call to tsmux_stream_add_data(). */ void tsmux_stream_set_buffer_release_func (TsMuxStream * stream, TsMuxStreamBufferReleaseFunc func) { g_return_if_fail (stream != NULL); stream->buffer_release = func; } /** * tsmux_stream_set_get_es_descriptors_func: * @stream: a #TsMuxStream * @func: a user callback function * @user_data: user data passed to @func * * Set the callback function and user data to be called when @stream has * to create Elementary Stream Descriptors. */ void tsmux_stream_set_get_es_descriptors_func (TsMuxStream * stream, TsMuxStreamGetESDescriptorsFunc func, void *user_data) { g_return_if_fail (stream != NULL); stream->get_es_descrs = func; stream->get_es_descrs_data = user_data; } /* Advance the current packet stream position by len bytes. * Mustn't consume more than available in the current packet */ static void tsmux_stream_consume (TsMuxStream * stream, guint len) { g_assert (stream->cur_buffer != NULL); g_assert (len <= stream->cur_buffer->size - stream->cur_buffer_consumed); stream->cur_buffer_consumed += len; stream->bytes_avail -= len; if (stream->cur_buffer_consumed == 0 && stream->cur_buffer->size != 0) return; if (GST_CLOCK_STIME_IS_VALID (stream->cur_buffer->pts)) stream->last_pts = stream->cur_buffer->pts; if (GST_CLOCK_STIME_IS_VALID (stream->cur_buffer->dts)) stream->last_dts = stream->cur_buffer->dts; if (stream->cur_buffer_consumed == stream->cur_buffer->size) { /* Current packet is completed, move along */ stream->buffers = g_list_delete_link (stream->buffers, stream->buffers); if (stream->buffer_release) { stream->buffer_release (stream->cur_buffer->data, stream->cur_buffer->user_data); } g_slice_free (TsMuxStreamBuffer, stream->cur_buffer); stream->cur_buffer = NULL; /* FIXME: As a hack, for unbounded streams, start a new PES packet for each * incoming packet we receive. This assumes that incoming data is * packetised sensibly - ie, every video frame */ if (stream->cur_pes_payload_size == 0) { stream->state = TSMUX_STREAM_STATE_HEADER; stream->pes_bytes_written = 0; } } } /** * tsmux_stream_at_pes_start: * @stream: a #TsMuxStream * * Check if @stream is at the start of a PES packet. * * Returns: TRUE if @stream is at a PES header packet. */ gboolean tsmux_stream_at_pes_start (TsMuxStream * stream) { g_return_val_if_fail (stream != NULL, FALSE); return stream->state == TSMUX_STREAM_STATE_HEADER; } /** * tsmux_stream_bytes_avail: * @stream: a #TsMuxStream * * Calculate how much bytes are available. * * Returns: The number of bytes available. */ static inline gint _tsmux_stream_bytes_avail (TsMuxStream * stream) { gint bytes_avail; g_return_val_if_fail (stream != NULL, 0); if (stream->cur_pes_payload_size != 0) bytes_avail = stream->cur_pes_payload_size - stream->pes_bytes_written; else bytes_avail = stream->bytes_avail; bytes_avail = MIN (bytes_avail, stream->bytes_avail); /* Calculate the number of bytes available in the current PES */ if (stream->state == TSMUX_STREAM_STATE_HEADER) bytes_avail += tsmux_stream_pes_header_length (stream); return bytes_avail; } gint tsmux_stream_bytes_avail (TsMuxStream * stream) { g_return_val_if_fail (stream != NULL, 0); return _tsmux_stream_bytes_avail (stream); } /** * tsmux_stream_bytes_in_buffer: * @stream: a #TsMuxStream * * Calculate how much bytes are in the buffer. * * Returns: The number of bytes in the buffer. */ gint tsmux_stream_bytes_in_buffer (TsMuxStream * stream) { g_return_val_if_fail (stream != NULL, 0); return stream->bytes_avail; } /** * tsmux_stream_initialize_pes_packet: * @stream: a #TsMuxStream * * Initializes the PES packet. * * Returns: TRUE if we the packet was initialized. */ gboolean tsmux_stream_initialize_pes_packet (TsMuxStream * stream) { if (stream->state != TSMUX_STREAM_STATE_HEADER) return TRUE; if (stream->pes_payload_size != 0) { /* Use prescribed fixed PES payload size */ stream->cur_pes_payload_size = stream->pes_payload_size; tsmux_stream_find_pts_dts_within (stream, stream->cur_pes_payload_size, &stream->pts, &stream->dts); } else { /* Output a PES packet of all currently available bytes otherwise */ stream->cur_pes_payload_size = stream->bytes_avail; tsmux_stream_find_pts_dts_within (stream, stream->cur_pes_payload_size, &stream->pts, &stream->dts); } stream->pi.flags &= ~(TSMUX_PACKET_FLAG_PES_WRITE_PTS_DTS | TSMUX_PACKET_FLAG_PES_WRITE_PTS); if (GST_CLOCK_STIME_IS_VALID (stream->pts) && GST_CLOCK_STIME_IS_VALID (stream->dts) && stream->pts != stream->dts) stream->pi.flags |= TSMUX_PACKET_FLAG_PES_WRITE_PTS_DTS; else { if (GST_CLOCK_STIME_IS_VALID (stream->pts)) stream->pi.flags |= TSMUX_PACKET_FLAG_PES_WRITE_PTS; } if (stream->buffers) { TsMuxStreamBuffer *buf = (TsMuxStreamBuffer *) (stream->buffers->data); if (buf->random_access) { stream->pi.flags |= TSMUX_PACKET_FLAG_RANDOM_ACCESS; stream->pi.flags |= TSMUX_PACKET_FLAG_ADAPTATION; } } if (stream->is_video_stream) { guint8 hdr_len; hdr_len = tsmux_stream_pes_header_length (stream); /* Unbounded for video streams if pes packet length is over 16 bit */ if ((stream->cur_pes_payload_size + hdr_len - 6) > G_MAXUINT16) stream->cur_pes_payload_size = 0; } return TRUE; } /** * tsmux_stream_get_data: * @stream: a #TsMuxStream * @buf: a buffer to hold the result * @len: the length of @buf * * Copy up to @len available data in @stream into the buffer @buf. * * Returns: TRUE if @len bytes could be retrieved. */ gboolean tsmux_stream_get_data (TsMuxStream * stream, guint8 * buf, guint len) { g_return_val_if_fail (stream != NULL, FALSE); g_return_val_if_fail (buf != NULL, FALSE); if (stream->state == TSMUX_STREAM_STATE_HEADER) { guint8 pes_hdr_length; pes_hdr_length = tsmux_stream_pes_header_length (stream); /* Submitted buffer must be at least as large as the PES header */ if (len < pes_hdr_length) return FALSE; TS_DEBUG ("Writing PES header of length %u and payload %d", pes_hdr_length, stream->cur_pes_payload_size); tsmux_stream_write_pes_header (stream, buf); len -= pes_hdr_length; buf += pes_hdr_length; stream->state = TSMUX_STREAM_STATE_PACKET; } if (len > (guint) _tsmux_stream_bytes_avail (stream)) return FALSE; stream->pes_bytes_written += len; if (stream->cur_pes_payload_size != 0 && stream->pes_bytes_written == stream->cur_pes_payload_size) { TS_DEBUG ("Finished PES packet"); stream->state = TSMUX_STREAM_STATE_HEADER; stream->pes_bytes_written = 0; } while (len > 0) { guint32 avail; guint8 *cur; if (stream->cur_buffer == NULL) { /* Start next packet */ if (stream->buffers == NULL) return FALSE; stream->cur_buffer = (TsMuxStreamBuffer *) (stream->buffers->data); stream->cur_buffer_consumed = 0; } /* Take as much as we can from the current buffer */ avail = stream->cur_buffer->size - stream->cur_buffer_consumed; cur = stream->cur_buffer->data + stream->cur_buffer_consumed; if (avail < len) { memcpy (buf, cur, avail); tsmux_stream_consume (stream, avail); buf += avail; len -= avail; } else { memcpy (buf, cur, len); tsmux_stream_consume (stream, len); len = 0; } } return TRUE; } static guint8 tsmux_stream_pes_header_length (TsMuxStream * stream) { guint8 packet_len; /* Calculate the length of the header for this stream */ /* start_code prefix + stream_id + pes_packet_length = 6 bytes */ packet_len = 6; if (stream->pi.flags & TSMUX_PACKET_FLAG_PES_FULL_HEADER) { /* For a PES 'full header' we have at least 3 more bytes, * and then more based on flags */ packet_len += 3; if (stream->pi.flags & TSMUX_PACKET_FLAG_PES_WRITE_PTS_DTS) { packet_len += 10; } else if (stream->pi.flags & TSMUX_PACKET_FLAG_PES_WRITE_PTS) { packet_len += 5; } if (stream->pi.flags & TSMUX_PACKET_FLAG_PES_EXT_STREAMID) { /* Need basic extension flags (1 byte), plus 2 more bytes for the * length + extended stream id */ packet_len += 3; } if (stream->pi.pes_header_length) { /* check for consistency, then we can add stuffing */ g_assert (packet_len <= stream->pi.pes_header_length + 6 + 3); packet_len = stream->pi.pes_header_length + 6 + 3; } } return packet_len; } /* Find a PTS/DTS to write into the pes header within the next bound bytes * of the data */ static void tsmux_stream_find_pts_dts_within (TsMuxStream * stream, guint bound, gint64 * pts, gint64 * dts) { GList *cur; *pts = GST_CLOCK_STIME_NONE; *dts = GST_CLOCK_STIME_NONE; for (cur = stream->buffers; cur; cur = cur->next) { TsMuxStreamBuffer *curbuf = cur->data; /* FIXME: This isn't quite correct - if the 'bound' is within this * buffer, we don't know if the timestamp is before or after the split * so we shouldn't return it */ if (bound <= curbuf->size) { *pts = curbuf->pts; *dts = curbuf->dts; return; } /* Have we found a buffer with pts/dts set? */ if (GST_CLOCK_STIME_IS_VALID (curbuf->pts) || GST_CLOCK_STIME_IS_VALID (curbuf->dts)) { *pts = curbuf->pts; *dts = curbuf->dts; return; } bound -= curbuf->size; } } static void tsmux_stream_write_pes_header (TsMuxStream * stream, guint8 * data) { guint16 length_to_write; guint8 hdr_len = tsmux_stream_pes_header_length (stream); guint8 *orig_data = data; /* start_code prefix + stream_id + pes_packet_length = 6 bytes */ data[0] = 0x00; data[1] = 0x00; data[2] = 0x01; data[3] = stream->id; data += 4; /* Write 2 byte PES packet length here. 0 (unbounded) is only * valid for video packets */ if (stream->cur_pes_payload_size != 0) { length_to_write = hdr_len + stream->cur_pes_payload_size - 6; } else { length_to_write = 0; } tsmux_put16 (&data, length_to_write); if (stream->pi.flags & TSMUX_PACKET_FLAG_PES_FULL_HEADER) { guint8 flags = 0; /* Not scrambled, original, not-copyrighted, data_alignment not specified */ flags = 0x81; if (stream->pi.flags & TSMUX_PACKET_FLAG_PES_DATA_ALIGNMENT) flags |= 0x4; *data++ = flags; flags = 0; /* Flags */ if (stream->pi.flags & TSMUX_PACKET_FLAG_PES_WRITE_PTS_DTS) flags |= 0xC0; else if (stream->pi.flags & TSMUX_PACKET_FLAG_PES_WRITE_PTS) flags |= 0x80; if (stream->pi.flags & TSMUX_PACKET_FLAG_PES_EXT_STREAMID) flags |= 0x01; /* Enable PES_extension_flag */ *data++ = flags; /* Header length is the total pes length, * minus the 9 bytes of start codes, flags + hdr_len */ g_return_if_fail (hdr_len >= 9); *data++ = (hdr_len - 9); if (stream->pi.flags & TSMUX_PACKET_FLAG_PES_WRITE_PTS_DTS) { tsmux_put_ts (&data, 0x3, stream->pts); tsmux_put_ts (&data, 0x1, stream->dts); } else if (stream->pi.flags & TSMUX_PACKET_FLAG_PES_WRITE_PTS) { tsmux_put_ts (&data, 0x2, stream->pts); } if (stream->pi.flags & TSMUX_PACKET_FLAG_PES_EXT_STREAMID) { guint8 ext_len; flags = 0x0f; /* (reserved bits) | PES_extension_flag_2 */ *data++ = flags; ext_len = 1; /* Only writing 1 byte into the extended fields */ *data++ = 0x80 | ext_len; /* Write the extended streamID */ *data++ = stream->id_extended; } /* write stuffing bytes if fixed PES header length requested */ if (stream->pi.pes_header_length) while (data < orig_data + stream->pi.pes_header_length + 9) *data++ = 0xff; } } /** * tsmux_stream_add_data: * @stream: a #TsMuxStream * @data: data to add * @len: length of @data * @user_data: user data to pass to release func * @pts: PTS of access unit in @data * @dts: DTS of access unit in @data * @random_access: TRUE if random access point (keyframe) * * Submit @len bytes of @data into @stream. @pts and @dts can be set to the * timestamp (against a 90Hz clock) of the first access unit in @data. A * timestamp of GST_CLOCK_STIME_NONE for @pts or @dts means unknown. * * @user_data will be passed to the release function as set with * tsmux_stream_set_buffer_release_func() when @data can be freed. */ void tsmux_stream_add_data (TsMuxStream * stream, guint8 * data, guint len, void *user_data, gint64 pts, gint64 dts, gboolean random_access) { TsMuxStreamBuffer *packet; g_return_if_fail (stream != NULL); packet = g_slice_new (TsMuxStreamBuffer); packet->data = data; packet->size = len; packet->user_data = user_data; packet->random_access = random_access; packet->pts = pts; packet->dts = dts; if (stream->bytes_avail == 0) { stream->last_pts = pts; stream->last_dts = dts; } stream->bytes_avail += len; stream->buffers = g_list_append (stream->buffers, packet); } /** * tsmux_stream_default_get_es_descrs: * @stream: a #TsMuxStream * @buf: a buffer to hold the ES descriptor * @len: the length used in @buf * * Write an Elementary Stream Descriptor for @stream into @buf. the number of * bytes consumed in @buf will be updated in @len. * * @buf and @len must be at least #TSMUX_MIN_ES_DESC_LEN. */ void tsmux_stream_default_get_es_descrs (TsMuxStream * stream, GstMpegtsPMTStream * pmt_stream) { GstMpegtsDescriptor *descriptor; g_return_if_fail (stream != NULL); g_return_if_fail (pmt_stream != NULL); if (stream->is_audio && stream->language[0] != '\0') { descriptor = gst_mpegts_descriptor_from_iso_639_language (stream->language); g_ptr_array_add (pmt_stream->descriptors, descriptor); descriptor = NULL; } /* Based on the stream type, write out any descriptors to go in the * PMT ES_info field */ /* tag (registration_descriptor), length, format_identifier */ switch (stream->stream_type) { case TSMUX_ST_AUDIO_AAC: /* FIXME */ break; case TSMUX_ST_VIDEO_MPEG4: /* FIXME */ break; case TSMUX_ST_VIDEO_H264: { /* FIXME : Not sure about this additional_identification_info */ guint8 add_info[] = { 0xFF, 0x1B, 0x44, 0x3F }; descriptor = gst_mpegts_descriptor_from_registration ("HDMV", add_info, 4); g_ptr_array_add (pmt_stream->descriptors, descriptor); break; } case TSMUX_ST_VIDEO_DIRAC: descriptor = gst_mpegts_descriptor_from_registration ("drac", NULL, 0); g_ptr_array_add (pmt_stream->descriptors, descriptor); break; case TSMUX_ST_VIDEO_JP2K: { /* J2K video descriptor * descriptor_tag 8 uimsbf * descriptor_length 8 uimsbf * profile_and_level 16 uimsbf * horizontal_size 32 uimsbf * vertical_size 32 uimsbf * max_bit_rate 32 uimsbf * max_buffer_size 32 uimsbf * DEN_frame_rate 16 uimsbf * NUM_frame_rate 16 uimsbf * color_specification 8 bslbf * still_mode 1 bslbf * interlace_video 1 bslbf * reserved 6 bslbf * private_data_byte 8 bslbf */ gint8 still_interlace_reserved = 0x00; int wr_size = 0; guint8 *add_info = NULL; guint8 level = stream->profile_and_level & 0xF; guint32 max_buffer_size = 0; GstByteWriter writer; gst_byte_writer_init_with_size (&writer, 32, FALSE); switch (level) { case 1: case 2: case 3: max_buffer_size = 1250000; break; case 4: max_buffer_size = 2500000; break; case 5: max_buffer_size = 5000000; break; case 6: max_buffer_size = 10000000; break; default: break; } gst_byte_writer_put_uint16_be (&writer, stream->profile_and_level); gst_byte_writer_put_uint32_be (&writer, stream->horizontal_size); gst_byte_writer_put_uint32_be (&writer, stream->vertical_size); gst_byte_writer_put_uint32_be (&writer, max_buffer_size); gst_byte_writer_put_uint32_be (&writer, stream->max_bitrate); gst_byte_writer_put_uint16_be (&writer, stream->den); gst_byte_writer_put_uint16_be (&writer, stream->num); gst_byte_writer_put_uint8 (&writer, stream->color_spec); if (stream->interlace_mode) still_interlace_reserved |= 0x40; gst_byte_writer_put_uint8 (&writer, still_interlace_reserved); gst_byte_writer_put_uint8 (&writer, 0x00); /* private data byte */ wr_size = gst_byte_writer_get_size (&writer); add_info = gst_byte_writer_reset_and_get_data (&writer); descriptor = gst_mpegts_descriptor_from_custom (GST_MTS_DESC_J2K_VIDEO, add_info, wr_size); g_ptr_array_add (pmt_stream->descriptors, descriptor); } break; case TSMUX_ST_PS_AUDIO_AC3: { guint8 add_info[6]; guint8 *pos; pos = add_info; /* audio_stream_descriptor () | ATSC A/52-2001 Annex A * * descriptor_tag 8 uimsbf * descriptor_length 8 uimsbf * sample_rate_code 3 bslbf * bsid 5 bslbf * bit_rate_code 6 bslbf * surround_mode 2 bslbf * bsmod 3 bslbf * num_channels 4 bslbf * full_svc 1 bslbf * langcod 8 bslbf * [...] */ *pos++ = 0x81; *pos++ = 0x04; /* 3 bits sample_rate_code, 5 bits hardcoded bsid (default ver 8) */ switch (stream->audio_sampling) { case 48000: *pos++ = 0x08; break; case 44100: *pos++ = 0x28; break; case 32000: *pos++ = 0x48; break; default: *pos++ = 0xE8; break; /* 48, 44.1 or 32 Khz */ } /* 1 bit bit_rate_limit, 5 bits bit_rate_code, 2 bits suround_mode */ switch (stream->audio_bitrate) { case 32: *pos++ = 0x00 << 2; break; case 40: *pos++ = 0x01 << 2; break; case 48: *pos++ = 0x02 << 2; break; case 56: *pos++ = 0x03 << 2; break; case 64: *pos++ = 0x04 << 2; break; case 80: *pos++ = 0x05 << 2; break; case 96: *pos++ = 0x06 << 2; break; case 112: *pos++ = 0x07 << 2; break; case 128: *pos++ = 0x08 << 2; break; case 160: *pos++ = 0x09 << 2; break; case 192: *pos++ = 0x0A << 2; break; case 224: *pos++ = 0x0B << 2; break; case 256: *pos++ = 0x0C << 2; break; case 320: *pos++ = 0x0D << 2; break; case 384: *pos++ = 0x0E << 2; break; case 448: *pos++ = 0x0F << 2; break; case 512: *pos++ = 0x10 << 2; break; case 576: *pos++ = 0x11 << 2; break; case 640: *pos++ = 0x12 << 2; break; default: *pos++ = 0x32 << 2; break; /* 640 Kb/s upper limit */ } /* 3 bits bsmod, 4 bits num_channels, 1 bit full_svc */ switch (stream->audio_channels) { case 1: *pos++ = 0x01 << 1; break; /* 1/0 */ case 2: *pos++ = 0x02 << 1; break; /* 2/0 */ case 3: *pos++ = 0x0A << 1; break; /* <= 3 */ case 4: *pos++ = 0x0B << 1; break; /* <= 4 */ case 5: *pos++ = 0x0C << 1; break; /* <= 5 */ case 6: default: *pos++ = 0x0D << 1; break; /* <= 6 */ } *pos++ = 0x00; descriptor = gst_mpegts_descriptor_from_registration ("AC-3", add_info, 6); g_ptr_array_add (pmt_stream->descriptors, descriptor); descriptor = gst_mpegts_descriptor_from_custom (GST_MTS_DESC_AC3_AUDIO_STREAM, add_info, 6); g_ptr_array_add (pmt_stream->descriptors, descriptor); break; } case TSMUX_ST_PS_AUDIO_DTS: /* FIXME */ break; case TSMUX_ST_PS_AUDIO_LPCM: /* FIXME */ break; case TSMUX_ST_PS_TELETEXT: /* FIXME empty descriptor for now; * should be provided by upstream in event or so ? */ descriptor = gst_mpegts_descriptor_from_custom (GST_MTS_DESC_DVB_TELETEXT, 0, 1); g_ptr_array_add (pmt_stream->descriptors, descriptor); break; case TSMUX_ST_PS_DVB_SUBPICTURE: /* fallthrough ... * that should never happen anyway as * dvb subtitles are private data */ case TSMUX_ST_PRIVATE_DATA: if (stream->is_dvb_sub) { GST_DEBUG ("Stream language %s", stream->language); /* Simple DVB subtitles with no monitor aspect ratio critical FIXME, how do we make it settable? */ /* Default composition page ID */ /* Default ancillary_page_id */ descriptor = gst_mpegts_descriptor_from_dvb_subtitling (stream->language, 0x10, 0x0001, 0x0152); g_ptr_array_add (pmt_stream->descriptors, descriptor); break; } if (stream->is_opus) { descriptor = gst_mpegts_descriptor_from_registration ("Opus", NULL, 0); g_ptr_array_add (pmt_stream->descriptors, descriptor); descriptor = gst_mpegts_descriptor_from_custom_with_extension (GST_MTS_DESC_DVB_EXTENSION, 0x80, &stream->opus_channel_config_code, 1); g_ptr_array_add (pmt_stream->descriptors, descriptor); } if (stream->is_meta) { descriptor = gst_mpegts_descriptor_from_registration ("KLVA", NULL, 0); GST_DEBUG ("adding KLVA registration descriptor"); g_ptr_array_add (pmt_stream->descriptors, descriptor); } default: break; } } /** * tsmux_stream_get_es_descrs: * @stream: a #TsMuxStream * @buf: a buffer to hold the ES descriptor * @len: the length used in @buf * * Write an Elementary Stream Descriptor for @stream into @buf. the number of * bytes consumed in @buf will be updated in @len. * * @buf and @len must be at least #TSMUX_MIN_ES_DESC_LEN. */ void tsmux_stream_get_es_descrs (TsMuxStream * stream, GstMpegtsPMTStream * pmt_stream) { g_return_if_fail (stream->get_es_descrs != NULL); stream->get_es_descrs (stream, pmt_stream, stream->get_es_descrs_data); } /** * tsmux_stream_pcr_ref: * @stream: a #TsMuxStream * * Mark the stream as being used as the PCR for some program. */ void tsmux_stream_pcr_ref (TsMuxStream * stream) { g_return_if_fail (stream != NULL); stream->pcr_ref++; } /** * tsmux_stream_pcr_unref: * @stream: a #TsMuxStream * * Mark the stream as no longer being used as the PCR for some program. */ void tsmux_stream_pcr_unref (TsMuxStream * stream) { g_return_if_fail (stream != NULL); stream->pcr_ref--; } /** * tsmux_stream_is_pcr: * @stream: a #TsMuxStream * * Check if @stream is used as the PCR for some program. * * Returns: TRUE if the stream is in use as the PCR for some program. */ gboolean tsmux_stream_is_pcr (TsMuxStream * stream) { return stream->pcr_ref != 0; } /** * tsmux_stream_get_pts: * @stream: a #TsMuxStream * * Return the PTS of the last buffer that has had bytes written and * which _had_ a PTS in @stream. * * Returns: the PTS of the last buffer in @stream. */ gint64 tsmux_stream_get_pts (TsMuxStream * stream) { g_return_val_if_fail (stream != NULL, GST_CLOCK_STIME_NONE); return stream->last_pts; } /** * tsmux_stream_get_dts: * @stream: a #TsMuxStream * * Return the DTS of the last buffer that has had bytes written and * which _had_ a DTS in @stream. * * Returns: the DTS of the last buffer in @stream. */ gint64 tsmux_stream_get_dts (TsMuxStream * stream) { g_return_val_if_fail (stream != NULL, GST_CLOCK_STIME_NONE); return stream->last_dts; }