/* 
 * 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., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, 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., 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.
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>

#include <gst/mpegts/mpegts.h>

#include "tsmux.h"
#include "tsmuxstream.h"

#define GST_CAT_DEFAULT mpegtsmux_debug

/* 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

/* 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)

/* Base for all written PCR and DTS/PTS,
 * so we have some slack to go backwards */
#define CLOCK_BASE (TSMUX_CLOCK_FREQ * 10 * 360)

static gboolean tsmux_write_pat (TsMux * mux);
static gboolean tsmux_write_pmt (TsMux * mux, TsMuxProgram * program);
static void
tsmux_section_free (TsMuxSection * section)
{
  gst_mpegts_section_unref (section->section);
  g_slice_free (TsMuxSection, section);
}

/**
 * tsmux_new:
 *
 * Create a new muxer session.
 *
 * Returns: A new #TsMux object.
 */
TsMux *
tsmux_new (void)
{
  TsMux *mux;

  mux = g_slice_new0 (TsMux);

  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 = G_MININT64;
  mux->pat_interval = TSMUX_DEFAULT_PAT_INTERVAL;

  mux->si_changed = TRUE;
  mux->last_si_ts = G_MININT64;
  mux->si_interval = TSMUX_DEFAULT_SI_INTERVAL;

  mux->si_sections = g_hash_table_new_full (g_direct_hash, g_direct_equal,
      NULL, (GDestroyNotify) tsmux_section_free);

  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_alloc_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 needs
 * a new buffer to write a packet into.
 * @user_data will be passed as user data in @func.
 */
void
tsmux_set_alloc_func (TsMux * mux, TsMuxAllocFunc func, void *user_data)
{
  g_return_if_fail (mux != NULL);

  mux->alloc_func = func;
  mux->alloc_func_data = user_data;
}

/**
 * tsmux_set_pat_interval:
 * @mux: a #TsMux
 * @freq: a new PAT interval
 *
 * Set the interval (in cycles of the 90kHz clock) for writing out the PAT table.
 *
 * 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_interval (TsMux * mux, guint freq)
{
  g_return_if_fail (mux != NULL);

  mux->pat_interval = freq;
}

/**
 * tsmux_get_pat_interval:
 * @mux: a #TsMux
 *
 * Get the configured PAT interval. See also tsmux_set_pat_interval().
 *
 * Returns: the configured PAT interval
 */
guint
tsmux_get_pat_interval (TsMux * mux)
{
  g_return_val_if_fail (mux != NULL, 0);

  return mux->pat_interval;
}

/**
 * tsmux_resend_pat:
 * @mux: a #TsMux
 *
 * Resends the PAT before the next stream packet.
 */
void
tsmux_resend_pat (TsMux * mux)
{
  g_return_if_fail (mux != NULL);

  mux->last_pat_ts = G_MININT64;
}

/**
 * tsmux_set_si_interval:
 * @mux: a #TsMux
 * @freq: a new SI table interval
 *
 * Set the interval (in cycles of the 90kHz clock) for writing out the SI tables.
 *
 */
void
tsmux_set_si_interval (TsMux * mux, guint freq)
{
  g_return_if_fail (mux != NULL);

  mux->si_interval = freq;
}

/**
 * tsmux_get_si_interval:
 * @mux: a #TsMux
 *
 * Get the configured SI table interval. See also tsmux_set_si_interval().
 *
 * Returns: the configured SI interval
 */
guint
tsmux_get_si_interval (TsMux * mux)
{
  g_return_val_if_fail (mux != NULL, 0);

  return mux->si_interval;
}

/**
 * tsmux_resend_si:
 * @mux: a #TsMux
 *
 * Resends the SI tables before the next stream packet.
 *
 */
void
tsmux_resend_si (TsMux * mux)
{
  g_return_if_fail (mux != NULL);

  mux->last_si_ts = G_MININT64;
}

/**
 * tsmux_add_mpegts_si_section:
 * @mux: a #TsMux
 * @section: (transfer full): a #GstMpegtsSection to add
 *
 * Add a Service Information #GstMpegtsSection to the stream
 *
 * Returns: %TRUE on success, %FALSE otherwise
 */
gboolean
tsmux_add_mpegts_si_section (TsMux * mux, GstMpegtsSection * section)
{
  TsMuxSection *tsmux_section;

  g_return_val_if_fail (mux != NULL, FALSE);
  g_return_val_if_fail (section != NULL, FALSE);
  g_return_val_if_fail (mux->si_sections != NULL, FALSE);

  tsmux_section = g_slice_new0 (TsMuxSection);

  GST_DEBUG ("Adding mpegts section with type %d to mux",
      section->section_type);

  tsmux_section->section = section;
  tsmux_section->pi.pid = section->pid;

  g_hash_table_insert (mux->si_sections,
      GINT_TO_POINTER (section->section_type), tsmux_section);

  mux->si_changed = TRUE;

  return TRUE;
}

/**
 * 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 PAT section */
  if (mux->pat.section)
    gst_mpegts_section_unref (mux->pat.section);

  /* Free all programs */
  for (cur = mux->programs; cur; cur = cur->next) {
    TsMuxProgram *program = (TsMuxProgram *) cur->data;

    tsmux_program_free (program);
  }
  g_list_free (mux->programs);

  /* Free all streams */
  for (cur = mux->streams; cur; cur = cur->next) {
    TsMuxStream *stream = (TsMuxStream *) cur->data;

    tsmux_stream_free (stream);
  }
  g_list_free (mux->streams);

  /* Free SI table sections */
  g_hash_table_destroy (mux->si_sections);

  g_slice_free (TsMux, mux);
}

