summaryrefslogtreecommitdiff
path: root/contrib/libvcd/mpeg_stream.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/libvcd/mpeg_stream.c')
-rw-r--r--contrib/libvcd/mpeg_stream.c487
1 files changed, 487 insertions, 0 deletions
diff --git a/contrib/libvcd/mpeg_stream.c b/contrib/libvcd/mpeg_stream.c
new file mode 100644
index 000000000..e5466766a
--- /dev/null
+++ b/contrib/libvcd/mpeg_stream.c
@@ -0,0 +1,487 @@
+/*
+ $Id: mpeg_stream.c,v 1.3 2005/01/01 02:43:59 rockyb Exp $
+
+ Copyright (C) 2000, 2004 Herbert Valerio Riedel <hvr@gnu.org>
+
+ 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
+*/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <cdio/cdio.h>
+#include <cdio/bytesex.h>
+
+#include <libvcd/logging.h>
+
+/* Private headers */
+#include "vcd_assert.h"
+#include "mpeg_stream.h"
+#include "data_structures.h"
+#include "mpeg.h"
+#include "util.h"
+
+static const char _rcsid[] = "$Id: mpeg_stream.c,v 1.3 2005/01/01 02:43:59 rockyb Exp $";
+
+struct _VcdMpegSource
+{
+ VcdDataSource *data_source;
+
+ bool scanned;
+
+ /* _get_packet cache */
+ unsigned _read_pkt_pos;
+ unsigned _read_pkt_no;
+
+ struct vcd_mpeg_stream_info info;
+};
+
+/*
+ * access functions
+ */
+
+VcdMpegSource *
+vcd_mpeg_source_new (VcdDataSource *mpeg_file)
+{
+ VcdMpegSource *new_obj;
+
+ vcd_assert (mpeg_file != NULL);
+
+ new_obj = _vcd_malloc (sizeof (VcdMpegSource));
+
+ new_obj->data_source = mpeg_file;
+ new_obj->scanned = false;
+
+ return new_obj;
+}
+
+void
+vcd_mpeg_source_destroy (VcdMpegSource *obj, bool destroy_file_obj)
+{
+ int i;
+ vcd_assert (obj != NULL);
+
+ if (destroy_file_obj)
+ vcd_data_source_destroy (obj->data_source);
+
+ for (i = 0; i < 3; i++)
+ if (obj->info.shdr[i].aps_list)
+ _cdio_list_free (obj->info.shdr[i].aps_list, true);
+
+ free (obj);
+}
+
+const struct vcd_mpeg_stream_info *
+vcd_mpeg_source_get_info (VcdMpegSource *obj)
+{
+ vcd_assert (obj != NULL);
+
+ vcd_assert (obj->scanned);
+
+ return &(obj->info);
+}
+
+long
+vcd_mpeg_source_stat (VcdMpegSource *obj)
+{
+ vcd_assert (obj != NULL);
+ vcd_assert (!obj->scanned);
+
+ return obj->info.packets * 2324;
+}
+
+void
+vcd_mpeg_source_scan (VcdMpegSource *obj, bool strict_aps, bool fix_scan_info,
+ vcd_mpeg_prog_cb_t callback, void *user_data)
+{
+ unsigned length = 0;
+ unsigned pos = 0;
+ unsigned pno = 0;
+ unsigned padbytes = 0;
+ unsigned padpackets = 0;
+ VcdMpegStreamCtx state;
+ CdioListNode *n;
+ vcd_mpeg_prog_info_t _progress = { 0, };
+
+ vcd_assert (obj != NULL);
+
+ if (obj->scanned)
+ {
+ vcd_debug ("already scanned... not rescanning");
+ return;
+ }
+
+ vcd_assert (!obj->scanned);
+
+ memset (&state, 0, sizeof (state));
+
+ if (fix_scan_info)
+ state.stream.scan_data_warnings = VCD_MPEG_SCAN_DATA_WARNS + 1;
+
+ vcd_data_source_seek (obj->data_source, 0);
+ length = vcd_data_source_stat (obj->data_source);
+
+ if (callback)
+ {
+ _progress.length = length;
+ callback (&_progress, user_data);
+ }
+
+
+ while (pos < length)
+ {
+ char buf[2324] = { 0, };
+ int read_len = MIN (sizeof (buf), (length - pos));
+ int pkt_len;
+
+ vcd_data_source_read (obj->data_source, buf, read_len, 1);
+
+ pkt_len = vcd_mpeg_parse_packet (buf, read_len, true, &state);
+
+ if (!pkt_len)
+ {
+ if (!pno)
+ vcd_error ("input mpeg stream has been deemed invalid -- aborting");
+
+ vcd_warn ("bad packet at packet #%d (stream byte offset %d)"
+ " -- remaining %d bytes of stream will be ignored",
+ pno, pos, length - pos);
+
+ pos = length; /* don't fall into assert... */
+ break;
+ }
+
+ if (callback && (pos - _progress.current_pos) > (length / 100))
+ {
+ _progress.current_pos = pos;
+ _progress.current_pack = pno;
+ callback (&_progress, user_data);
+ }
+
+ switch (state.packet.aps)
+ {
+ case APS_NONE:
+ break;
+
+ case APS_I:
+ case APS_GI:
+ if (strict_aps)
+ break; /* allow only if now strict aps */
+
+ case APS_SGI:
+ case APS_ASGI:
+ {
+ struct aps_data *_data = _vcd_malloc (sizeof (struct aps_data));
+
+ _data->packet_no = pno;
+ _data->timestamp = state.packet.aps_pts;
+
+ if (!state.stream.shdr[state.packet.aps_idx].aps_list)
+ state.stream.shdr[state.packet.aps_idx].aps_list = _cdio_list_new ();
+
+ _cdio_list_append (state.stream.shdr[state.packet.aps_idx].aps_list, _data);
+ }
+ break;
+
+ default:
+ vcd_assert_not_reached ();
+ break;
+ }
+
+ pos += pkt_len;
+ pno++;
+
+ if (pkt_len != read_len)
+ {
+ padbytes += (2324 - pkt_len);
+
+ if (!padpackets)
+ vcd_warn ("mpeg stream will be padded on the fly -- hope that's ok for you!");
+
+ padpackets++;
+
+ vcd_data_source_seek (obj->data_source, pos);
+ }
+ }
+
+ vcd_data_source_close (obj->data_source);
+
+ if (callback)
+ {
+ _progress.current_pos = pos;
+ _progress.current_pack = pno;
+ callback (&_progress, user_data);
+ }
+
+ vcd_assert (pos == length);
+
+ obj->info = state.stream;
+ obj->scanned = true;
+
+ obj->info.playing_time = obj->info.max_pts - obj->info.min_pts;
+
+ if (obj->info.min_pts)
+ vcd_debug ("pts start offset %f (max pts = %f)",
+ obj->info.min_pts, obj->info.max_pts);
+
+ vcd_debug ("playing time %f", obj->info.playing_time);
+
+ if (!state.stream.scan_data && state.stream.version == MPEG_VERS_MPEG2)
+ vcd_warn ("mpeg stream contained no scan information (user) data");
+
+ {
+ int i;
+
+ for (i = 0; i < 3; i++)
+ if (obj->info.shdr[i].aps_list)
+ _CDIO_LIST_FOREACH (n, obj->info.shdr[i].aps_list)
+ {
+ struct aps_data *_data = _cdio_list_node_data (n);
+
+ _data->timestamp -= obj->info.min_pts;
+ }
+ }
+
+ if (padpackets)
+ vcd_warn ("autopadding requires to insert additional %d zero bytes"
+ " into MPEG stream (due to %d unaligned packets of %d total)",
+ padbytes, padpackets, state.stream.packets);
+
+ obj->info.version = state.stream.version;
+}
+
+static double
+_approx_pts (CdioList *aps_list, uint32_t packet_no)
+{
+ double retval = 0;
+ CdioListNode *node;
+
+ struct aps_data *_laps = NULL;
+
+ double last_pts_ratio = 0;
+
+ _CDIO_LIST_FOREACH (node, aps_list)
+ {
+ struct aps_data *_aps = _cdio_list_node_data (node);
+
+ if (_laps)
+ {
+ long p = _aps->packet_no;
+ double t = _aps->timestamp;
+
+ p -= _laps->packet_no;
+ t -= _laps->timestamp;
+
+ last_pts_ratio = t / p;
+ }
+
+ if (_aps->packet_no >= packet_no)
+ break;
+
+ _laps = _aps;
+ }
+
+ retval = packet_no;
+ retval -= _laps->packet_no;
+ retval *= last_pts_ratio;
+ retval += _laps->timestamp;
+
+ return retval;
+}
+
+static void
+_set_scan_msf (msf_t *_msf, long lsn)
+{
+ if (lsn == -1)
+ {
+ _msf->m = _msf->s = _msf->f = 0xff;
+ return;
+ }
+
+ cdio_lba_to_msf (lsn, _msf);
+ _msf->s |= 0x80;
+ _msf->f |= 0x80;
+}
+
+static void
+_fix_scan_info (struct vcd_mpeg_scan_data_t *scan_data_ptr,
+ unsigned packet_no, double pts, CdioList *aps_list)
+{
+ CdioListNode *node;
+ long _next = -1, _prev = -1, _forw = -1, _back = -1;
+
+ _CDIO_LIST_FOREACH (node, aps_list)
+ {
+ struct aps_data *_aps = _cdio_list_node_data (node);
+
+ if (_aps->packet_no == packet_no)
+ continue;
+ else if (_aps->packet_no < packet_no)
+ {
+ _prev = _aps->packet_no;
+
+ if (pts - _aps->timestamp < 10 && _back == -1)
+ _back = _aps->packet_no;
+ }
+ else if (_aps->packet_no > packet_no)
+ {
+ if (_next == -1)
+ _next = _aps->packet_no;
+
+ if (_aps->timestamp - pts < 10)
+ _forw = _aps->packet_no;
+ }
+ }
+
+ if (_back == -1)
+ _back = packet_no;
+
+ if (_forw == -1)
+ _forw = packet_no;
+
+ _set_scan_msf (&scan_data_ptr->prev_ofs, _prev);
+ _set_scan_msf (&scan_data_ptr->next_ofs, _next);
+ _set_scan_msf (&scan_data_ptr->back_ofs, _back);
+ _set_scan_msf (&scan_data_ptr->forw_ofs, _forw);
+}
+
+int
+vcd_mpeg_source_get_packet (VcdMpegSource *obj, unsigned long packet_no,
+ void *packet_buf, struct vcd_mpeg_packet_info *flags,
+ bool fix_scan_info)
+{
+ unsigned length;
+ unsigned pos;
+ unsigned pno;
+ VcdMpegStreamCtx state;
+
+ vcd_assert (obj != NULL);
+ vcd_assert (obj->scanned);
+ vcd_assert (packet_buf != NULL);
+
+ if (packet_no >= obj->info.packets)
+ {
+ vcd_error ("invalid argument");
+ return -1;
+ }
+
+ if (packet_no < obj->_read_pkt_no)
+ {
+ vcd_warn ("rewinding mpeg stream...");
+ obj->_read_pkt_no = 0;
+ obj->_read_pkt_pos = 0;
+ }
+
+ memset (&state, 0, sizeof (state));
+ state.stream.seen_pts = true;
+ state.stream.min_pts = obj->info.min_pts;
+ state.stream.scan_data_warnings = VCD_MPEG_SCAN_DATA_WARNS + 1;
+
+ pos = obj->_read_pkt_pos;
+ pno = obj->_read_pkt_no;
+ length = vcd_data_source_stat (obj->data_source);
+
+ vcd_data_source_seek (obj->data_source, pos);
+
+ while (pos < length)
+ {
+ char buf[2324] = { 0, };
+ int read_len = MIN (sizeof (buf), (length - pos));
+ int pkt_len;
+
+ vcd_data_source_read (obj->data_source, buf, read_len, 1);
+
+ pkt_len = vcd_mpeg_parse_packet (buf, read_len,
+ fix_scan_info, &state);
+
+ vcd_assert (pkt_len > 0);
+
+ if (pno == packet_no)
+ {
+ /* optimized for sequential access,
+ thus save pointer to next mpeg pack */
+ obj->_read_pkt_pos = pos + pkt_len;
+ obj->_read_pkt_no = pno + 1;
+
+ if (fix_scan_info
+ && state.packet.scan_data_ptr
+ && obj->info.version == MPEG_VERS_MPEG2)
+ {
+ int vid_idx = 0;
+ double _pts;
+
+ if (state.packet.video[2])
+ vid_idx = 2;
+ else if (state.packet.video[1])
+ vid_idx = 1;
+ else
+ vid_idx = 0;
+
+ if (state.packet.has_pts)
+ _pts = state.packet.pts - obj->info.min_pts;
+ else
+ _pts = _approx_pts (obj->info.shdr[vid_idx].aps_list, packet_no);
+
+ _fix_scan_info (state.packet.scan_data_ptr, packet_no,
+ _pts, obj->info.shdr[vid_idx].aps_list);
+ }
+
+ memset (packet_buf, 0, 2324);
+ memcpy (packet_buf, buf, pkt_len);
+
+ if (flags)
+ {
+ *flags = state.packet;
+ flags->pts -= obj->info.min_pts;
+ }
+
+ return 0; /* breaking out */
+ }
+
+ pos += pkt_len;
+ pno++;
+
+ if (pkt_len != read_len)
+ vcd_data_source_seek (obj->data_source, pos);
+ }
+
+ vcd_assert (pos == length);
+
+ vcd_error ("shouldnt be reached...");
+
+ return -1;
+}
+
+void
+vcd_mpeg_source_close (VcdMpegSource *obj)
+{
+ vcd_assert (obj != NULL);
+
+ vcd_data_source_close (obj->data_source);
+}
+
+
+/*
+ * Local variables:
+ * c-file-style: "gnu"
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ */