mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-03 16:09:39 +00:00
ad6ea8fbe9
Basic version with only the system header and the program stream map. An advanced version could include codec-specific bits like SPS/PPS too. This is useful in connection with e.g. multifilesink to make sure new files always start with the stream headers.
504 lines
14 KiB
C
504 lines
14 KiB
C
/* 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., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, 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) {
|
|
PS_DEBUG ("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)
|
|
{
|
|
GstBuffer *buf;
|
|
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;
|
|
|
|
if (mux->sys_header != NULL)
|
|
return;
|
|
|
|
buf = gst_buffer_new_and_alloc (len);
|
|
|
|
/* system_header_start_code */
|
|
bits_initwrite (&bw, len, GST_BUFFER_DATA (buf));
|
|
|
|
/* 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 = g_list_first (mux->streams), private_hit = FALSE; cur != NULL;
|
|
cur = g_list_next (cur)) {
|
|
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", GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
|
|
|
|
mux->sys_header = buf;
|
|
}
|
|
|
|
static gboolean
|
|
psmux_write_system_header (PsMux * mux)
|
|
{
|
|
psmux_ensure_system_header (mux);
|
|
|
|
memcpy (mux->packet_buf, GST_BUFFER_DATA (mux->sys_header),
|
|
GST_BUFFER_SIZE (mux->sys_header));
|
|
mux->packet_bytes_written = GST_BUFFER_SIZE (mux->sys_header);
|
|
|
|
return psmux_packet_out (mux);
|
|
}
|
|
|
|
static void
|
|
psmux_ensure_program_stream_map (PsMux * mux)
|
|
{
|
|
GstBuffer *buf;
|
|
gint psm_size = 16, es_map_size = 0;
|
|
bits_buffer_t bw;
|
|
GList *cur;
|
|
guint16 len;
|
|
guint8 *pos;
|
|
|
|
if (mux->psm != NULL)
|
|
return;
|
|
|
|
/* pre-write the descriptor loop */
|
|
pos = mux->es_info_buf;
|
|
for (cur = g_list_first (mux->streams); cur != NULL; cur = g_list_next (cur)) {
|
|
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;
|
|
|
|
buf = gst_buffer_new_and_alloc (psm_size);
|
|
|
|
bits_initwrite (&bw, psm_size, GST_BUFFER_DATA (buf));
|
|
|
|
/* 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", GST_BUFFER_DATA (buf),
|
|
GST_BUFFER_SIZE (buf));
|
|
|
|
mux->psm = buf;
|
|
}
|
|
|
|
static gboolean
|
|
psmux_write_program_stream_map (PsMux * mux)
|
|
{
|
|
psmux_ensure_program_stream_map (mux);
|
|
|
|
memcpy (mux->packet_buf, GST_BUFFER_DATA (mux->psm),
|
|
GST_BUFFER_SIZE (mux->psm));
|
|
mux->packet_bytes_written = GST_BUFFER_SIZE (mux->psm);
|
|
|
|
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;
|
|
}
|