static gint
tsmux_program_compare (TsMuxProgram * program, gint * needle)
{
  return (program->pgm_number - *needle);
}

/**
 * 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, gint prog_id)
{
  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_slice_new0 (TsMuxProgram);

  program->pmt_changed = TRUE;
  program->last_pmt_ts = G_MININT64;
  program->pmt_interval = TSMUX_DEFAULT_PMT_INTERVAL;

  if (prog_id == 0) {
    program->pgm_number = mux->next_pgm_no++;
    while (g_list_find_custom (mux->programs, &program->pgm_number,
            (GCompareFunc) tsmux_program_compare) != NULL) {
      program->pgm_number = mux->next_pgm_no++;
    }
  } else {
    program->pgm_number = prog_id;
    while (g_list_find_custom (mux->programs, &program->pgm_number,
            (GCompareFunc) tsmux_program_compare) != NULL) {
      program->pgm_number++;
    }
  }

  program->pmt_pid = mux->next_pmt_pid++;
  program->pcr_stream = NULL;

  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_interval:
 * @program: a #TsMuxProgram
 * @freq: a new PMT interval
 *
 * Set the interval (in cycles of the 90kHz clock) for writing out the PMT table.
 *
 * 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_interval (TsMuxProgram * program, guint freq)
{
  g_return_if_fail (program != NULL);

  program->pmt_interval = freq;
}

/**
 * tsmux_get_pmt_interval:
 * @program: a #TsMuxProgram
 *
 * Get the configured PMT interval. See also tsmux_set_pmt_interval().
 *
 * Returns: the configured PMT interval
 */
guint
tsmux_get_pmt_interval (TsMuxProgram * program)
{
  g_return_val_if_fail (program != NULL, 0);

  return program->pmt_interval;
}

/**
 * tsmux_resend_pmt:
 * @program: a #TsMuxProgram
 *
 * Resends the PMT before the next stream packet.
 */
void
tsmux_resend_pmt (TsMuxProgram * program)
{
  g_return_if_fail (program != NULL);

  program->last_pmt_ts = G_MININT64;
}

/**
 * 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);

  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);

  /* make sure this PID is free
   * (and not taken by a specific earlier request) */
  do {
    mux->next_stream_pid++;
  } while (tsmux_find_stream (mux, mux->next_stream_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,
    gchar * language)
{
  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++;

  if (language)
    g_strlcat (stream->language, language, 3 * sizeof (gchar));
  else
    g_strlcat (stream->language, "eng", 3 * sizeof (gchar));

  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 = mux->streams; cur; cur = cur->next) {
    TsMuxStream *stream = (TsMuxStream *) cur->data;

    if (tsmux_stream_get_pid (stream) == pid) {
      found = stream;
      break;
    }
  }
  return found;
}

