summaryrefslogtreecommitdiffstats
path: root/gst/mpegtsmux/tsmux/tsmux.c
diff options
context:
space:
mode:
Diffstat (limited to 'gst/mpegtsmux/tsmux/tsmux.c')
-rw-r--r--gst/mpegtsmux/tsmux/tsmux.c1056
1 files changed, 1056 insertions, 0 deletions
diff --git a/gst/mpegtsmux/tsmux/tsmux.c b/gst/mpegtsmux/tsmux/tsmux.c
new file mode 100644
index 00000000..0952f699
--- /dev/null
+++ b/gst/mpegtsmux/tsmux/tsmux.c
@@ -0,0 +1,1056 @@
+/*
+ * 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 "tsmux.h"
+#include "tsmuxstream.h"
+#include "crc.h"
+
+/* Maximum total data length for a PAT section is 1024 bytes, minus an
+ * 8 byte header, then the length of each program entry is 32 bits,
+ * then finally a 32 bit CRC. Thus the maximum number of programs in this mux
+ * is (1024 - 8 - 4) / 4 = 253 because it only supports single section PATs */
+#define TSMUX_MAX_PROGRAMS 253
+
+#define TSMUX_SECTION_HDR_SIZE 8
+
+#define TSMUX_DEFAULT_NETWORK_ID 0x0001
+#define TSMUX_DEFAULT_TS_ID 0x0001
+
+#define TSMUX_START_PROGRAM_ID 0x0001
+#define TSMUX_START_PMT_PID 0x0010
+#define TSMUX_START_ES_PID 0x0040
+
+/* HACK: We use a fixed buffering offset for the PCR at the moment -
+ * this is the amount 'in advance' of the stream that the PCR sits.
+ * 1/8 second atm */
+#define TSMUX_PCR_OFFSET (TSMUX_CLOCK_FREQ / 8)
+
+/* Times per second to write PCR */
+#define TSMUX_DEFAULT_PCR_FREQ (25)
+
+/* PAT frequency (1/10th sec) */
+#define TSMUX_DEFAULT_PAT_FREQ (TSMUX_CLOCK_FREQ / 10)
+/* PMT frequency (1/10th sec) */
+#define TSMUX_DEFAULT_PMT_FREQ (TSMUX_CLOCK_FREQ / 10)
+
+static gboolean tsmux_write_pat (TsMux * mux);
+static gboolean tsmux_write_pmt (TsMux * mux, TsMuxProgram * program);
+
+/**
+ * tsmux_new:
+ *
+ * Create a new muxer session.
+ *
+ * Returns: A new #TsMux object.
+ */
+TsMux *
+tsmux_new ()
+{
+ TsMux *mux;
+
+ mux = g_new0 (TsMux, 1);
+
+ mux->transport_id = TSMUX_DEFAULT_TS_ID;
+
+ mux->next_pgm_no = TSMUX_START_PROGRAM_ID;
+ mux->next_pmt_pid = TSMUX_START_PMT_PID;
+ mux->next_stream_pid = TSMUX_START_ES_PID;
+
+ mux->pat_changed = TRUE;
+ mux->last_pat_ts = -1;
+ mux->pat_frequency = TSMUX_DEFAULT_PAT_FREQ;
+
+ return mux;
+}
+
+/**
+ * tsmux_set_write_func:
+ * @mux: a #TsMux
+ * @func: a user callback function
+ * @user_data: user data passed to @func
+ *
+ * Set the callback function and user data to be called when @mux has output to
+ * produce. @user_data will be passed as user data in @func.
+ */
+void
+tsmux_set_write_func (TsMux * mux, TsMuxWriteFunc func, void *user_data)
+{
+ g_return_if_fail (mux != NULL);
+
+ mux->write_func = func;
+ mux->write_func_data = user_data;
+}
+
+/**
+ * tsmux_set_pat_frequency:
+ * @mux: a #TsMux
+ * @freq: a new PAT frequency
+ *
+ * Set the frequency (against the 90Hz clock) for writing out the PAT table.
+ * A frequency of 0 will only write the PAT table when it changes.
+ *
+ * Many transport stream clients might have problems if the PAT table is not
+ * inserted in the stream at regular intervals, especially when initially trying
+ * to figure out the contents of the stream.
+ */
+void
+tsmux_set_pat_frequency (TsMux * mux, guint freq)
+{
+ g_return_if_fail (mux != NULL);
+
+ mux->pat_frequency = freq;
+}
+
+/**
+ * tsmux_get_pat_frequency:
+ * @mux: a #TsMux
+ *
+ * Get the configured PAT frequency. See also tsmux_set_pat_frequency().
+ *
+ * Returns: the configured PAT frequency
+ */
+guint
+tsmux_get_pat_frequency (TsMux * mux)
+{
+ g_return_val_if_fail (mux != NULL, 0);
+
+ return mux->pat_frequency;
+}
+
+/**
+ * tsmux_free:
+ * @mux: a #TsMux
+ *
+ * Free all resources associated with @mux. After calling this function @mux can
+ * not be used anymore.
+ */
+void
+tsmux_free (TsMux * mux)
+{
+ GList *cur;
+
+ g_return_if_fail (mux != NULL);
+
+ /* Free all programs */
+ for (cur = g_list_first (mux->programs); cur != NULL; cur = g_list_next (cur)) {
+ TsMuxProgram *program = (TsMuxProgram *) cur->data;
+
+ tsmux_program_free (program);
+ }
+ g_list_free (mux->programs);
+
+ /* Free all streams */
+ for (cur = g_list_first (mux->streams); cur != NULL; cur = g_list_next (cur)) {
+ TsMuxStream *stream = (TsMuxStream *) cur->data;
+
+ tsmux_stream_free (stream);
+ }
+ g_list_free (mux->streams);
+
+ g_free (mux);
+}
+
+/**
+ * tsmux_program_new:
+ * @mux: a #TsMux
+ *
+ * Create a new program in the mising session @mux.
+ *
+ * Returns: a new #TsMuxProgram or %NULL when the maximum number of programs has
+ * been reached.
+ */
+TsMuxProgram *
+tsmux_program_new (TsMux * mux)
+{
+ TsMuxProgram *program;
+
+ g_return_val_if_fail (mux != NULL, NULL);
+
+ /* Ensure we have room for another program */
+ if (mux->nb_programs == TSMUX_MAX_PROGRAMS)
+ return NULL;
+
+ program = g_new0 (TsMuxProgram, 1);
+
+ program->pmt_changed = TRUE;
+ program->last_pmt_ts = -1;
+ program->pmt_frequency = TSMUX_DEFAULT_PMT_FREQ;
+
+ program->pgm_number = mux->next_pgm_no++;
+ program->pmt_pid = mux->next_pmt_pid++;
+ program->pcr_stream = NULL;
+ program->last_pcr = -1;
+
+ program->streams = g_array_sized_new (FALSE, TRUE, sizeof (TsMuxStream *), 1);
+
+ mux->programs = g_list_prepend (mux->programs, program);
+ mux->nb_programs++;
+ mux->pat_changed = TRUE;
+
+ return program;
+}
+
+/**
+ * tsmux_set_pmt_frequency:
+ * @program: a #TsMuxProgram
+ * @freq: a new PMT frequency
+ *
+ * Set the frequency (against the 90Hz clock) for writing out the PMT table.
+ * A frequency of 0 will only write the PMT table when it changes.
+ *
+ * Many transport stream clients might have problems if the PMT table is not
+ * inserted in the stream at regular intervals, especially when initially trying
+ * to figure out the contents of the stream.
+ */
+void
+tsmux_set_pmt_frequency (TsMuxProgram * program, guint freq)
+{
+ g_return_if_fail (program != NULL);
+
+ program->pmt_frequency = freq;
+}
+
+/**
+ * tsmux_get_pmt_frequency:
+ * @program: a #TsMuxProgram
+ *
+ * Get the configured PMT frequency. See also tsmux_set_pmt_frequency().
+ *
+ * Returns: the configured PMT frequency
+ */
+guint
+tsmux_get_pmt_frequency (TsMuxProgram * program)
+{
+ g_return_val_if_fail (program != NULL, 0);
+
+ return program->pmt_frequency;
+}
+
+/**
+ * tsmux_program_add_stream:
+ * @program: a #TsMuxProgram
+ * @stream: a #TsMuxStream
+ *
+ * Add @stream to @program.
+ */
+void
+tsmux_program_add_stream (TsMuxProgram * program, TsMuxStream * stream)
+{
+ g_return_if_fail (program != NULL);
+ g_return_if_fail (stream != NULL);
+
+ program->nb_streams++;
+ g_array_append_val (program->streams, stream);
+ program->pmt_changed = TRUE;
+}
+
+/**
+ * tsmux_program_set_pcr_stream:
+ * @program: a #TsMuxProgram
+ * @stream: a #TsMuxStream
+ *
+ * Set @stream as the PCR stream for @program, overwriting the previously
+ * configured PCR stream. When @stream is NULL, program will have no PCR stream
+ * configured.
+ */
+void
+tsmux_program_set_pcr_stream (TsMuxProgram * program, TsMuxStream * stream)
+{
+ g_return_if_fail (program != NULL);
+
+ if (program->pcr_stream == stream)
+ return;
+
+ if (program->pcr_stream != NULL)
+ tsmux_stream_pcr_unref (program->pcr_stream);
+ if (stream)
+ tsmux_stream_pcr_ref (stream);
+ program->pcr_stream = stream;
+
+ program->pmt_changed = TRUE;
+}
+
+/**
+ * tsmux_get_new_pid:
+ * @mux: a #TsMux
+ *
+ * Get a new free PID.
+ *
+ * Returns: a new free PID.
+ */
+guint16
+tsmux_get_new_pid (TsMux * mux)
+{
+ g_return_val_if_fail (mux != NULL, -1);
+
+ /* FIXME: It's possible that this next PID is already taken if a
+ * specific PID was requested earlier. We should find a free PID */
+ return mux->next_stream_pid++;
+}
+
+/**
+ * tsmux_create_stream:
+ * @mux: a #TsMux
+ * @stream_type: a #TsMuxStreamType
+ * @pid: the PID of the new stream.
+ *
+ * Create a new stream of @stream_type in the muxer session @mux.
+ *
+ * When @pid is set to #TSMUX_PID_AUTO, a new free PID will automatically
+ * be allocated for the new stream.
+ *
+ * Returns: a new #TsMuxStream.
+ */
+TsMuxStream *
+tsmux_create_stream (TsMux * mux, TsMuxStreamType stream_type, guint16 pid)
+{
+ TsMuxStream *stream;
+ guint16 new_pid;
+
+ g_return_val_if_fail (mux != NULL, NULL);
+
+ if (pid == TSMUX_PID_AUTO) {
+ new_pid = tsmux_get_new_pid (mux);
+ } else {
+ new_pid = pid & 0x1FFF;
+ }
+
+ /* Ensure we're not creating a PID collision */
+ if (tsmux_find_stream (mux, new_pid))
+ return NULL;
+
+ stream = tsmux_stream_new (new_pid, stream_type);
+
+ mux->streams = g_list_prepend (mux->streams, stream);
+ mux->nb_streams++;
+
+ return stream;
+}
+
+/**
+ * tsmux_find_stream:
+ * @mux: a #TsMux
+ * @pid: the PID to find.
+ *
+ * Find the stream associated wih PID.
+ *
+ * Returns: a #TsMuxStream with @pid or NULL when the stream was not found.
+ */
+TsMuxStream *
+tsmux_find_stream (TsMux * mux, guint16 pid)
+{
+ TsMuxStream *found = NULL;
+ GList *cur;
+
+ g_return_val_if_fail (mux != NULL, NULL);
+
+ for (cur = g_list_first (mux->streams); cur != NULL; cur = g_list_next (cur)) {
+ TsMuxStream *stream = (TsMuxStream *) cur->data;
+
+ if (tsmux_stream_get_pid (stream) == pid) {
+ found = stream;
+ break;
+ }
+ }
+ return found;
+}
+
+static gboolean
+tsmux_packet_out (TsMux * mux)
+{
+ if (G_UNLIKELY (mux->write_func == NULL))
+ return TRUE;
+
+ return mux->write_func (mux->packet_buf, TSMUX_PACKET_LENGTH,
+ mux->write_func_data, mux->new_pcr);
+}
+
+/*
+ * adaptation_field() {
+ * adaptation_field_length 8 uimsbf
+ * if(adaptation_field_length >0) {
+ * discontinuity_indicator 1 bslbf
+ * random_access_indicator 1 bslbf
+ * elementary_stream_priority_indicator 1 bslbf
+ * PCR_flag 1 bslbf
+ * OPCR_flag 1 bslbf
+ * splicing_point_flag 1 bslbf
+ * transport_private_data_flag 1 bslbf
+ * adaptation_field_extension_flag 1 bslbf
+ * if(PCR_flag == '1') {
+ * program_clock_reference_base 33 uimsbf
+ * reserved 6 bslbf
+ * program_clock_reference_extension 9 uimsbf
+ * }
+ * if(OPCR_flag == '1') {
+ * original_program_clock_reference_base 33 uimsbf
+ * reserved 6 bslbf
+ * original_program_clock_reference_extension 9 uimsbf
+ * }
+ * if (splicing_point_flag == '1') {
+ * splice_countdown 8 tcimsbf
+ * }
+ * if(transport_private_data_flag == '1') {
+ * transport_private_data_length 8 uimsbf
+ * for (i=0; i<transport_private_data_length;i++){
+ * private_data_byte 8 bslbf
+ * }
+ * }
+ * if (adaptation_field_extension_flag == '1' ) {
+ * adaptation_field_extension_length 8 uimsbf
+ * ltw_flag 1 bslbf
+ * piecewise_rate_flag 1 bslbf
+ * seamless_splice_flag 1 bslbf
+ * reserved 5 bslbf
+ * if (ltw_flag == '1') {
+ * ltw_valid_flag 1 bslbf
+ * ltw_offset 15 uimsbf
+ * }
+ * if (piecewise_rate_flag == '1') {
+ * reserved 2 bslbf
+ * piecewise_rate 22 uimsbf
+ * }
+ * if (seamless_splice_flag == '1'){
+ * splice_type 4 bslbf
+ * DTS_next_AU[32..30] 3 bslbf
+ * marker_bit 1 bslbf
+ * DTS_next_AU[29..15] 15 bslbf
+ * marker_bit 1 bslbf
+ * DTS_next_AU[14..0] 15 bslbf
+ * marker_bit 1 bslbf
+ * }
+ * for ( i=0;i<N;i++) {
+ * reserved 8 bslbf
+ * }
+ * }
+ * for (i=0;i<N;i++){
+ * stuffing_byte 8 bslbf
+ * }
+ * }
+ * }
+ */
+static gboolean
+tsmux_write_adaptation_field (guint8 * buf,
+ TsMuxPacketInfo * pi, guint8 min_length, guint8 * written)
+{
+ guint8 pos = 2;
+ guint8 flags = 0;
+
+ g_assert (min_length <= TSMUX_PAYLOAD_LENGTH);
+
+ /* Write out all the fields from the packet info only if the
+ * user set the flag to request the adaptation field - if the flag
+ * isn't set, we're just supposed to write stuffing bytes */
+ if (pi->flags & TSMUX_PACKET_FLAG_ADAPTATION) {
+ TS_DEBUG ("writing adaptation fields");
+ if (pi->flags & TSMUX_PACKET_FLAG_DISCONT)
+ flags |= 0x80;
+ if (pi->flags & TSMUX_PACKET_FLAG_RANDOM_ACCESS)
+ flags |= 0x40;
+ if (pi->flags & TSMUX_PACKET_FLAG_PRIORITY)
+ flags |= 0x20;
+ if (pi->flags & TSMUX_PACKET_FLAG_WRITE_PCR) {
+ guint64 pcr_base;
+ guint32 pcr_ext;
+
+ pcr_base = (pi->pcr / 300);
+ pcr_ext = (pi->pcr % 300);
+
+ flags |= 0x10;
+ TS_DEBUG ("Writing PCR %" G_GUINT64_FORMAT " + ext %u", pcr_base,
+ pcr_ext);
+ buf[pos++] = (pcr_base >> 25) & 0xff;
+ buf[pos++] = (pcr_base >> 17) & 0xff;
+ buf[pos++] = (pcr_base >> 9) & 0xff;
+ buf[pos++] = (pcr_base >> 1) & 0xff;
+ buf[pos++] = ((pcr_base << 7) & 0x80) | ((pcr_ext >> 8) & 0x01);
+ buf[pos++] = (pcr_ext) & 0xff;
+ }
+ if (pi->flags & TSMUX_PACKET_FLAG_WRITE_OPCR) {
+ guint64 opcr_base;
+ guint32 opcr_ext;
+
+ opcr_base = (pi->opcr / 300);
+ opcr_ext = (pi->opcr % 300);
+
+ flags |= 0x08;
+ TS_DEBUG ("Writing OPCR");
+ buf[pos++] = (opcr_base >> 25) & 0xff;
+ buf[pos++] = (opcr_base >> 17) & 0xff;
+ buf[pos++] = (opcr_base >> 9) & 0xff;
+ buf[pos++] = (opcr_base >> 1) & 0xff;
+ buf[pos++] = ((opcr_base << 7) & 0x80) | ((opcr_ext >> 8) & 0x01);
+ buf[pos++] = (opcr_ext) & 0xff;
+ }
+ if (pi->flags & TSMUX_PACKET_FLAG_WRITE_SPLICE) {
+ flags |= 0x04;
+ buf[pos++] = pi->splice_countdown;
+ }
+ if (pi->private_data_len > 0) {
+ flags |= 0x02;
+ /* Private data to write, ensure we have enough room */
+ if ((1 + pi->private_data_len) > (TSMUX_PAYLOAD_LENGTH - pos))
+ return FALSE;
+ buf[pos++] = pi->private_data_len;
+ memcpy (&(buf[pos]), pi->private_data, pi->private_data_len);
+ pos += pi->private_data_len;
+ TS_DEBUG ("%u bytes of private data", pi->private_data_len);
+ }
+ if (pi->flags & TSMUX_PACKET_FLAG_WRITE_ADAPT_EXT) {
+ flags |= 0x01;
+ TS_DEBUG ("FIXME: write Adaptation extension");
+ /* Write an empty extension for now */
+ buf[pos++] = 1;
+ buf[pos++] = 0;
+ }
+ }
+ /* Write the flags at the start */
+ buf[1] = flags;
+
+ /* Stuffing bytes if needed */
+ while (pos < min_length)
+ buf[pos++] = 0xff;
+
+ /* Write the adaptation field length, which doesn't include its own byte */
+ buf[0] = pos - 1;
+
+ if (written)
+ *written = pos;
+
+ return TRUE;
+}
+
+static gboolean
+tsmux_write_ts_header (guint8 * buf, TsMuxPacketInfo * pi,
+ guint * payload_len_out, guint * payload_offset_out)
+{
+ guint8 *tmp;
+ guint8 adaptation_flag;
+ guint8 adapt_min_length = 0;
+ guint8 adapt_len;
+ guint payload_len;
+ gboolean write_adapt = FALSE;
+
+ /* Sync byte */
+ buf[0] = TSMUX_SYNC_BYTE;
+
+ TS_DEBUG ("PID 0x%04x, counter = 0x%01x, %u bytes avail", pi->pid,
+ pi->packet_count & 0x0f, pi->stream_avail);
+
+ /* 3 bits:
+ * transport_error_indicator
+ * payload_unit_start_indicator
+ * transport_priority: (00)
+ * 13 bits: PID
+ */
+ tmp = buf + 1;
+ if (pi->packet_start_unit_indicator) {
+ tsmux_put16 (&tmp, 0x4000 | pi->pid);
+ } else
+ tsmux_put16 (&tmp, pi->pid);
+
+ /* 2 bits: scrambling_control (NOT SUPPORTED) (00)
+ * 2 bits: adaptation field control (1x has_adaptation_field | x1 has_payload)
+ * 4 bits: continuity counter (xxxx)
+ */
+ adaptation_flag = pi->packet_count & 0x0f;
+
+ if (pi->flags & TSMUX_PACKET_FLAG_ADAPTATION) {
+ write_adapt = TRUE;
+ }
+
+ if (pi->stream_avail < TSMUX_PAYLOAD_LENGTH) {
+ /* Need an adaptation field regardless for stuffing */
+ adapt_min_length = TSMUX_PAYLOAD_LENGTH - pi->stream_avail;
+ write_adapt = TRUE;
+ }
+
+ if (write_adapt) {
+ gboolean res;
+
+ /* Flag the adaptation field presence */
+ adaptation_flag |= 0x20;
+ res = tsmux_write_adaptation_field (buf + TSMUX_HEADER_LENGTH,
+ pi, adapt_min_length, &adapt_len);
+ if (G_UNLIKELY (res == FALSE))
+ return FALSE;
+
+ /* Should have written at least the number of bytes we requested */
+ g_assert (adapt_len >= adapt_min_length);
+ } else {
+ adapt_len = 0;
+ }
+
+ /* The amount of packet data we wrote is the remaining space after
+ * the adaptation field */
+ *payload_len_out = payload_len = TSMUX_PAYLOAD_LENGTH - adapt_len;
+ *payload_offset_out = TSMUX_HEADER_LENGTH + adapt_len;
+
+ /* Now if we are going to write out some payload, flag that fact */
+ if (payload_len > 0 && pi->stream_avail > 0) {
+ /* Flag the presence of a payload */
+ adaptation_flag |= 0x10;
+
+ /* We must have enough data to fill the payload, or some calculation
+ * went wrong */
+ g_assert (payload_len <= pi->stream_avail);
+
+ /* Packet with payload, increment the continuity counter */
+ pi->packet_count++;
+ }
+
+ /* Write the byte of transport_scrambling_control, adaptation_field_control
+ * + continuity counter out */
+ buf[3] = adaptation_flag;
+
+
+ if (write_adapt) {
+ TS_DEBUG ("Adaptation field of size >= %d + %d bytes payload",
+ adapt_len, payload_len);
+ } else {
+ TS_DEBUG ("Payload of %d bytes only", payload_len);
+ }
+
+ return TRUE;
+}
+
+/**
+ * tsmux_write_stream_packet:
+ * @mux: a #TsMux
+ * @stream: a #TsMuxStream
+ *
+ * Write a packet of @stream.
+ *
+ * Returns: TRUE if the packet could be written.
+ */
+gboolean
+tsmux_write_stream_packet (TsMux * mux, TsMuxStream * stream)
+{
+ guint payload_len, payload_offs;
+ TsMuxPacketInfo *pi = &stream->pi;
+ gboolean res;
+
+
+ mux->new_pcr = -1;
+ g_return_val_if_fail (mux != NULL, FALSE);
+ g_return_val_if_fail (stream != NULL, FALSE);
+
+ if (tsmux_stream_is_pcr (stream)) {
+ gint64 cur_pcr = 0;
+ gint64 cur_pts = tsmux_stream_get_pts (stream);
+ gboolean write_pat;
+ GList *cur;
+
+ if (cur_pts != -1) {
+ TS_DEBUG ("TS for PCR stream is %" G_GINT64_FORMAT, cur_pts);
+ }
+
+ /* FIXME: The current PCR needs more careful calculation than just
+ * writing a fixed offset */
+ if (cur_pts != -1 && (cur_pts >= TSMUX_PCR_OFFSET))
+ cur_pcr = (cur_pts - TSMUX_PCR_OFFSET) *
+ (TSMUX_SYS_CLOCK_FREQ / TSMUX_CLOCK_FREQ);
+
+ /* Need to decide whether to write a new PCR in this packet */
+ if (stream->last_pcr == -1 ||
+ (cur_pcr - stream->last_pcr >
+ (TSMUX_CLOCK_FREQ / TSMUX_DEFAULT_PCR_FREQ))) {
+
+ stream->pi.flags |=
+ TSMUX_PACKET_FLAG_ADAPTATION | TSMUX_PACKET_FLAG_WRITE_PCR;
+ stream->pi.pcr = cur_pcr;
+ stream->last_pcr = cur_pcr;
+ mux->new_pcr = cur_pcr;
+ }
+
+ /* check if we need to rewrite pat */
+ if (mux->last_pat_ts == -1 || mux->pat_changed)
+ write_pat = TRUE;
+ else if (cur_pcr >= mux->last_pat_ts + mux->pat_frequency)
+ write_pat = TRUE;
+ else
+ write_pat = FALSE;
+
+ if (write_pat) {
+ mux->last_pat_ts = cur_pcr;
+ if (!tsmux_write_pat (mux))
+ return FALSE;
+ }
+
+ /* check if we need to rewrite any of the current pmts */
+ for (cur = g_list_first (mux->programs); cur != NULL;
+ cur = g_list_next (cur)) {
+ TsMuxProgram *program = (TsMuxProgram *) cur->data;
+ gboolean write_pmt;
+
+ if (program->last_pmt_ts == -1 || program->pmt_changed)
+ write_pmt = TRUE;
+ else if (cur_pcr >= program->last_pmt_ts + program->pmt_frequency)
+ write_pmt = TRUE;
+ else
+ write_pmt = FALSE;
+
+ if (write_pmt) {
+ program->last_pmt_ts = cur_pcr;
+ if (!tsmux_write_pmt (mux, program))
+ return FALSE;
+ }
+ }
+ }
+
+ pi->stream_avail = tsmux_stream_bytes_avail (stream);
+ pi->packet_start_unit_indicator = tsmux_stream_at_pes_start (stream);
+
+ if (!tsmux_write_ts_header (mux->packet_buf, pi, &payload_len, &payload_offs))
+ return FALSE;
+
+ if (!tsmux_stream_get_data (stream, mux->packet_buf + payload_offs,
+ payload_len))
+ return FALSE;
+
+ res = tsmux_packet_out (mux);
+
+ /* Reset all dynamic flags */
+ stream->pi.flags &= TSMUX_PACKET_FLAG_PES_FULL_HEADER;
+
+ return res;
+}
+
+/**
+ * tsmux_program_free:
+ * @program: a #TsMuxProgram
+ *
+ * Free the resources of @program. After this call @program can not be used
+ * anymore.
+ */
+void
+tsmux_program_free (TsMuxProgram * program)
+{
+ g_return_if_fail (program != NULL);
+
+ g_array_free (program->streams, TRUE);
+ g_free (program);
+}
+
+static gboolean
+tsmux_write_section (TsMux * mux, TsMuxSection * section)
+{
+ guint8 *cur_in;
+ guint payload_remain;
+ guint payload_len, payload_offs;
+ TsMuxPacketInfo *pi;
+
+ pi = &section->pi;
+
+ pi->packet_start_unit_indicator = TRUE;
+
+ cur_in = section->data;
+ payload_remain = pi->stream_avail;
+
+ while (payload_remain > 0) {
+ if (pi->packet_start_unit_indicator) {
+ /* Need to write an extra single byte start pointer */
+ pi->stream_avail++;
+
+ if (!tsmux_write_ts_header (mux->packet_buf, pi,
+ &payload_len, &payload_offs)) {
+ pi->stream_avail--;
+ return FALSE;
+ }
+ pi->stream_avail--;
+
+ /* Write the pointer byte */
+ mux->packet_buf[payload_offs] = 0x00;
+
+ payload_offs++;
+ payload_len--;
+ pi->packet_start_unit_indicator = FALSE;
+ } else {
+ if (!tsmux_write_ts_header (mux->packet_buf, pi,
+ &payload_len, &payload_offs))
+ return FALSE;
+ }
+
+ TS_DEBUG ("Outputting %d bytes to section. %d remaining after",
+ payload_len, payload_remain - payload_len);
+
+ memcpy (mux->packet_buf + payload_offs, cur_in, payload_len);
+
+ cur_in += payload_len;
+ payload_remain -= payload_len;
+
+ if (G_UNLIKELY (!tsmux_packet_out (mux))) {
+ mux->new_pcr = -1;
+ return FALSE;
+ }
+ mux->new_pcr = -1;
+ }
+
+ return TRUE;
+}
+
+static void
+tsmux_write_section_hdr (guint8 * pos, guint8 table_id, guint16 len,
+ guint16 id, guint8 version, guint8 section_nr, guint8 last_section_nr)
+{
+ /* The length passed is the total length of the section, but we're not
+ * supposed to include the first 3 bytes of the header in the count */
+ len -= 3;
+
+ /* 1 byte table identifier */
+ *pos++ = table_id;
+ /* section_syntax_indicator = '0' | '0' | '11' reserved bits | (len >> 8) */
+ tsmux_put16 (&pos, 0xB000 | len);
+ /* 2 bytes transport/program id */
+ tsmux_put16 (&pos, id);
+
+ /* '11' reserved | version 'xxxxxx' | 'x' current_next */
+ *pos++ = 0xC0 | ((version & 0x1F) << 1) | 0x01;
+ *pos++ = section_nr;
+ *pos++ = last_section_nr;
+}
+
+static gboolean
+tsmux_write_pat (TsMux * mux)
+{
+ GList *cur;
+ TsMuxSection *pat = &mux->pat;
+
+ if (mux->pat_changed) {
+ /* program_association_section ()
+ * table_id 8 uimsbf
+ * section_syntax_indicator 1 bslbf
+ * '0' 1 bslbf
+ * reserved 2 bslbf
+ * section_length 12 uimsbf
+ * transport_stream_id 16 uimsbf
+ * reserved 2 bslbf
+ * version_number 5 uimsbf
+ * current_next_indicator 1 bslbf
+ * section_number 8 uimsbf
+ * last_section_number 8 uimsbf
+ * for (i = 0; i < N; i++) {
+ * program_number 16 uimsbf
+ * reserved 3 bslbf
+ * network_PID_or_program_map_PID 13 uimbsf
+ * }
+ * CRC_32 32 rbchof
+ */
+ guint8 *pos;
+ guint32 crc;
+
+ /* Prepare the section data after the section header */
+ pos = pat->data + TSMUX_SECTION_HDR_SIZE;
+
+ for (cur = g_list_first (mux->programs); cur != NULL;
+ cur = g_list_next (cur)) {
+ TsMuxProgram *program = (TsMuxProgram *) cur->data;
+
+ tsmux_put16 (&pos, program->pgm_number);
+ tsmux_put16 (&pos, 0xE000 | program->pmt_pid);
+ }
+
+ /* Measure the section length, include extra 4 bytes for CRC below */
+ pat->pi.stream_avail = pos - pat->data + 4;
+
+ /* Go back and write the header now that we know the final length.
+ * table_id = 0 for PAT */
+ tsmux_write_section_hdr (pat->data, 0x00, pat->pi.stream_avail,
+ mux->transport_id, mux->pat_version, 0, 0);
+
+ /* Calc and output CRC for data bytes, not including itself */
+ crc = calc_crc32 (pat->data, pat->pi.stream_avail - 4);
+ tsmux_put32 (&pos, crc);
+
+ TS_DEBUG ("PAT has %d programs, is %u bytes",
+ mux->nb_programs, pat->pi.stream_avail);
+ mux->pat_changed = FALSE;
+ mux->pat_version++;
+ }
+
+ return tsmux_write_section (mux, pat);
+}
+
+static gboolean
+tsmux_write_pmt (TsMux * mux, TsMuxProgram * program)
+{
+ TsMuxSection *pmt = &program->pmt;
+
+ if (program->pmt_changed) {
+ /* program_association_section ()
+ * table_id 8 uimsbf
+ * section_syntax_indicator 1 bslbf
+ * '0' 1 bslbf
+ * reserved 2 bslbf
+ * section_length 12 uimsbf
+ * program_id 16 uimsbf
+ * reserved 2 bslbf
+ * version_number 5 uimsbf
+ * current_next_indicator 1 bslbf
+ * section_number 8 uimsbf
+ * last_section_number 8 uimsbf
+ * reserved 3 bslbf
+ * PCR_PID 13 uimsbf
+ * reserved 4 bslbf
+ * program_info_length 12 uimsbf
+ * for (i = 0; i < N; i++)
+ * descriptor ()
+ *
+ * for (i = 0; i < N1; i++) {
+ * stream_type 8 uimsbf
+ * reserved 3 bslbf
+ * elementary_PID 13 uimbsf
+ * reserved 4 bslbf
+ * ES_info_length 12 uimbsf
+ * for (i = 0; i < N1; i++) {
+ * descriptor ();
+ * }
+ * }
+ * CRC_32 32 rbchof
+ */
+ guint8 *pos;
+ guint32 crc;
+ guint i;
+
+ /* Prepare the section data after the basic section header */
+ pos = pmt->data + TSMUX_SECTION_HDR_SIZE;
+
+ if (program->pcr_stream == NULL)
+ tsmux_put16 (&pos, 0xFFFF);
+ else
+ tsmux_put16 (&pos, 0xE000 | tsmux_stream_get_pid (program->pcr_stream));
+
+ /* FIXME: Write program descriptors if needed, for now write a
+ * length of 0 */
+ tsmux_put16 (&pos, 0xF000);
+
+ /* Write out the entries */
+ for (i = 0; i < program->nb_streams; i++) {
+ TsMuxStream *stream = g_array_index (program->streams, TsMuxStream *, i);
+ guint16 es_info_len;
+
+ /* FIXME: Use API to retrieve this from the stream */
+ *pos++ = stream->stream_type;
+ tsmux_put16 (&pos, 0xE000 | tsmux_stream_get_pid (stream));
+
+ /* Write any ES descriptors needed */
+ tsmux_stream_get_es_descrs (stream, mux->es_info_buf, &es_info_len);
+ tsmux_put16 (&pos, 0xF000 | es_info_len);
+
+ if (es_info_len > 0) {
+ TS_DEBUG ("Writing descriptor of len %d for PID 0x%04x",
+ es_info_len, tsmux_stream_get_pid (stream));
+ if (G_UNLIKELY (pos + es_info_len >=
+ pmt->data + TSMUX_MAX_SECTION_LENGTH))
+ return FALSE;
+
+ memcpy (pos, mux->es_info_buf, es_info_len);
+ pos += es_info_len;
+ }
+ }
+
+ /* Include the CRC in the byte count */
+ pmt->pi.stream_avail = pos - pmt->data + 4;
+
+ /* Go back and patch the pmt_header now that we know the length.
+ * table_id = 2 for PMT */
+ tsmux_write_section_hdr (pmt->data, 0x02, pmt->pi.stream_avail,
+ program->pgm_number, program->pmt_version, 0, 0);
+
+ /* Calc and output CRC for data bytes,
+ * but not counting the CRC bytes this time */
+ crc = calc_crc32 (pmt->data, pmt->pi.stream_avail - 4);
+ tsmux_put32 (&pos, crc);
+
+ TS_DEBUG ("PMT for program %d has %d streams, is %u bytes",
+ program->pgm_number, program->nb_streams, pmt->pi.stream_avail);
+
+ pmt->pi.pid = program->pmt_pid;
+ program->pmt_changed = FALSE;
+ program->pmt_version++;
+ }
+
+ return tsmux_write_section (mux, pmt);
+}