diff options
Diffstat (limited to 'contrib/libvcd/mpeg.c')
-rw-r--r-- | contrib/libvcd/mpeg.c | 1177 |
1 files changed, 1177 insertions, 0 deletions
diff --git a/contrib/libvcd/mpeg.c b/contrib/libvcd/mpeg.c new file mode 100644 index 000000000..545a44fbc --- /dev/null +++ b/contrib/libvcd/mpeg.c @@ -0,0 +1,1177 @@ +/* + $Id: mpeg.c,v 1.2 2004/04/11 12:20:32 miguelfreitas Exp $ + + Copyright (C) 2000 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 <stdio.h> + +#include <cdio/cdio.h> + +/* Private headers */ +#include "bitvec.h" +#include "mpeg.h" +#include "util.h" + +static const char _rcsid[] = "$Id: mpeg.c,v 1.2 2004/04/11 12:20:32 miguelfreitas Exp $"; + +#define MPEG_START_CODE_PATTERN ((uint32_t) 0x00000100) +#define MPEG_START_CODE_MASK ((uint32_t) 0xffffff00) + +#define MPEG_PICTURE_CODE ((uint32_t) 0x00000100) +/* [...slice codes... 0x1a7] */ + +#define MPEG_USER_CODE ((uint32_t) 0x000001b2) +#define MPEG_SEQUENCE_CODE ((uint32_t) 0x000001b3) +#define MPEG_EXT_CODE ((uint32_t) 0x000001b5) +#define MPEG_SEQ_END_CODE ((uint32_t) 0x000001b7) +#define MPEG_GOP_CODE ((uint32_t) 0x000001b8) +#define MPEG_PROGRAM_END_CODE ((uint32_t) 0x000001b9) +#define MPEG_PACK_HEADER_CODE ((uint32_t) 0x000001ba) +#define MPEG_SYSTEM_HEADER_CODE ((uint32_t) 0x000001bb) +#define MPEG_PRIVATE_1_CODE ((uint32_t) 0x000001bd) +#define MPEG_PAD_CODE ((uint32_t) 0x000001be) + +#define MPEG_AUDIO_C0_CODE ((uint32_t) 0x000001c0) /* default */ +#define MPEG_AUDIO_C1_CODE ((uint32_t) 0x000001c1) /* 2nd audio stream id (dual channel) */ +#define MPEG_AUDIO_C2_CODE ((uint32_t) 0x000001c2) /* 3rd audio stream id (surround sound) */ + +#define MPEG_VIDEO_E0_CODE ((uint32_t) 0x000001e0) /* motion */ +#define MPEG_VIDEO_E1_CODE ((uint32_t) 0x000001e1) /* lowres still */ +#define MPEG_VIDEO_E2_CODE ((uint32_t) 0x000001e2) /* hires still */ + +#define PICT_TYPE_I 1 +#define PICT_TYPE_P 2 +#define PICT_TYPE_B 3 +#define PICT_TYPE_D 4 + +static struct { + mpeg_norm_t norm; + unsigned hsize; + unsigned vsize; + int frate_idx; +} const norm_table[] = { + { MPEG_NORM_FILM, 352, 240, 1 }, + { MPEG_NORM_PAL, 352, 288, 3 }, + { MPEG_NORM_NTSC, 352, 240, 4 }, + { MPEG_NORM_PAL_S, 480, 576, 3 }, + { MPEG_NORM_NTSC_S, 480, 480, 4 }, + { MPEG_NORM_OTHER, } +}; + +static const double frame_rates[16] = { + 0.0, 24000.0/1001, 24.0, 25.0, + 30000.0/1001, 30.0, 50.0, 60000.0/1001, + 60.0, 0.0, +}; + +#ifdef DEBUG +# define MARKER(buf, offset) \ + vcd_assert (vcd_bitvec_read_bit (buf, offset) == 1) +#else +# define MARKER(buf, offset) \ + { if (GNUC_UNLIKELY (vcd_bitvec_read_bit (buf, offset) != 1)) vcd_debug ("mpeg: some marker is not set..."); } +#endif + +static inline bool +_start_code_p (uint32_t code) +{ + return (code & MPEG_START_CODE_MASK) == MPEG_START_CODE_PATTERN; +} + +static inline int +_vid_streamid_idx (uint8_t streamid) +{ + switch (streamid | MPEG_START_CODE_PATTERN) + { + case MPEG_VIDEO_E0_CODE: + return 0; + break; + + case MPEG_VIDEO_E1_CODE: + return 1; + break; + + case MPEG_VIDEO_E2_CODE: + return 2; + break; + + default: + vcd_assert_not_reached (); + break; + } + + return -1; +} + +static inline int +_aud_streamid_idx (uint8_t streamid) +{ + switch (streamid | MPEG_START_CODE_PATTERN) + { + case MPEG_AUDIO_C0_CODE: + return 0; + break; + + case MPEG_AUDIO_C1_CODE: + return 1; + break; + + case MPEG_AUDIO_C2_CODE: + return 2; + break; + + default: + vcd_assert_not_reached (); + break; + } + + return -1; +} + +/* used for SCR, PTS and DTS */ +static inline uint64_t +_parse_timecode (const uint8_t *buf, unsigned *offset) +{ + uint64_t _retval; + + _retval = vcd_bitvec_read_bits (buf, offset, 3); + + MARKER (buf, offset); + + _retval <<= 15; + _retval |= vcd_bitvec_read_bits (buf, offset, 15); + + MARKER (buf, offset); + + _retval <<= 15; + _retval |= vcd_bitvec_read_bits (buf, offset, 15); + + MARKER (buf, offset); + + return _retval; +} + +static void +_parse_sequence_header (uint8_t streamid, const uint8_t *buf, + VcdMpegStreamCtx *state) +{ + unsigned offset = 0; + unsigned hsize, vsize, aratio, frate, brate, bufsize, constr; + const uint8_t *data = buf; + const int vid_idx = _vid_streamid_idx (streamid); + + const double aspect_ratios[16] = + { + 0.0000, 1.0000, 0.6735, 0.7031, + 0.7615, 0.8055, 0.8437, 0.8935, + 0.9375, 0.9815, 1.0255, 1.0695, + 1.1250, 1.1575, 1.2015, 0.0000 + }; + + if (state->stream.shdr[vid_idx].seen) /* we have it already */ + return; + + hsize = vcd_bitvec_read_bits (data, &offset, 12); + + vsize = vcd_bitvec_read_bits (data, &offset, 12); + + aratio = vcd_bitvec_read_bits (data, &offset, 4); + + frate = vcd_bitvec_read_bits (data, &offset, 4); + + brate = vcd_bitvec_read_bits (data, &offset, 18); + + MARKER (data, &offset); + + bufsize = vcd_bitvec_read_bits (data, &offset, 10); + + constr = vcd_bitvec_read_bits (data, &offset, 1); + + /* skip intra quantizer matrix */ + + if (vcd_bitvec_read_bits (data, &offset, 1)) + offset += 64 << 3; + + /* skip non-intra quantizer matrix */ + + if (vcd_bitvec_read_bits (data, &offset, 1)) + offset += 64 << 3; + + state->stream.shdr[vid_idx].hsize = hsize; + state->stream.shdr[vid_idx].vsize = vsize; + state->stream.shdr[vid_idx].aratio = aspect_ratios[aratio]; + state->stream.shdr[vid_idx].frate = frame_rates[frate]; + state->stream.shdr[vid_idx].bitrate = 400 * brate; + state->stream.shdr[vid_idx].vbvsize = bufsize * 16 * 1024; + state->stream.shdr[vid_idx].constrained_flag = (constr != 0); + + state->stream.shdr[vid_idx].seen = true; +} + +static void +_parse_gop_header (uint8_t streamid, const uint8_t *buf, + VcdMpegStreamCtx *state) +{ + const uint8_t *data = buf; + unsigned offset = 0; + + bool drop_flag; + /* bool close_gop; */ + /* bool broken_link; */ + + unsigned hour, minute, second, frame; + + drop_flag = vcd_bitvec_read_bits(data, &offset, 1) != 0; + + hour = vcd_bitvec_read_bits(data, &offset, 5); + + minute = vcd_bitvec_read_bits(data, &offset, 6); + + MARKER (data, &offset); + + second = vcd_bitvec_read_bits(data, &offset, 6); + + frame = vcd_bitvec_read_bits(data, &offset, 6); + + /* close_gop = vcd_bitvec_read_bits(data, &offset, 1) != 0; */ + + /* broken_link = vcd_bitvec_read_bits(data, &offset, 1) != 0; */ + + state->packet.gop = true; + state->packet.gop_timecode.h = hour; + state->packet.gop_timecode.m = minute; + state->packet.gop_timecode.s = second; + state->packet.gop_timecode.f = frame; +} + +static inline void +_check_scan_data (const char str[], const msf_t *msf, + VcdMpegStreamCtx *state) +{ + char tmp[16]; + + if (state->stream.scan_data_warnings > VCD_MPEG_SCAN_DATA_WARNS) + return; + + if (state->stream.scan_data_warnings == VCD_MPEG_SCAN_DATA_WARNS) + { + vcd_warn ("mpeg user scan data: from now on, scan information " + "data errors will not be reported anymore---consider" + " enabling the 'update scan offsets' option, " + "if it is not enabled already!"); + state->stream.scan_data_warnings++; + return; + } + + if (msf->m == 0xff + && msf->s == 0xff + && msf->f == 0xff) + return; + + if ((msf->s & 0x80) == 0 + || (msf->f & 0x80) == 0) + { + snprintf (tmp, sizeof (tmp), "%.2x:%.2x.%.2x", msf->m, msf->s, msf->f); + + vcd_warn ("mpeg user scan data: msb of second or frame field " + "not set for '%s': [%s]", str, tmp); + + state->stream.scan_data_warnings++; + + return; + } + + if ((msf->m >> 4) > 9 + || ((0x80 ^ msf->s) >> 4) > 9 + || ((0x80 ^ msf->f) >> 4) > 9 + || (msf->m & 0xf) > 9 + || (msf->s & 0xf) > 9 + || (msf->f & 0xf) > 9) + { + snprintf (tmp, sizeof (tmp), "%.2x:%.2x.%.2x", + msf->m, 0x80 ^ msf->s, 0x80 ^ msf->f); + + vcd_warn ("mpeg user scan data: one or more BCD fields out of range " + "for '%s': [%s]", str, tmp); + + state->stream.scan_data_warnings++; + } +} + +static void +_parse_user_data (uint8_t streamid, const void *buf, unsigned len, + unsigned offset, + VcdMpegStreamCtx *state) +{ + unsigned pos = 0; + PRAGMA_BEGIN_PACKED + struct { + uint8_t tag; + uint8_t len; + uint8_t data[EMPTY_ARRAY_SIZE]; + } GNUC_PACKED const *udg = buf; + PRAGMA_END_PACKED + + if (udg->tag == 0x00) /* if first tag's already 0x00 */ + { + vcd_debug ("strange (possibly non-compliant) user_data seen..."); + } + else while (pos + 2 < len) + { + if (udg->tag == 0x00) + break; + + if (pos + udg->len >= len) + break; + + if (udg->len < 2) + break; + + switch (udg->tag) + { + case 0x00: + vcd_assert_not_reached (); + break; + + case 0x10: /* scan information */ + { + struct vcd_mpeg_scan_data_t *usdi = (void *) udg; + vcd_assert (sizeof (struct vcd_mpeg_scan_data_t) == 14); + + if (GNUC_UNLIKELY (usdi->len != 14)) + { + vcd_warn ("invalid user scan data length (%d != 14)", usdi->len); + break; + } + + vcd_assert (usdi->len == 14); + _check_scan_data ("previous_I_offset", &usdi->prev_ofs, state); + _check_scan_data ("next_I_offset ", &usdi->next_ofs, state); + _check_scan_data ("backward_I_offset", &usdi->back_ofs, state); + _check_scan_data ("forward_I_offset ", &usdi->forw_ofs, state); + + state->packet.scan_data_ptr = usdi; + state->stream.scan_data++; + } + break; + + case 0x11: /* closed caption data */ + vcd_debug ("closed caption data seen -- not supported yet (len = %d)", udg->len); + break; + + default: + vcd_warn ("unknown user data tag id 0x%.2x encountered", udg->tag); + return; /* since we cannot rely on udg->len anymore... */ + break; + } + + + pos += udg->len; + vcd_assert (udg->len >= 2); + udg = (void *) &udg->data[udg->len - 2]; + } + + vcd_assert (pos <= len); +} + +static int +_analyze_pes_header (const uint8_t *buf, int len, + VcdMpegStreamCtx *state) +{ + bool _has_pts = false; + bool _has_dts = false; + int64_t pts = 0; + mpeg_vers_t pes_mpeg_ver = MPEG_VERS_INVALID; + + int pos; + + if (vcd_bitvec_peek_bits (buf, 0, 2) == 2) /* %10 - ISO13818-1 */ + { + unsigned pos2 = 0; + + pes_mpeg_ver = MPEG_VERS_MPEG2; + + pos2 += 2; + + pos2 += 2; /* PES_scrambling_control */ + pos2++; /* PES_priority */ + pos2++; /* data_alignment_indicator */ + pos2++; /* copyright */ + pos2++; /* original_or_copy */ + + switch (vcd_bitvec_read_bits (buf, &pos2, 2)) /* PTS_DTS_flags */ + { + case 2: /* %10 */ + _has_pts = true; + break; + + case 3: /* %11 */ + _has_dts = _has_pts = true; + break; + + default: + /* NOOP */ + break; + } + + pos2++; /* ESCR_flag */ + + pos2++; /* */ + pos2++; /* */ + pos2++; /* */ + pos2++; /* */ + + pos2++; /* PES_extension_flag */ + + pos = vcd_bitvec_read_bits (buf, &pos2, 8); /* PES_header_data_length */ + pos += pos2 >> 3; + + if (_has_pts && _has_dts) + { + vcd_assert (vcd_bitvec_peek_bits (buf, pos2, 4) == 3); /* %0011 */ + pos2 += 4; + + pts = _parse_timecode (buf, &pos2); + + vcd_assert (vcd_bitvec_peek_bits (buf, pos2, 4) == 1); /* %0001 */ + pos2 += 4; + + /* dts = */ _parse_timecode (buf, &pos2); + } + else if (_has_pts) + { + vcd_assert (vcd_bitvec_peek_bits (buf, pos2, 4) == 2); /* %0010 */ + pos2 += 4; + + pts = _parse_timecode (buf, &pos2); + } + } + else /* ISO11172-1 */ + { + unsigned pos2 = 0; + + pes_mpeg_ver = MPEG_VERS_MPEG1; + + /* get rid of stuffing bytes */ + while (((pos2 + 8) < (len << 3)) + && vcd_bitvec_peek_bits (buf, pos2, 8) == 0xff) + pos2 += 8; + + if (vcd_bitvec_peek_bits (buf, pos2, 2) == 1) /* %01 */ + { + pos2 += 2; + + pos2++; /* STD_buffer_scale */ + pos2 += 13; /* STD_buffer_size */ + } + + switch (vcd_bitvec_peek_bits (buf, pos2, 4)) + { + case 0x2: /* %0010 */ + pos2 += 4; + _has_pts = true; + + pts = _parse_timecode (buf, &pos2); + break; + + case 0x3: /* %0011 */ + pos2 += 4; + + _has_dts = _has_pts = true; + pts = _parse_timecode (buf, &pos2); + + vcd_assert (vcd_bitvec_peek_bits (buf, pos2, 4) == 1); /* %0001 */ + pos2 += 4; + + /* dts = */ _parse_timecode (buf, &pos2); + break; + + case 0x0: /* %0000 */ + vcd_assert (vcd_bitvec_peek_bits (buf, pos2, 8) == 0x0f); + pos2 += 8; + break; + + case 0xf: /* %1111 - actually a syntax error! */ + vcd_assert (vcd_bitvec_peek_bits (buf, pos2, 8) == 0xff); + vcd_warn ("Unexpected stuffing byte noticed in ISO11172 PES header!"); + pos2 += 8; + break; + + default: + vcd_error ("Error in ISO11172 PES header"); + break; + } + + pos = pos2 >> 3; + } + + if (_has_pts) + { + double pts2; + + pts2 = (double) pts / 90000.0; + + if (!state->stream.seen_pts) + { + state->stream.max_pts = state->stream.min_pts = pts2; + state->stream.seen_pts = true; + } + else + { + state->stream.max_pts = MAX (state->stream.max_pts, pts2); + state->stream.min_pts = MIN (state->stream.min_pts, pts2); + } + + state->packet.has_pts = true; + state->packet.pts = pts2; + } + + if (state->stream.version != pes_mpeg_ver) + vcd_warn ("pack header mpeg version does not match pes header mpeg version"); + + return pos; +} + +static void +_analyze_audio_pes (uint8_t streamid, const uint8_t *buf, int len, bool only_pts, + VcdMpegStreamCtx *state) +{ + const int aud_idx = _aud_streamid_idx (streamid); + unsigned bitpos; + + vcd_assert (aud_idx != -1); + + bitpos = _analyze_pes_header (buf, len, state); + + /* if only pts extraction was needed, we are done here... */ + if (only_pts) + return; + + if (state->stream.ahdr[aud_idx].seen) + return; + + bitpos <<= 3; + + while (bitpos <= (len << 3)) + { + unsigned syncword = vcd_bitvec_peek_bits (buf, bitpos, 12); + + if (syncword != 0xfff) + { + bitpos += 8; + continue; + } + + bitpos += 12; + + if (GNUC_UNLIKELY (!vcd_bitvec_read_bits (buf, &bitpos, 1))) + { + vcd_debug ("non-MPEG1 audio stream header seen"); + break; + } + + switch (vcd_bitvec_read_bits (buf, &bitpos, 2)) /* layer */ + { + case 3: /* %11 */ + state->stream.ahdr[aud_idx].layer = 1; + break; + case 2: /* %10 */ + state->stream.ahdr[aud_idx].layer = 2; + break; + case 1: /* %01 */ + state->stream.ahdr[aud_idx].layer = 3; + break; + case 0: /* %00 */ + state->stream.ahdr[aud_idx].layer = 0; + break; + } + + bitpos++; /* protection_bit */ + + { + const int bits = vcd_bitvec_read_bits (buf, &bitpos, 4); + + const unsigned bit_rates[4][16] = { + {0, }, + {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0}, + {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0}, + {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0} + }; + + vcd_assert (IN(state->stream.ahdr[aud_idx].layer, 0, 3)); + vcd_assert (IN(bits, 0, 15)); + + state->stream.ahdr[aud_idx].bitrate = 1024 * bit_rates[state->stream.ahdr[aud_idx].layer][bits]; + } + + switch (vcd_bitvec_read_bits (buf, &bitpos, 2)) /* sampling_frequency */ + { + case 0: /* %00 */ + state->stream.ahdr[aud_idx].sampfreq = 44100; + break; + case 1: /* %01 */ + state->stream.ahdr[aud_idx].sampfreq = 48000; + break; + case 2: /* %10 */ + state->stream.ahdr[aud_idx].sampfreq = 32000; + break; + case 3: /* %11 */ + state->stream.ahdr[aud_idx].sampfreq = 0; + break; + } + + bitpos++; /* padding_bit */ + + bitpos++; /* private_bit */ + + state->stream.ahdr[aud_idx].mode = 1 + vcd_bitvec_read_bits (buf, &bitpos, 2); /* mode */ + + state->stream.ahdr[aud_idx].seen = true; + + /* we got the info, let's jump outta here */ + break; + } +} + +static void +_analyze_video_pes (uint8_t streamid, const uint8_t *buf, int len, bool only_pts, + VcdMpegStreamCtx *state) +{ + const int vid_idx = _vid_streamid_idx (streamid); + + int pos, pes_header; + int sequence_header_pos = -1; + int gop_header_pos = -1; + int ipicture_header_pos = -1; + + vcd_assert (vid_idx != -1); + + pes_header = pos = _analyze_pes_header (buf, len, state); + + /* if only pts extraction was needed, we are done here... */ + if (only_pts) + return; + + while (pos + 4 <= len) + { + uint32_t code = vcd_bitvec_peek_bits32 (buf, pos << 3); + + if (!_start_code_p (code)) + { + pos++; + continue; + } + + switch (code) + { + case MPEG_PICTURE_CODE: + pos += 4; + + if (vcd_bitvec_peek_bits (buf, (pos << 3) + 10, 3) == 1) + ipicture_header_pos = pos; + break; + + case MPEG_SEQUENCE_CODE: + pos += 4; + sequence_header_pos = pos; + _parse_sequence_header (streamid, buf + pos, state); + break; + + case MPEG_GOP_CODE: + pos += 4; + if (pos + 4 > len) + break; + gop_header_pos = pos; + _parse_gop_header (streamid, buf + pos, state); + state->packet.gop = true; + break; + + case MPEG_USER_CODE: + pos += 4; + if (pos + 4 > len) + break; + _parse_user_data (streamid, buf + pos, len - pos, pos, state); + break; + + case MPEG_EXT_CODE: + default: + pos += 4; + break; + } + } + + /* decide whether this packet qualifies as access point */ + state->packet.aps = APS_NONE; /* paranoia */ + + if (state->packet.has_pts + && ipicture_header_pos != -1) + { + enum aps_t _aps_type = APS_NONE; + + switch (state->stream.version) + { + case MPEG_VERS_MPEG1: + case MPEG_VERS_MPEG2: + if (sequence_header_pos != -1 + && sequence_header_pos < gop_header_pos + && gop_header_pos < ipicture_header_pos) + _aps_type = (sequence_header_pos - 4 == pes_header) ? APS_ASGI : APS_SGI; + else if (gop_header_pos != 1 + && gop_header_pos < ipicture_header_pos) + _aps_type = APS_GI; + else + _aps_type = APS_I; + + break; + + default: + break; + } + + if (_aps_type) + { + const double pts2 = state->packet.pts; + + if (state->stream.shdr[vid_idx].last_aps_pts > pts2) + vcd_warn ("APS' pts seems out of order (actual pts %f, last seen pts %f) " + "-- ignoring this aps", + pts2, state->stream.shdr[vid_idx].last_aps_pts); + else + { + state->packet.aps_idx = vid_idx; + state->packet.aps = _aps_type; + state->packet.aps_pts = pts2; + state->stream.shdr[vid_idx].last_aps_pts = pts2; + } + } + } +} + +static void +_register_streamid (uint8_t streamid, VcdMpegStreamCtx *state) +{ + const uint32_t code = MPEG_START_CODE_PATTERN | streamid; + + switch (code) + { + case MPEG_VIDEO_E0_CODE: + case MPEG_VIDEO_E1_CODE: + case MPEG_VIDEO_E2_CODE: + state->packet.video[_vid_streamid_idx (streamid)] = true; + break; + + case MPEG_AUDIO_C0_CODE: + case MPEG_AUDIO_C1_CODE: + case MPEG_AUDIO_C2_CODE: + state->packet.audio[_aud_streamid_idx (streamid)] = true; + break; + + case MPEG_PAD_CODE: + state->packet.padding = true; + break; + + case MPEG_SYSTEM_HEADER_CODE: + state->packet.system_header = true; + break; + } +} + +static void +_analyze_system_header (const uint8_t *buf, int len, + VcdMpegStreamCtx *state) +{ + unsigned bitpos = 0; + + MARKER (buf, &bitpos); + + bitpos += 22; /* rate_bound */ + + MARKER (buf, &bitpos); + + bitpos += 6; /* audio_bound */ + + bitpos++; /* fixed_flag */ + bitpos++; /* CSPS_flag */ + bitpos++; /* system_audio_lock_flag */ + bitpos++; /* system_video_lock_flag */ + + MARKER (buf, &bitpos); + + bitpos += 5; /* video_bound */ + + bitpos += 1; /* packet_rate_restriction_flag -- only ISO 13818-1 */ + bitpos += 7; /* reserved */ + + while (vcd_bitvec_peek_bits (buf, bitpos, 1) == 1 + && bitpos <= (len << 3)) + { + const uint8_t stream_id = vcd_bitvec_read_bits (buf, &bitpos, 8); + + bitpos += 2; /* %11 */ + + bitpos++; /* P-STD_buffer_bound_scale */ + bitpos += 13; /* P-STD_buffer_size_bound */ + + _register_streamid (stream_id, state); + } + + vcd_assert (bitpos <= (len << 3)); +} + +static void +_analyze_private_1_stream (const uint8_t *buf, int len, + VcdMpegStreamCtx *state) +{ + unsigned bitpos = _analyze_pes_header (buf, len, state); + int ogt_idx = -1; + + uint8_t private_data_id; + + bitpos <<= 3; + + private_data_id = vcd_bitvec_read_bits (buf, &bitpos, 8); + + switch (private_data_id) + { + uint8_t sub_stream_id; + + case 0x00: + case 0x01: + case 0x02: + case 0x03: + /* CVD subs */ + ogt_idx = private_data_id; + + if (!state->stream.ogt[ogt_idx]) + vcd_debug ("Assuming CVD-style subtitles for data_id 0x%.2x in private stream 1", ogt_idx); + + break; + + case 0x70: + /* SVCD OGT */ + sub_stream_id = vcd_bitvec_read_bits (buf, &bitpos, 8); + + if (sub_stream_id < 4) + { + ogt_idx = sub_stream_id; + if (!state->stream.ogt[ogt_idx]) + vcd_debug ("subtitles detect for channel 0x%.2x", ogt_idx); + } + else + vcd_warn ("sub_stream_id out of range (0x%.2x)", sub_stream_id); + break; + + default: + vcd_warn ("unknown private_data_id for private stream 1 seen (0x%.2x)", + private_data_id); + return; + break; + } + + if (ogt_idx >= 0) + state->stream.ogt[ogt_idx] = state->packet.ogt[ogt_idx] = true; +} + +int +vcd_mpeg_parse_packet (const void *_buf, unsigned buflen, bool parse_pes, + VcdMpegStreamCtx *ctx) +{ + const uint8_t *buf = _buf; + int pos; + + vcd_assert (buf != NULL); + vcd_assert (ctx != NULL); + + /* clear packet info */ + memset (&(ctx->packet), 0, sizeof (ctx->packet)); + + ctx->stream.packets++; + + for (pos = 0; pos < buflen && !buf[pos]; pos++); + + if (pos == buflen) + { + ctx->packet.zero = true; + return buflen; + } + + /* verify the packet begins with a pack header */ + if (vcd_bitvec_peek_bits32 (buf, 0) != MPEG_PACK_HEADER_CODE) + { + const uint32_t _code = vcd_bitvec_peek_bits32 (buf, 0); + + vcd_warn ("mpeg scan: pack header code (0x%8.8x) expected, " + "but 0x%8.8x found (buflen = %d)", + (unsigned int) MPEG_PACK_HEADER_CODE, + (unsigned int) _code, buflen); + + ctx->stream.packets--; + + if (!ctx->stream.packets) + { + if (_code == MPEG_SEQUENCE_CODE) + vcd_warn ("...this looks like a elementary video stream" + " but a multiplexed program stream was required."); + + if ((0xfff00000 & _code) == 0xfff00000) + vcd_warn ("...this looks like a elementary audio stream" + " but a multiplexed program stream was required."); + + if (_code == 0x52494646) + vcd_warn ("...this looks like a RIFF header" + " but a plain multiplexed program stream was required."); + } + else if (_code == MPEG_PROGRAM_END_CODE) + vcd_warn ("...PEM (program end marker) found instead of pack header;" + " should be in last 4 bytes of pack"); + + return 0; + } + + /* take a look at the pack header */ + pos = 0; + + while (pos + 4 <= buflen) + { + uint32_t code = vcd_bitvec_peek_bits32 (buf, pos << 3); + + /* skip zero bytes... */ + if (!code) + { + pos += (pos + 4 == buflen) ? 4 : 2; + continue; + } + + /* continue until start code seen */ + if (!_start_code_p (code)) + { + pos++; + continue; + } + + switch (code) + { + uint16_t size; + int bits; + unsigned bitpos; + + case MPEG_PACK_HEADER_CODE: + if (pos) + return pos; + + pos += 4; + + bitpos = pos << 3; + bits = vcd_bitvec_peek_bits (buf, bitpos, 4); + + if (bits == 0x2) /* %0010 ISO11172-1 */ + { + uint64_t _scr; + uint32_t _muxrate; + + bitpos += 4; + + if (!ctx->stream.version) + ctx->stream.version = MPEG_VERS_MPEG1; + + if (ctx->stream.version != MPEG_VERS_MPEG1) + vcd_warn ("mixed mpeg versions?"); + + _scr = _parse_timecode (buf, &bitpos); + + MARKER (buf, &bitpos); + + _muxrate = vcd_bitvec_read_bits (buf, &bitpos, 22); + + MARKER (buf, &bitpos); + + vcd_assert (bitpos % 8 == 0); + pos = bitpos >> 3; + + ctx->packet.scr = _scr; + ctx->stream.muxrate = ctx->packet.muxrate = _muxrate * 50 * 8; + } + else if (bits >> 2 == 0x1) /* %01xx ISO13818-1 */ + { + uint64_t _scr; + uint32_t _muxrate; + int tmp; + + bitpos += 2; + + if (!ctx->stream.version) + ctx->stream.version = MPEG_VERS_MPEG2; + + if (ctx->stream.version != MPEG_VERS_MPEG2) + vcd_warn ("mixed mpeg versions?"); + + _scr = _parse_timecode (buf, &bitpos); + + _scr *= 300; + _scr += vcd_bitvec_read_bits (buf, &bitpos, 9); /* SCR ext */ + + MARKER (buf, &bitpos); + + _muxrate = vcd_bitvec_read_bits (buf, &bitpos, 22); + + MARKER (buf, &bitpos); + MARKER (buf, &bitpos); + + bitpos += 5; /* reserved */ + + tmp = vcd_bitvec_read_bits (buf, &bitpos, 3) << 3; + + bitpos += tmp; + + vcd_assert (bitpos % 8 == 0); + pos = bitpos >> 3; + + ctx->packet.scr = _scr; + ctx->stream.muxrate = ctx->packet.muxrate = _muxrate * 50 * 8; + } + else + { + vcd_warn ("packet not recognized as either version 1 or 2 (%d)" + " -- assuming v1", bits); + } + + break; + + case MPEG_SYSTEM_HEADER_CODE: + case MPEG_PAD_CODE: + case MPEG_PRIVATE_1_CODE: + case MPEG_VIDEO_E0_CODE: + case MPEG_VIDEO_E1_CODE: + case MPEG_VIDEO_E2_CODE: + case MPEG_AUDIO_C0_CODE: + case MPEG_AUDIO_C1_CODE: + case MPEG_AUDIO_C2_CODE: + pos += 4; + size = vcd_bitvec_peek_bits16 (buf, pos << 3); + pos += 2; + + if (pos + size > buflen) + { + vcd_warn ("packet length beyond buffer" + " (pos = %d + size = %d > buflen = %d) " + "-- stream may be truncated or packet length > 2324 bytes!", + pos, size, buflen); + ctx->stream.packets--; + return 0; + } + + _register_streamid (code & 0xff, ctx); + + switch (code) + { + case MPEG_SYSTEM_HEADER_CODE: + _analyze_system_header (buf + pos, size, ctx); + break; + + case MPEG_VIDEO_E0_CODE: + case MPEG_VIDEO_E1_CODE: + case MPEG_VIDEO_E2_CODE: + _analyze_video_pes (code & 0xff, buf + pos, size, !parse_pes, ctx); + break; + + case MPEG_AUDIO_C0_CODE: + case MPEG_AUDIO_C1_CODE: + case MPEG_AUDIO_C2_CODE: + _analyze_audio_pes (code & 0xff, buf + pos, size, !parse_pes, ctx); + break; + + case MPEG_PRIVATE_1_CODE: + _analyze_private_1_stream (buf + pos, size, ctx); + break; + } + + pos += size; + break; + + case MPEG_PROGRAM_END_CODE: + ctx->packet.pem = true; + pos += 4; + break; + + case MPEG_PICTURE_CODE: + pos += 3; + break; + + default: + vcd_debug ("unexpected start code 0x%8.8x", (unsigned int) code); + pos += 4; + break; + } + } + + if (pos != buflen) + vcd_debug ("pos != buflen (%d != %d)", pos, buflen); /* fixme? */ + + return buflen; +} + +mpeg_norm_t +vcd_mpeg_get_norm (const struct vcd_mpeg_stream_vid_info *_info) +{ + int i; + + for (i = 0; norm_table[i].norm != MPEG_NORM_OTHER;i++) + if (norm_table[i].hsize == _info->hsize + && norm_table[i].vsize == _info->vsize + && frame_rates[norm_table[i].frate_idx] == _info->frate) + break; + + return norm_table[i].norm; +} + +enum vcd_mpeg_packet_type +vcd_mpeg_packet_get_type (const struct vcd_mpeg_packet_info *_info) +{ + if (_info->video[0] + || _info->video[1] + || _info->video[2]) + return PKT_TYPE_VIDEO; + else if (_info->audio[0] + || _info->audio[1] + || _info->audio[2]) + return PKT_TYPE_AUDIO; + else if (_info->zero) + return PKT_TYPE_ZERO; + else if (_info->ogt[0] + || _info->ogt[1] + || _info->ogt[2] + || _info->ogt[3]) + return PKT_TYPE_OGT; + else if (_info->system_header || _info->padding) + return PKT_TYPE_EMPTY; + + return PKT_TYPE_INVALID; +} + + +/* + * Local variables: + * c-file-style: "gnu" + * tab-width: 8 + * indent-tabs-mode: nil + * End: + */ |