diff options
Diffstat (limited to 'gst/mpegtsmux/tsmux/tsmux.c')
-rw-r--r-- | gst/mpegtsmux/tsmux/tsmux.c | 1056 |
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 = §ion->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); +} |