static gboolean
tsmux_get_buffer (TsMux * mux, GstBuffer ** buf)
{
  g_return_val_if_fail (buf, FALSE);

  if (G_UNLIKELY (!mux->alloc_func))
    return FALSE;

  mux->alloc_func (buf, mux->alloc_func_data);

  if (!*buf)
    return FALSE;

  g_assert (gst_buffer_get_size (*buf) == TSMUX_PACKET_LENGTH);
  return TRUE;
}

static gboolean
tsmux_packet_out (TsMux * mux, GstBuffer * buf, gint64 pcr)
{
  if (G_UNLIKELY (mux->write_func == NULL)) {
    if (buf)
      gst_buffer_unref (buf);
    return TRUE;
  }

  return mux->write_func (buf, mux->write_func_data, 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) | 0x7e | ((pcr_ext >> 8) & 0x01);   /* set 6 reserve bits to 1 */
      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) | 0x7e | ((opcr_ext >> 8) & 0x01); /* set 6 reserve bits to 1 */
      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++] = 0x1f;        /* lower 5 bits are reserved, and should be all 1 */
    }
  }
  /* 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 = 0;
  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);
  }

  /* 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;
}

static gboolean
tsmux_section_write_packet (GstMpegtsSectionType * type,
    TsMuxSection * section, TsMux * mux)
{
  GstBuffer *section_buffer;
  GstBuffer *packet_buffer = NULL;
  GstMemory *mem;
  guint8 *packet;
  guint8 *data;
  gsize data_size = 0;
  gsize payload_written;
  guint len = 0, offset = 0, payload_len = 0;
  guint extra_alloc_bytes = 0;

  g_return_val_if_fail (section != NULL, FALSE);
  g_return_val_if_fail (mux != NULL, FALSE);

  /* Mark the start of new PES unit */
  section->pi.packet_start_unit_indicator = TRUE;

  data = gst_mpegts_section_packetize (section->section, &data_size);

  if (!data) {
    TS_DEBUG ("Could not packetize section");
    return FALSE;
  }

  /* Mark payload data size */
  section->pi.stream_avail = data_size;
  payload_written = 0;

  /* Wrap section data in a buffer without free function.
     The data will be freed when the GstMpegtsSection is destroyed. */
  section_buffer = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY,
      data, data_size, 0, data_size, NULL, NULL);

  TS_DEBUG ("Section buffer with size %" G_GSIZE_FORMAT " created",
      gst_buffer_get_size (section_buffer));

  while (section->pi.stream_avail > 0) {

    packet = g_malloc (TSMUX_PACKET_LENGTH);

    if (section->pi.packet_start_unit_indicator) {
      /* Wee need room for a pointer byte */
      section->pi.stream_avail++;

      if (!tsmux_write_ts_header (packet, &section->pi, &len, &offset))
        goto fail;

      /* Write the pointer byte */
      packet[offset++] = 0x00;
      payload_len = len - 1;

    } else {
      if (!tsmux_write_ts_header (packet, &section->pi, &len, &offset))
        goto fail;
      payload_len = len;
    }

    /* Wrap the TS header and adaption field in a GstMemory */
    mem = gst_memory_new_wrapped (GST_MEMORY_FLAG_READONLY,
        packet, TSMUX_PACKET_LENGTH, 0, offset, packet, g_free);

    TS_DEBUG ("Creating packet buffer at offset "
        "%" G_GSIZE_FORMAT " with length %u", payload_written, payload_len);

    /* If in M2TS mode, we will need to resize to 4 bytes after the end
       of the buffer. For performance reasons, we will now try to include
       4 extra bytes from the source buffer, then resize down, to avoid
       having an extra 4 byte GstMemory appended. If the source buffer
       does not have enough data for this, a new GstMemory will be used */
    if (gst_buffer_get_size (section_buffer) - (payload_written +
            payload_len) >= 4) {
      /* enough space */
      extra_alloc_bytes = 4;
    }
    packet_buffer = gst_buffer_copy_region (section_buffer, GST_BUFFER_COPY_ALL,
        payload_written, payload_len + extra_alloc_bytes);

    /* Prepend the header to the section data */
    gst_buffer_prepend_memory (packet_buffer, mem);

    /* add an extra 4 bytes if it could not be reserved already */
    if (extra_alloc_bytes == 4) {
      /* we allocated those already, resize */
      gst_buffer_set_size (packet_buffer,
          gst_buffer_get_size (packet_buffer) - extra_alloc_bytes);
    } else {
      void *ptr = g_malloc (4);
      GstMemory *extra =
          gst_memory_new_wrapped (GST_MEMORY_FLAG_READONLY, ptr, 4, 0, 0, ptr,
          g_free);
      gst_buffer_append_memory (packet_buffer, extra);
    }

    TS_DEBUG ("Writing %d bytes to section. %d bytes remaining",
        len, section->pi.stream_avail - len);

    /* Push the packet without PCR */
    if (G_UNLIKELY (!tsmux_packet_out (mux, packet_buffer, -1))) {
      /* Buffer given away */
      packet_buffer = NULL;
      goto fail;
    }

    packet_buffer = NULL;
    section->pi.stream_avail -= len;
    payload_written += payload_len;
    section->pi.packet_start_unit_indicator = FALSE;
  }

  gst_buffer_unref (section_buffer);

  return TRUE;

