diff options
Diffstat (limited to 'gst/mpegtsmux/tsmux/tsmuxstream.c')
-rw-r--r-- | gst/mpegtsmux/tsmux/tsmuxstream.c | 692 |
1 files changed, 692 insertions, 0 deletions
diff --git a/gst/mpegtsmux/tsmux/tsmuxstream.c b/gst/mpegtsmux/tsmux/tsmuxstream.c new file mode 100644 index 00000000..74b3c82b --- /dev/null +++ b/gst/mpegtsmux/tsmux/tsmuxstream.c @@ -0,0 +1,692 @@ +/* + * Copyright 2006 BBC and Fluendo S.A. + * + * This library is licensed under 4 different licenses and you + * can choose to use it under the terms of any one of them. The + * four licenses are the MPL 1.1, the LGPL, the GPL 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * GPL: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> + +#include "tsmuxcommon.h" +#include "tsmuxstream.h" + +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; + + 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, TsMuxStreamType stream_type) +{ + TsMuxStream *stream = g_new0 (TsMuxStream, 1); + + 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; + + switch (stream_type) { + case TSMUX_ST_VIDEO_MPEG1: + case TSMUX_ST_VIDEO_MPEG2: + case TSMUX_ST_VIDEO_MPEG4: + case TSMUX_ST_VIDEO_H264: + /* 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_AUDIO_AAC: + case TSMUX_ST_AUDIO_MPEG1: + case TSMUX_ST_AUDIO_MPEG2: + /* FIXME: Assign sequential IDs? */ + stream->id = 0xC0; + stream->pi.flags |= TSMUX_PACKET_FLAG_PES_FULL_HEADER; + break; + case TSMUX_ST_VIDEO_DIRAC: + stream->id = 0xFD; + /* FIXME: assign sequential extended IDs? */ + stream->id_extended = 0x60; + + stream->pi.flags |= + TSMUX_PACKET_FLAG_PES_FULL_HEADER | + TSMUX_PACKET_FLAG_PES_EXT_STREAMID; + stream->is_video_stream = TRUE; + break; + default: + g_critical ("Stream type 0x%0x not yet implemented", stream_type); + break; + } + + stream->last_pts = -1; + stream->last_dts = -1; + + stream->pcr_ref = 0; + stream->last_pcr = -1; + + 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) +{ + g_return_if_fail (stream != NULL); + + g_free (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; +} + +/* 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) + return; + + if (stream->cur_buffer->pts != -1) { + stream->last_pts = stream->cur_buffer->pts; + stream->last_dts = stream->cur_buffer->dts; + } else if (stream->cur_buffer->dts != -1) + 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_free (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; + } +} + +/** + * 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. + */ +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 = tsmux_stream_bytes_in_buffer (stream); + + bytes_avail = MIN (bytes_avail, tsmux_stream_bytes_in_buffer (stream)); + + /* 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; +} + +/** + * 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_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; + + 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 if (stream->is_video_stream) { + /* Unbounded for video streams */ + stream->cur_pes_payload_size = 0; + tsmux_stream_find_pts_dts_within (stream, + tsmux_stream_bytes_in_buffer (stream), &stream->pts, &stream->dts); + } else { + /* Output a PES packet of all currently available bytes otherwise */ + stream->cur_pes_payload_size = tsmux_stream_bytes_in_buffer (stream); + 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 (stream->pts != -1 && stream->dts != -1) + stream->pi.flags |= TSMUX_PACKET_FLAG_PES_WRITE_PTS_DTS; + else { + if (stream->pts != -1) + stream->pi.flags |= TSMUX_PACKET_FLAG_PES_WRITE_PTS; + } + + 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; + } + } + + 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 = -1; + *dts = -1; + + for (cur = g_list_first (stream->buffers); cur != NULL; + cur = g_list_next (cur)) { + 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 (curbuf->pts != -1 || curbuf->dts != -1) { + *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); + + /* 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 */ + *data++ = 0x81; + + /* 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++ = 0x80 | stream->id_extended; + } + } +} + +/** + * 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 + * + * 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 -1 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) +{ + TsMuxStreamBuffer *packet; + + g_return_if_fail (stream != NULL); + + packet = g_new (TsMuxStreamBuffer, 1); + packet->data = data; + packet->size = len; + packet->user_data = user_data; + + packet->pts = pts; + packet->dts = dts; + + if (stream->bytes_avail == 0) + stream->last_pts = pts; + + stream->bytes_avail += len; + stream->buffers = g_list_append (stream->buffers, packet); +} + +/** + * 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, guint8 * buf, guint16 * len) +{ + guint8 *pos; + + g_return_if_fail (stream != NULL); + + if (buf == NULL) { + if (len != NULL) + *len = 0; + return; + } + + /* Based on the stream type, write out any descriptors to go in the + * PMT ES_info field */ + pos = buf; + + switch (stream->stream_type) { + case TSMUX_ST_VIDEO_DIRAC: + /* tag (registration_descriptor), length, format_identifier */ + *pos++ = 0x05; + *pos++ = 4; + *pos++ = 0x64; /* 'd' */ + *pos++ = 0x72; /* 'r' */ + *pos++ = 0x61; /* 'a' */ + *pos++ = 0x63; /* 'c' */ + break; + default: + break; + } + + if (len) + *len = (pos - buf); +} + +/** + * 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. + */ +guint64 +tsmux_stream_get_pts (TsMuxStream * stream) +{ + g_return_val_if_fail (stream != NULL, -1); + + return stream->last_pts; +} |