/* $Id: mpeg_stream.c,v 1.3 2005/01/01 02:43:59 rockyb Exp $ Copyright (C) 2000, 2004 Herbert Valerio Riedel 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 #include #include #include #include #include /* 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: */