fail:
  g_free (packet);
  if (section_buffer)
    gst_buffer_unref (section_buffer);
  return FALSE;
}

static gboolean
tsmux_write_si (TsMux * mux)
{
  g_hash_table_foreach (mux->si_sections,
      (GHFunc) tsmux_section_write_packet, mux);

  mux->si_changed = FALSE;

  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;
  gint64 cur_pcr = -1;
  GstBuffer *buf = NULL;
  GstMapInfo map;

  g_return_val_if_fail (mux != NULL, FALSE);
  g_return_val_if_fail (stream != NULL, FALSE);

  if (tsmux_stream_is_pcr (stream)) {
    gint64 cur_pts = tsmux_stream_get_pts (stream);
    gboolean write_pat;
    gboolean write_si;
    GList *cur;

    cur_pcr = 0;
    if (cur_pts != G_MININT64) {
      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 != G_MININT64) {
      /* CLOCK_BASE >= TSMUX_PCR_OFFSET */
      cur_pts += CLOCK_BASE;
      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_SYS_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;
    } else {
      cur_pcr = -1;
    }

    /* check if we need to rewrite pat */
    if (mux->last_pat_ts == G_MININT64 || mux->pat_changed)
      write_pat = TRUE;
    else if (cur_pts >= mux->last_pat_ts + mux->pat_interval)
      write_pat = TRUE;
    else
      write_pat = FALSE;

    if (write_pat) {
      mux->last_pat_ts = cur_pts;
      if (!tsmux_write_pat (mux))
        return FALSE;
    }

    /* check if we need to rewrite sit */
    if (mux->last_si_ts == G_MININT64 || mux->si_changed)
      write_si = TRUE;
    else if (cur_pts >= mux->last_si_ts + mux->si_interval)
      write_si = TRUE;
    else
      write_si = FALSE;

    if (write_si) {
      mux->last_si_ts = cur_pts;
      if (!tsmux_write_si (mux))
        return FALSE;
    }

    /* check if we need to rewrite any of the current pmts */
    for (cur = mux->programs; cur; cur = cur->next) {
      TsMuxProgram *program = (TsMuxProgram *) cur->data;
      gboolean write_pmt;

      if (program->last_pmt_ts == G_MININT64 || program->pmt_changed)
        write_pmt = TRUE;
      else if (cur_pts >= program->last_pmt_ts + program->pmt_interval)
        write_pmt = TRUE;
      else
        write_pmt = FALSE;

      if (write_pmt) {
        program->last_pmt_ts = cur_pts;
        if (!tsmux_write_pmt (mux, program))
          return FALSE;
      }
    }
  }

  pi->packet_start_unit_indicator = tsmux_stream_at_pes_start (stream);
  if (pi->packet_start_unit_indicator) {
    tsmux_stream_initialize_pes_packet (stream);
    if (stream->dts != G_MININT64)
      stream->dts += CLOCK_BASE;
    if (stream->pts != G_MININT64)
      stream->pts += CLOCK_BASE;
  }
  pi->stream_avail = tsmux_stream_bytes_avail (stream);

  /* obtain buffer */
  if (!tsmux_get_buffer (mux, &buf))
    return FALSE;

  gst_buffer_map (buf, &map, GST_MAP_READ);

  if (!tsmux_write_ts_header (map.data, pi, &payload_len, &payload_offs))
    goto fail;


  if (!tsmux_stream_get_data (stream, map.data + payload_offs, payload_len))
    goto fail;

  gst_buffer_unmap (buf, &map);

  GST_DEBUG ("Writing PES of size %d", (int) gst_buffer_get_size (buf));
  res = tsmux_packet_out (mux, buf, cur_pcr);

  /* Reset all dynamic flags */
  stream->pi.flags &= TSMUX_PACKET_FLAG_PES_FULL_HEADER;

  return res;

  /* ERRORS */
fail:
  {
    gst_buffer_unmap (buf, &map);
    if (buf)
      gst_buffer_unref (buf);
    return FALSE;
  }
}

