/* MPEG-PS muxer plugin for GStreamer * Copyright 2008 Lin YANG <oxcsnicho@gmail.com> * * 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. */ /* * 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/gst.h> #include "mpegpsmux.h" #include "psmuxcommon.h" #include "psmuxstream.h" #include "psmux.h" #include "crc.h" static gboolean psmux_packet_out (PsMux * mux); static gboolean psmux_write_pack_header (PsMux * mux); static gboolean psmux_write_system_header (PsMux * mux); static gboolean psmux_write_program_stream_map (PsMux * mux); /** * psmux_new: * * Create a new muxer session. * * Returns: A new #PsMux object. */ PsMux * psmux_new (void) { PsMux *mux; mux = g_slice_new0 (PsMux); mux->pts = -1; /* uninitialized values */ mux->pack_hdr_pts = -1; mux->sys_hdr_pts = -1; mux->psm_pts = -1; mux->bit_pts = 0; mux->pes_max_payload = PSMUX_PES_MAX_PAYLOAD; mux->bit_rate = 400 * 1024; /* XXX: better default values? */ mux->rate_bound = 2 * 1024; /* 2* bit_rate / (8*50). XXX: any better default? */ mux->pack_hdr_freq = PSMUX_PACK_HDR_FREQ; mux->sys_hdr_freq = PSMUX_SYS_HDR_FREQ; mux->psm_freq = PSMUX_PSM_FREQ; psmux_stream_id_info_init (&mux->id_info); return mux; } /** * psmux_set_write_func: * @mux: a #PsMux * @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 psmux_set_write_func (PsMux * mux, PsMuxWriteFunc func, void *user_data) { g_return_if_fail (mux != NULL); mux->write_func = func; mux->write_func_data = user_data; } gboolean psmux_write_end_code (PsMux * mux) { guint8 end_code[4] = { 0, 0, 1, PSMUX_PROGRAM_END }; return mux->write_func (end_code, 4, mux->write_func_data); } /** * psmux_free: * @mux: a #PsMux * * Free all resources associated with @mux. After calling this function @mux can * not be used anymore. */ void psmux_free (PsMux * mux) { GList *cur; g_return_if_fail (mux != NULL); /* Free all streams */ for (cur = g_list_first (mux->streams); cur != NULL; cur = g_list_next (cur)) { PsMuxStream *stream = (PsMuxStream *) cur->data; psmux_stream_free (stream); } g_list_free (mux->streams); if (mux->sys_header != NULL) gst_buffer_unref (mux->sys_header); if (mux->psm != NULL) gst_buffer_unref (mux->psm); g_slice_free (PsMux, mux); } /** * psmux_create_stream: * @mux: a #PsMux * @stream_type: a #PsMuxStreamType * * Create a new stream of @stream_type in the muxer session @mux. * * Returns: a new #PsMuxStream. */ PsMuxStream * psmux_create_stream (PsMux * mux, PsMuxStreamType stream_type) { PsMuxStream *stream; // guint16 new_pid; g_return_val_if_fail (mux != NULL, NULL); #if 0 if (pid == PSMUX_PID_AUTO) { new_pid = psmux_get_new_pid (mux); } else { new_pid = pid & 0x1FFF; } /* Ensure we're not creating a PID collision */ if (psmux_find_stream (mux, new_pid)) return NULL; #endif stream = psmux_stream_new (mux, stream_type); mux->streams = g_list_prepend (mux->streams, stream); if (stream->stream_id_ext) { if (!mux->nb_private_streams) mux->nb_streams++; mux->nb_private_streams++; } else mux->nb_streams++; if (stream->is_video_stream) { mux->video_bound++; if (mux->video_bound > 32) g_critical ("Number of video es exceeds upper limit"); } else if (stream->is_audio_stream) { mux->audio_bound++; if (mux->audio_bound > 64) g_critical ("Number of audio es exceeds upper limit"); } return stream; } static gboolean psmux_packet_out (PsMux * mux) { gboolean res; if (G_UNLIKELY (mux->write_func == NULL)) return TRUE; res = mux->write_func (mux->packet_buf, mux->packet_bytes_written, mux->write_func_data); if (res) { mux->bit_size += mux->packet_bytes_written; } mux->packet_bytes_written = 0; return res; } /** * psmux_write_stream_packet: * @mux: a #PsMux * @stream: a #PsMuxStream * * Write a packet of @stream. * * Returns: TRUE if the packet could be written. */ gboolean psmux_write_stream_packet (PsMux * mux, PsMuxStream * stream) { gboolean res; g_return_val_if_fail (mux != NULL, FALSE); g_return_val_if_fail (stream != NULL, FALSE); { guint64 ts = psmux_stream_get_pts (stream); if (ts != -1) mux->pts = ts; } if (mux->pts - mux->pack_hdr_pts > PSMUX_PACK_HDR_INTERVAL || mux->pes_cnt % mux->pack_hdr_freq == 0) { /* Time to write pack header */ /* FIXME: currently we write the mux rate of the PREVIOUS pack into the * pack header, because of the incapability to calculate the mux_rate * before outputing the pack. To calculate the mux_rate for the current * pack, we need to put the whole pack into buffer, calculate the * mux_rate, and then output the whole trunck. */ if (mux->pts != -1 && mux->pts > mux->bit_pts && mux->pts - mux->bit_pts > PSMUX_BITRATE_CALC_INTERVAL) { /* XXX: smoothing the rate? */ mux->bit_rate = gst_util_uint64_scale (mux->bit_size, 8 * CLOCKBASE, (mux->pts - mux->bit_pts)); mux->bit_size = 0; mux->bit_pts = mux->pts; } psmux_write_pack_header (mux); mux->pack_hdr_pts = mux->pts; } if (mux->pes_cnt % mux->sys_hdr_freq == 0) { /* Time to write system header */ psmux_write_system_header (mux); mux->sys_hdr_pts = mux->pts; } if (mux->pes_cnt % mux->psm_freq == 0) { /* Time to write program stream map (PSM) */ psmux_write_program_stream_map (mux); mux->psm_pts = mux->pts; } /* Write the packet */ if (!(mux->packet_bytes_written = psmux_stream_get_data (stream, mux->packet_buf, mux->pes_max_payload + PSMUX_PES_MAX_HDR_LEN))) { return FALSE; } res = psmux_packet_out (mux); if (!res) { GST_DEBUG_OBJECT (mux, "packet write false"); return FALSE; } mux->pes_cnt += 1; return res; } static gboolean psmux_write_pack_header (PsMux * mux) { bits_buffer_t bw; guint64 scr = mux->pts; /* XXX: is this correct? necessary to put any offset? */ if (mux->pts == -1) scr = 0; /* pack_start_code */ bits_initwrite (&bw, 14, mux->packet_buf); bits_write (&bw, 24, PSMUX_START_CODE_PREFIX); bits_write (&bw, 8, PSMUX_PACK_HEADER); /* scr */ bits_write (&bw, 2, 0x1); bits_write (&bw, 3, (scr >> 30) & 0x07); bits_write (&bw, 1, 1); bits_write (&bw, 15, (scr >> 15) & 0x7fff); bits_write (&bw, 1, 1); bits_write (&bw, 15, scr & 0x7fff); bits_write (&bw, 1, 1); bits_write (&bw, 9, 0); /* system_clock_reference_extension: set to 0 (like what VLC does) */ bits_write (&bw, 1, 1); { /* Scale to get the mux_rate, rounding up */ guint mux_rate = gst_util_uint64_scale (mux->bit_rate + 8 * 50 - 1, 1, 8 * 50); if (mux_rate > mux->rate_bound / 2) mux->rate_bound = mux_rate * 2; bits_write (&bw, 22, mux_rate); /* program_mux_rate */ bits_write (&bw, 2, 3); } bits_write (&bw, 5, 0x1f); bits_write (&bw, 3, 0); /* pack_stuffing_length */ mux->packet_bytes_written = 14; return psmux_packet_out (mux); } static void psmux_ensure_system_header (PsMux * mux) { bits_buffer_t bw; guint len = 12 + (mux->nb_streams + (mux->nb_private_streams > 1 ? mux->nb_private_streams - 1 : 0)) * 3; GList *cur; gboolean private_hit = FALSE; guint8 *data; if (mux->sys_header != NULL) return; data = g_malloc (len); bits_initwrite (&bw, len, data); /* system_header start code */ bits_write (&bw, 24, PSMUX_START_CODE_PREFIX); bits_write (&bw, 8, PSMUX_SYSTEM_HEADER); bits_write (&bw, 16, len - 6); /* header_length (bytes after this field) */ bits_write (&bw, 1, 1); /* marker */ bits_write (&bw, 22, mux->rate_bound); /* rate_bound */ bits_write (&bw, 1, 1); /* marker */ bits_write (&bw, 6, mux->audio_bound); /* audio_bound */ bits_write (&bw, 1, 0); /* fixed_flag */ bits_write (&bw, 1, 0); /* CSPS_flag */ bits_write (&bw, 1, 0); /* system_audio_lock_flag */ bits_write (&bw, 1, 0); /* system_video_lock_flag */ bits_write (&bw, 1, 1); /* marker */ bits_write (&bw, 5, mux->video_bound); /* video_bound */ bits_write (&bw, 1, 0); /* packet_rate_restriction_flag */ bits_write (&bw, 7, 0x7f); /* reserved_bits */ for (cur = mux->streams, private_hit = FALSE; cur != NULL; cur = cur->next) { PsMuxStream *stream = (PsMuxStream *) cur->data; if (private_hit && stream->stream_id == PSMUX_EXTENDED_STREAM) continue; bits_write (&bw, 8, stream->stream_id); /* stream_id */ bits_write (&bw, 2, 0x3); /* reserved */ bits_write (&bw, 1, stream->is_video_stream); /* buffer_bound_scale */ bits_write (&bw, 13, stream->max_buffer_size / (stream->is_video_stream ? 1024 : 128)); /* buffer_size_bound */ if (stream->stream_id == PSMUX_EXTENDED_STREAM) private_hit = TRUE; } GST_MEMDUMP ("System Header", data, len); mux->sys_header = gst_buffer_new_wrapped (data, len); } static gboolean psmux_write_system_header (PsMux * mux) { GstMapInfo map; psmux_ensure_system_header (mux); gst_buffer_map (mux->sys_header, &map, GST_MAP_READ); memcpy (mux->packet_buf, map.data, map.size); mux->packet_bytes_written = map.size; gst_buffer_unmap (mux->sys_header, &map); return psmux_packet_out (mux); } static void psmux_ensure_program_stream_map (PsMux * mux) { gint psm_size = 16, es_map_size = 0; bits_buffer_t bw; GList *cur; guint16 len; guint8 *pos; guint8 *data; if (mux->psm != NULL) return; /* pre-write the descriptor loop */ pos = mux->es_info_buf; for (cur = mux->streams; cur != NULL; cur = cur->next) { PsMuxStream *stream = (PsMuxStream *) cur->data; len = 0; *pos++ = stream->stream_type; *pos++ = stream->stream_id; psmux_stream_get_es_descrs (stream, pos + 2, &len); psmux_put16 (&pos, len); es_map_size += len + 4; pos += len; #if 0 if (stream->lang[0] != 0) es_map_size += 6; #endif } psm_size += es_map_size; data = g_malloc (psm_size); bits_initwrite (&bw, psm_size, data); /* psm start code */ bits_write (&bw, 24, PSMUX_START_CODE_PREFIX); bits_write (&bw, 8, PSMUX_PROGRAM_STREAM_MAP); bits_write (&bw, 16, psm_size - 6); /* psm_length */ bits_write (&bw, 1, 1); /* current_next_indicator */ bits_write (&bw, 2, 0xF); /* reserved */ bits_write (&bw, 5, 0x1); /* psm_version = 1 */ bits_write (&bw, 7, 0xFF); /* reserved */ bits_write (&bw, 1, 1); /* marker */ bits_write (&bw, 16, 0); /* program_stream_info_length */ /* program_stream_info empty */ bits_write (&bw, 16, es_map_size); /* elementary_stream_map_length */ memcpy (bw.p_data + bw.i_data, mux->es_info_buf, es_map_size); /* CRC32 */ { guint32 crc = calc_crc32 (bw.p_data, psm_size - 4); guint8 *pos = bw.p_data + psm_size - 4; psmux_put32 (&pos, crc); } GST_MEMDUMP ("Program Stream Map", data, psm_size); mux->psm = gst_buffer_new_wrapped (data, psm_size); } static gboolean psmux_write_program_stream_map (PsMux * mux) { GstMapInfo map; psmux_ensure_program_stream_map (mux); gst_buffer_map (mux->psm, &map, GST_MAP_READ); memcpy (mux->packet_buf, map.data, map.size); mux->packet_bytes_written = map.size; gst_buffer_unmap (mux->psm, &map); return psmux_packet_out (mux); } GList * psmux_get_stream_headers (PsMux * mux) { GList *list; psmux_ensure_system_header (mux); psmux_ensure_program_stream_map (mux); list = g_list_append (NULL, gst_buffer_ref (mux->sys_header)); list = g_list_append (list, gst_buffer_ref (mux->psm)); return list; }