/**
 * 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);

  /* Free PMT section */
  if (program->pmt.section)
    gst_mpegts_section_unref (program->pmt.section);

  g_array_free (program->streams, TRUE);
  g_slice_free (TsMuxProgram, program);
}

static gboolean
tsmux_write_pat (TsMux * mux)
{

  if (mux->pat_changed) {
    /* program_association_section ()
     * 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
     */
    GList *cur;
    GPtrArray *pat;

    pat = gst_mpegts_pat_new ();

    for (cur = mux->programs; cur; cur = cur->next) {
      GstMpegtsPatProgram *pat_pgm;
      TsMuxProgram *program = (TsMuxProgram *) cur->data;

      pat_pgm = gst_mpegts_pat_program_new ();
      pat_pgm->program_number = program->pgm_number;
      pat_pgm->network_or_program_map_PID = program->pmt_pid;

      g_ptr_array_add (pat, pat_pgm);
    }

    if (mux->pat.section)
      gst_mpegts_section_unref (mux->pat.section);

    mux->pat.section = gst_mpegts_section_from_pat (pat, mux->transport_id);

    mux->pat.section->version_number = mux->pat_version++;

    TS_DEBUG ("PAT has %d programs", mux->nb_programs);
    mux->pat_changed = FALSE;
  }

  return tsmux_section_write_packet (GINT_TO_POINTER (GST_MPEGTS_SECTION_PAT),
      &mux->pat, mux);
}

static gboolean
tsmux_write_pmt (TsMux * mux, TsMuxProgram * program)
{

  if (program->pmt_changed) {
    /* program_association_section ()
     * 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 ();
     *    }
     * }
     */
    GstMpegtsDescriptor *descriptor;
    GstMpegtsPMT *pmt;
    guint8 desc[] = { 0x0F, 0xFF, 0xFC, 0xFC };
    guint i;

    pmt = gst_mpegts_pmt_new ();

    if (program->pcr_stream == NULL)
      pmt->pcr_pid = 0x1FFF;
    else
      pmt->pcr_pid = tsmux_stream_get_pid (program->pcr_stream);

    descriptor = gst_mpegts_descriptor_from_registration ("HDMV", NULL, 0);
    g_ptr_array_add (pmt->descriptors, descriptor);

    descriptor = gst_mpegts_descriptor_from_custom (0x88, desc, 4);
    g_ptr_array_add (pmt->descriptors, descriptor);

    /* Write out the entries */
    for (i = 0; i < program->streams->len; i++) {
      GstMpegtsPMTStream *pmt_stream;
      TsMuxStream *stream = g_array_index (program->streams, TsMuxStream *, i);

      pmt_stream = gst_mpegts_pmt_stream_new ();

      /* FIXME: Use API to retrieve this from the stream */
      pmt_stream->stream_type = stream->stream_type;
      pmt_stream->pid = tsmux_stream_get_pid (stream);

      /* Write any ES descriptors needed */
      tsmux_stream_get_es_descrs (stream, pmt_stream);
      g_ptr_array_add (pmt->streams, pmt_stream);
    }

    TS_DEBUG ("PMT for program %d has %d streams",
        program->pgm_number, program->streams->len);

    pmt->program_number = program->pgm_number;

    program->pmt.pi.pid = program->pmt_pid;
    program->pmt_changed = FALSE;

    if (program->pmt.section)
      gst_mpegts_section_unref (program->pmt.section);

    program->pmt.section = gst_mpegts_section_from_pmt (pmt, program->pmt_pid);
    program->pmt.section->version_number = program->pmt_version++;
  }

  return tsmux_section_write_packet (GINT_TO_POINTER (GST_MPEGTS_SECTION_PMT),
      &program->pmt, mux);
}