Mercurial > libavformat.hg
annotate mpeg.c @ 312:8a04d2e1be2f libavformat
frame rate should be completely disabled in asf (closer now) - disabled seek
| author | bellard |
|---|---|
| date | Mon, 10 Nov 2003 18:49:58 +0000 |
| parents | 944c8edaf609 |
| children | 4530681af424 |
| rev | line source |
|---|---|
| 0 | 1 /* |
| 2 * MPEG1/2 mux/demux | |
| 3 * Copyright (c) 2000, 2001, 2002 Fabrice Bellard. | |
| 4 * | |
| 5 * This library is free software; you can redistribute it and/or | |
| 6 * modify it under the terms of the GNU Lesser General Public | |
| 7 * License as published by the Free Software Foundation; either | |
| 8 * version 2 of the License, or (at your option) any later version. | |
| 9 * | |
| 10 * This library is distributed in the hope that it will be useful, | |
| 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 13 * Lesser General Public License for more details. | |
| 14 * | |
| 15 * You should have received a copy of the GNU Lesser General Public | |
| 16 * License along with this library; if not, write to the Free Software | |
| 17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
| 18 */ | |
| 19 #include "avformat.h" | |
| 20 | |
| 21 #define MAX_PAYLOAD_SIZE 4096 | |
| 22 #define NB_STREAMS 2 | |
| 310 | 23 //#define DEBUG_SEEK |
| 0 | 24 |
| 25 typedef struct { | |
| 65 | 26 uint8_t buffer[MAX_PAYLOAD_SIZE]; |
| 0 | 27 int buffer_ptr; |
| 65 | 28 uint8_t id; |
| 0 | 29 int max_buffer_size; /* in bytes */ |
| 30 int packet_number; | |
| 65 | 31 int64_t start_pts; |
| 0 | 32 } StreamInfo; |
| 33 | |
| 34 typedef struct { | |
| 35 int packet_size; /* required packet size */ | |
| 36 int packet_data_max_size; /* maximum data size inside a packet */ | |
| 37 int packet_number; | |
| 38 int pack_header_freq; /* frequency (in packets^-1) at which we send pack headers */ | |
| 39 int system_header_freq; | |
| 40 int mux_rate; /* bitrate in units of 50 bytes/s */ | |
| 41 /* stream info */ | |
| 42 int audio_bound; | |
| 43 int video_bound; | |
| 44 int is_mpeg2; | |
| 45 int is_vcd; | |
| 46 } MpegMuxContext; | |
| 47 | |
| 48 #define PACK_START_CODE ((unsigned int)0x000001ba) | |
| 49 #define SYSTEM_HEADER_START_CODE ((unsigned int)0x000001bb) | |
| 50 #define SEQUENCE_END_CODE ((unsigned int)0x000001b7) | |
| 51 #define PACKET_START_CODE_MASK ((unsigned int)0xffffff00) | |
| 52 #define PACKET_START_CODE_PREFIX ((unsigned int)0x00000100) | |
| 53 #define ISO_11172_END_CODE ((unsigned int)0x000001b9) | |
| 54 | |
| 55 /* mpeg2 */ | |
| 56 #define PROGRAM_STREAM_MAP 0x1bc | |
| 57 #define PRIVATE_STREAM_1 0x1bd | |
| 58 #define PADDING_STREAM 0x1be | |
| 59 #define PRIVATE_STREAM_2 0x1bf | |
| 60 | |
| 61 | |
| 62 #define AUDIO_ID 0xc0 | |
| 63 #define VIDEO_ID 0xe0 | |
| 64 | |
|
277
a313e1080322
disable encoders where appropriate (patch courtesy of BERO
melanson
parents:
276
diff
changeset
|
65 #ifdef CONFIG_ENCODERS |
| 0 | 66 extern AVOutputFormat mpeg1system_mux; |
| 67 extern AVOutputFormat mpeg1vcd_mux; | |
| 68 extern AVOutputFormat mpeg2vob_mux; | |
| 69 | |
| 70 static int put_pack_header(AVFormatContext *ctx, | |
| 65 | 71 uint8_t *buf, int64_t timestamp) |
| 0 | 72 { |
| 73 MpegMuxContext *s = ctx->priv_data; | |
| 74 PutBitContext pb; | |
| 75 | |
| 276 | 76 init_put_bits(&pb, buf, 128); |
| 0 | 77 |
| 78 put_bits(&pb, 32, PACK_START_CODE); | |
| 79 if (s->is_mpeg2) { | |
|
174
7d56e9f83fdb
Write correct MPEG2-PS streams patch by (mru at users dot sourceforge dot net (M?ns Rullg?rd))
michaelni
parents:
165
diff
changeset
|
80 put_bits(&pb, 2, 0x1); |
| 0 | 81 } else { |
| 82 put_bits(&pb, 4, 0x2); | |
| 83 } | |
| 65 | 84 put_bits(&pb, 3, (uint32_t)((timestamp >> 30) & 0x07)); |
| 0 | 85 put_bits(&pb, 1, 1); |
| 65 | 86 put_bits(&pb, 15, (uint32_t)((timestamp >> 15) & 0x7fff)); |
| 0 | 87 put_bits(&pb, 1, 1); |
| 65 | 88 put_bits(&pb, 15, (uint32_t)((timestamp) & 0x7fff)); |
| 0 | 89 put_bits(&pb, 1, 1); |
| 90 if (s->is_mpeg2) { | |
| 91 /* clock extension */ | |
| 92 put_bits(&pb, 9, 0); | |
| 93 put_bits(&pb, 1, 1); | |
| 94 } | |
| 95 put_bits(&pb, 1, 1); | |
| 96 put_bits(&pb, 22, s->mux_rate); | |
| 97 put_bits(&pb, 1, 1); | |
| 98 if (s->is_mpeg2) { | |
| 99 put_bits(&pb, 5, 0x1f); /* reserved */ | |
| 100 put_bits(&pb, 3, 0); /* stuffing length */ | |
| 101 } | |
| 102 flush_put_bits(&pb); | |
| 103 return pbBufPtr(&pb) - pb.buf; | |
| 104 } | |
| 105 | |
| 65 | 106 static int put_system_header(AVFormatContext *ctx, uint8_t *buf) |
| 0 | 107 { |
| 108 MpegMuxContext *s = ctx->priv_data; | |
| 109 int size, rate_bound, i, private_stream_coded, id; | |
| 110 PutBitContext pb; | |
| 111 | |
| 276 | 112 init_put_bits(&pb, buf, 128); |
| 0 | 113 |
| 114 put_bits(&pb, 32, SYSTEM_HEADER_START_CODE); | |
| 115 put_bits(&pb, 16, 0); | |
| 116 put_bits(&pb, 1, 1); | |
| 117 | |
| 118 rate_bound = s->mux_rate; /* maximum bit rate of the multiplexed stream */ | |
| 119 put_bits(&pb, 22, rate_bound); | |
| 120 put_bits(&pb, 1, 1); /* marker */ | |
| 121 put_bits(&pb, 6, s->audio_bound); | |
| 122 | |
| 123 put_bits(&pb, 1, 1); /* variable bitrate */ | |
| 124 put_bits(&pb, 1, 1); /* non constrainted bit stream */ | |
| 125 | |
| 126 put_bits(&pb, 1, 0); /* audio locked */ | |
| 127 put_bits(&pb, 1, 0); /* video locked */ | |
| 128 put_bits(&pb, 1, 1); /* marker */ | |
| 129 | |
| 130 put_bits(&pb, 5, s->video_bound); | |
| 131 put_bits(&pb, 8, 0xff); /* reserved byte */ | |
| 132 | |
| 133 /* audio stream info */ | |
| 134 private_stream_coded = 0; | |
| 135 for(i=0;i<ctx->nb_streams;i++) { | |
| 136 StreamInfo *stream = ctx->streams[i]->priv_data; | |
| 137 id = stream->id; | |
| 138 if (id < 0xc0) { | |
| 139 /* special case for private streams (AC3 use that) */ | |
| 140 if (private_stream_coded) | |
| 141 continue; | |
| 142 private_stream_coded = 1; | |
| 143 id = 0xbd; | |
| 144 } | |
| 145 put_bits(&pb, 8, id); /* stream ID */ | |
| 146 put_bits(&pb, 2, 3); | |
| 147 if (id < 0xe0) { | |
| 148 /* audio */ | |
| 149 put_bits(&pb, 1, 0); | |
| 150 put_bits(&pb, 13, stream->max_buffer_size / 128); | |
| 151 } else { | |
| 152 /* video */ | |
| 153 put_bits(&pb, 1, 1); | |
| 154 put_bits(&pb, 13, stream->max_buffer_size / 1024); | |
| 155 } | |
| 156 } | |
| 157 flush_put_bits(&pb); | |
| 158 size = pbBufPtr(&pb) - pb.buf; | |
| 159 /* patch packet size */ | |
| 160 buf[4] = (size - 6) >> 8; | |
| 161 buf[5] = (size - 6) & 0xff; | |
| 162 | |
| 163 return size; | |
| 164 } | |
| 165 | |
| 166 static int mpeg_mux_init(AVFormatContext *ctx) | |
| 167 { | |
| 168 MpegMuxContext *s = ctx->priv_data; | |
| 169 int bitrate, i, mpa_id, mpv_id, ac3_id; | |
| 170 AVStream *st; | |
| 171 StreamInfo *stream; | |
| 172 | |
| 173 s->packet_number = 0; | |
| 174 s->is_vcd = (ctx->oformat == &mpeg1vcd_mux); | |
| 175 s->is_mpeg2 = (ctx->oformat == &mpeg2vob_mux); | |
| 176 | |
| 177 if (s->is_vcd) | |
| 178 s->packet_size = 2324; /* VCD packet size */ | |
| 179 else | |
| 180 s->packet_size = 2048; | |
| 181 | |
| 182 /* startcode(4) + length(2) + flags(1) */ | |
| 183 s->packet_data_max_size = s->packet_size - 7; | |
| 184 s->audio_bound = 0; | |
| 185 s->video_bound = 0; | |
| 186 mpa_id = AUDIO_ID; | |
| 187 ac3_id = 0x80; | |
| 188 mpv_id = VIDEO_ID; | |
| 189 for(i=0;i<ctx->nb_streams;i++) { | |
| 190 st = ctx->streams[i]; | |
| 191 stream = av_mallocz(sizeof(StreamInfo)); | |
| 192 if (!stream) | |
| 193 goto fail; | |
| 194 st->priv_data = stream; | |
| 195 | |
| 196 switch(st->codec.codec_type) { | |
| 197 case CODEC_TYPE_AUDIO: | |
| 198 if (st->codec.codec_id == CODEC_ID_AC3) | |
| 199 stream->id = ac3_id++; | |
| 200 else | |
| 201 stream->id = mpa_id++; | |
| 202 stream->max_buffer_size = 4 * 1024; | |
| 203 s->audio_bound++; | |
| 204 break; | |
| 205 case CODEC_TYPE_VIDEO: | |
| 206 stream->id = mpv_id++; | |
| 207 stream->max_buffer_size = 46 * 1024; | |
| 208 s->video_bound++; | |
| 209 break; | |
| 210 default: | |
| 211 av_abort(); | |
| 212 } | |
| 213 } | |
| 214 | |
| 215 /* we increase slightly the bitrate to take into account the | |
| 216 headers. XXX: compute it exactly */ | |
| 217 bitrate = 2000; | |
| 218 for(i=0;i<ctx->nb_streams;i++) { | |
| 219 st = ctx->streams[i]; | |
| 220 bitrate += st->codec.bit_rate; | |
| 221 } | |
| 222 s->mux_rate = (bitrate + (8 * 50) - 1) / (8 * 50); | |
| 223 | |
| 224 if (s->is_vcd || s->is_mpeg2) | |
| 225 /* every packet */ | |
| 226 s->pack_header_freq = 1; | |
| 227 else | |
| 228 /* every 2 seconds */ | |
| 229 s->pack_header_freq = 2 * bitrate / s->packet_size / 8; | |
|
291
b19f70a6d60f
1/0 fix by (Tim Allen <tim at proximity dot com dot au>)
michael
parents:
277
diff
changeset
|
230 |
|
b19f70a6d60f
1/0 fix by (Tim Allen <tim at proximity dot com dot au>)
michael
parents:
277
diff
changeset
|
231 /* the above seems to make pack_header_freq zero sometimes */ |
|
b19f70a6d60f
1/0 fix by (Tim Allen <tim at proximity dot com dot au>)
michael
parents:
277
diff
changeset
|
232 if (s->pack_header_freq == 0) |
|
b19f70a6d60f
1/0 fix by (Tim Allen <tim at proximity dot com dot au>)
michael
parents:
277
diff
changeset
|
233 s->pack_header_freq = 1; |
| 0 | 234 |
| 235 if (s->is_mpeg2) | |
| 236 /* every 200 packets. Need to look at the spec. */ | |
| 237 s->system_header_freq = s->pack_header_freq * 40; | |
| 238 else if (s->is_vcd) | |
| 239 /* every 40 packets, this is my invention */ | |
| 240 s->system_header_freq = s->pack_header_freq * 40; | |
| 241 else | |
| 242 s->system_header_freq = s->pack_header_freq * 5; | |
| 243 | |
| 244 for(i=0;i<ctx->nb_streams;i++) { | |
| 245 stream = ctx->streams[i]->priv_data; | |
| 246 stream->buffer_ptr = 0; | |
| 247 stream->packet_number = 0; | |
| 248 stream->start_pts = -1; | |
| 249 } | |
| 250 return 0; | |
| 251 fail: | |
| 252 for(i=0;i<ctx->nb_streams;i++) { | |
| 253 av_free(ctx->streams[i]->priv_data); | |
| 254 } | |
| 255 return -ENOMEM; | |
| 256 } | |
| 257 | |
| 258 /* flush the packet on stream stream_index */ | |
| 242 | 259 static void flush_packet(AVFormatContext *ctx, int stream_index) |
| 0 | 260 { |
| 261 MpegMuxContext *s = ctx->priv_data; | |
| 262 StreamInfo *stream = ctx->streams[stream_index]->priv_data; | |
| 65 | 263 uint8_t *buf_ptr; |
| 0 | 264 int size, payload_size, startcode, id, len, stuffing_size, i, header_len; |
| 65 | 265 int64_t timestamp; |
| 266 uint8_t buffer[128]; | |
| 0 | 267 |
| 268 id = stream->id; | |
| 269 timestamp = stream->start_pts; | |
| 270 | |
| 271 #if 0 | |
| 272 printf("packet ID=%2x PTS=%0.3f\n", | |
| 273 id, timestamp / 90000.0); | |
| 274 #endif | |
| 275 | |
| 276 buf_ptr = buffer; | |
| 277 if (((s->packet_number % s->pack_header_freq) == 0)) { | |
| 278 /* output pack and systems header if needed */ | |
| 279 size = put_pack_header(ctx, buf_ptr, timestamp); | |
| 280 buf_ptr += size; | |
| 281 if ((s->packet_number % s->system_header_freq) == 0) { | |
| 282 size = put_system_header(ctx, buf_ptr); | |
| 283 buf_ptr += size; | |
| 284 } | |
| 285 } | |
| 286 size = buf_ptr - buffer; | |
| 287 put_buffer(&ctx->pb, buffer, size); | |
| 288 | |
| 289 /* packet header */ | |
| 290 if (s->is_mpeg2) { | |
| 291 header_len = 8; | |
| 292 } else { | |
| 293 header_len = 5; | |
| 294 } | |
| 242 | 295 payload_size = s->packet_size - (size + 6 + header_len); |
| 0 | 296 if (id < 0xc0) { |
| 297 startcode = PRIVATE_STREAM_1; | |
| 298 payload_size -= 4; | |
| 299 } else { | |
| 300 startcode = 0x100 + id; | |
| 301 } | |
| 302 stuffing_size = payload_size - stream->buffer_ptr; | |
| 303 if (stuffing_size < 0) | |
| 304 stuffing_size = 0; | |
| 305 | |
| 306 put_be32(&ctx->pb, startcode); | |
| 307 | |
| 308 put_be16(&ctx->pb, payload_size + header_len); | |
| 309 /* stuffing */ | |
| 310 for(i=0;i<stuffing_size;i++) | |
| 311 put_byte(&ctx->pb, 0xff); | |
| 312 | |
| 313 if (s->is_mpeg2) { | |
| 314 put_byte(&ctx->pb, 0x80); /* mpeg2 id */ | |
| 315 put_byte(&ctx->pb, 0x80); /* flags */ | |
| 316 put_byte(&ctx->pb, 0x05); /* header len (only pts is included) */ | |
| 317 } | |
| 318 put_byte(&ctx->pb, | |
| 319 (0x02 << 4) | | |
| 320 (((timestamp >> 30) & 0x07) << 1) | | |
| 321 1); | |
| 65 | 322 put_be16(&ctx->pb, (uint16_t)((((timestamp >> 15) & 0x7fff) << 1) | 1)); |
| 323 put_be16(&ctx->pb, (uint16_t)((((timestamp) & 0x7fff) << 1) | 1)); | |
| 0 | 324 |
| 325 if (startcode == PRIVATE_STREAM_1) { | |
| 326 put_byte(&ctx->pb, id); | |
| 327 if (id >= 0x80 && id <= 0xbf) { | |
| 328 /* XXX: need to check AC3 spec */ | |
| 329 put_byte(&ctx->pb, 1); | |
| 330 put_byte(&ctx->pb, 0); | |
| 331 put_byte(&ctx->pb, 2); | |
| 332 } | |
| 333 } | |
| 334 | |
| 335 /* output data */ | |
| 336 put_buffer(&ctx->pb, stream->buffer, payload_size - stuffing_size); | |
| 337 put_flush_packet(&ctx->pb); | |
| 338 | |
| 339 /* preserve remaining data */ | |
| 340 len = stream->buffer_ptr - payload_size; | |
| 341 if (len < 0) | |
| 342 len = 0; | |
| 343 memmove(stream->buffer, stream->buffer + stream->buffer_ptr - len, len); | |
| 344 stream->buffer_ptr = len; | |
| 345 | |
| 346 s->packet_number++; | |
| 347 stream->packet_number++; | |
| 348 stream->start_pts = -1; | |
| 349 } | |
| 350 | |
| 351 static int mpeg_mux_write_packet(AVFormatContext *ctx, int stream_index, | |
| 241 | 352 const uint8_t *buf, int size, int64_t pts) |
| 0 | 353 { |
| 354 MpegMuxContext *s = ctx->priv_data; | |
| 355 AVStream *st = ctx->streams[stream_index]; | |
| 356 StreamInfo *stream = st->priv_data; | |
| 357 int len; | |
| 358 | |
| 359 while (size > 0) { | |
| 360 /* set pts */ | |
| 361 if (stream->start_pts == -1) { | |
| 362 stream->start_pts = pts; | |
| 363 } | |
| 364 len = s->packet_data_max_size - stream->buffer_ptr; | |
| 365 if (len > size) | |
| 366 len = size; | |
| 367 memcpy(stream->buffer + stream->buffer_ptr, buf, len); | |
| 368 stream->buffer_ptr += len; | |
| 369 buf += len; | |
| 370 size -= len; | |
| 371 while (stream->buffer_ptr >= s->packet_data_max_size) { | |
| 372 /* output the packet */ | |
| 373 if (stream->start_pts == -1) | |
| 374 stream->start_pts = pts; | |
| 242 | 375 flush_packet(ctx, stream_index); |
| 0 | 376 } |
| 377 } | |
| 378 return 0; | |
| 379 } | |
| 380 | |
| 381 static int mpeg_mux_end(AVFormatContext *ctx) | |
| 382 { | |
| 383 StreamInfo *stream; | |
| 384 int i; | |
| 385 | |
| 386 /* flush each packet */ | |
| 387 for(i=0;i<ctx->nb_streams;i++) { | |
| 388 stream = ctx->streams[i]->priv_data; | |
| 389 if (stream->buffer_ptr > 0) { | |
| 242 | 390 flush_packet(ctx, i); |
| 0 | 391 } |
| 392 } | |
| 393 | |
| 242 | 394 /* End header according to MPEG1 systems standard. We do not write |
| 395 it as it is usually not needed by decoders and because it | |
| 396 complicates MPEG stream concatenation. */ | |
| 0 | 397 //put_be32(&ctx->pb, ISO_11172_END_CODE); |
| 398 //put_flush_packet(&ctx->pb); | |
|
237
35231c0be8e5
memleak fix by (Michel Bardiaux <mbardiaux at peaktime dot be>)
michaelni
parents:
210
diff
changeset
|
399 |
|
35231c0be8e5
memleak fix by (Michel Bardiaux <mbardiaux at peaktime dot be>)
michaelni
parents:
210
diff
changeset
|
400 for(i=0;i<ctx->nb_streams;i++) |
|
35231c0be8e5
memleak fix by (Michel Bardiaux <mbardiaux at peaktime dot be>)
michaelni
parents:
210
diff
changeset
|
401 av_freep(&ctx->streams[i]->priv_data); |
|
35231c0be8e5
memleak fix by (Michel Bardiaux <mbardiaux at peaktime dot be>)
michaelni
parents:
210
diff
changeset
|
402 |
| 0 | 403 return 0; |
| 404 } | |
|
277
a313e1080322
disable encoders where appropriate (patch courtesy of BERO
melanson
parents:
276
diff
changeset
|
405 #endif //CONFIG_ENCODERS |
| 0 | 406 |
| 407 /*********************************************/ | |
| 408 /* demux code */ | |
| 409 | |
| 410 #define MAX_SYNC_SIZE 100000 | |
| 411 | |
| 412 static int mpegps_probe(AVProbeData *p) | |
| 413 { | |
|
165
e4d2f704bf80
- Looks a tiny bit harder in mpegps_probe() for a valid start code. This is
michaelni
parents:
65
diff
changeset
|
414 int code, c, i; |
| 0 | 415 |
|
165
e4d2f704bf80
- Looks a tiny bit harder in mpegps_probe() for a valid start code. This is
michaelni
parents:
65
diff
changeset
|
416 code = 0xff; |
| 0 | 417 /* we search the first start code. If it is a packet start code, |
| 418 then we decide it is mpeg ps. We do not send highest value to | |
| 419 give a chance to mpegts */ | |
| 49 | 420 /* NOTE: the search range was restricted to avoid too many false |
| 421 detections */ | |
| 422 | |
| 423 if (p->buf_size < 6) | |
| 424 return 0; | |
|
165
e4d2f704bf80
- Looks a tiny bit harder in mpegps_probe() for a valid start code. This is
michaelni
parents:
65
diff
changeset
|
425 |
|
e4d2f704bf80
- Looks a tiny bit harder in mpegps_probe() for a valid start code. This is
michaelni
parents:
65
diff
changeset
|
426 for (i = 0; i < 20; i++) { |
|
e4d2f704bf80
- Looks a tiny bit harder in mpegps_probe() for a valid start code. This is
michaelni
parents:
65
diff
changeset
|
427 c = p->buf[i]; |
|
e4d2f704bf80
- Looks a tiny bit harder in mpegps_probe() for a valid start code. This is
michaelni
parents:
65
diff
changeset
|
428 code = (code << 8) | c; |
|
e4d2f704bf80
- Looks a tiny bit harder in mpegps_probe() for a valid start code. This is
michaelni
parents:
65
diff
changeset
|
429 if ((code & 0xffffff00) == 0x100) { |
|
e4d2f704bf80
- Looks a tiny bit harder in mpegps_probe() for a valid start code. This is
michaelni
parents:
65
diff
changeset
|
430 if (code == PACK_START_CODE || |
|
e4d2f704bf80
- Looks a tiny bit harder in mpegps_probe() for a valid start code. This is
michaelni
parents:
65
diff
changeset
|
431 code == SYSTEM_HEADER_START_CODE || |
|
e4d2f704bf80
- Looks a tiny bit harder in mpegps_probe() for a valid start code. This is
michaelni
parents:
65
diff
changeset
|
432 (code >= 0x1e0 && code <= 0x1ef) || |
|
e4d2f704bf80
- Looks a tiny bit harder in mpegps_probe() for a valid start code. This is
michaelni
parents:
65
diff
changeset
|
433 (code >= 0x1c0 && code <= 0x1df) || |
|
e4d2f704bf80
- Looks a tiny bit harder in mpegps_probe() for a valid start code. This is
michaelni
parents:
65
diff
changeset
|
434 code == PRIVATE_STREAM_2 || |
|
e4d2f704bf80
- Looks a tiny bit harder in mpegps_probe() for a valid start code. This is
michaelni
parents:
65
diff
changeset
|
435 code == PROGRAM_STREAM_MAP || |
|
e4d2f704bf80
- Looks a tiny bit harder in mpegps_probe() for a valid start code. This is
michaelni
parents:
65
diff
changeset
|
436 code == PRIVATE_STREAM_1 || |
|
e4d2f704bf80
- Looks a tiny bit harder in mpegps_probe() for a valid start code. This is
michaelni
parents:
65
diff
changeset
|
437 code == PADDING_STREAM) |
| 210 | 438 return AVPROBE_SCORE_MAX - 2; |
|
165
e4d2f704bf80
- Looks a tiny bit harder in mpegps_probe() for a valid start code. This is
michaelni
parents:
65
diff
changeset
|
439 else |
|
e4d2f704bf80
- Looks a tiny bit harder in mpegps_probe() for a valid start code. This is
michaelni
parents:
65
diff
changeset
|
440 return 0; |
|
e4d2f704bf80
- Looks a tiny bit harder in mpegps_probe() for a valid start code. This is
michaelni
parents:
65
diff
changeset
|
441 } |
| 0 | 442 } |
| 443 return 0; | |
| 444 } | |
| 445 | |
| 446 | |
| 447 typedef struct MpegDemuxContext { | |
| 448 int header_state; | |
| 449 } MpegDemuxContext; | |
| 450 | |
| 310 | 451 static int mpegps_read_header(AVFormatContext *s, |
| 452 AVFormatParameters *ap) | |
| 453 { | |
| 454 MpegDemuxContext *m = s->priv_data; | |
| 455 m->header_state = 0xff; | |
| 456 s->ctx_flags |= AVFMTCTX_NOHEADER; | |
| 457 | |
| 458 /* no need to do more */ | |
| 459 return 0; | |
| 460 } | |
| 461 | |
| 462 static int64_t get_pts(ByteIOContext *pb, int c) | |
| 463 { | |
| 464 int64_t pts; | |
| 465 int val; | |
| 466 | |
| 467 if (c < 0) | |
| 468 c = get_byte(pb); | |
| 469 pts = (int64_t)((c >> 1) & 0x07) << 30; | |
| 470 val = get_be16(pb); | |
| 471 pts |= (int64_t)(val >> 1) << 15; | |
| 472 val = get_be16(pb); | |
| 473 pts |= (int64_t)(val >> 1); | |
| 474 return pts; | |
| 475 } | |
| 476 | |
| 477 static int find_next_start_code(ByteIOContext *pb, int *size_ptr, | |
| 478 uint32_t *header_state) | |
| 0 | 479 { |
| 480 unsigned int state, v; | |
| 481 int val, n; | |
| 482 | |
| 483 state = *header_state; | |
| 484 n = *size_ptr; | |
| 485 while (n > 0) { | |
| 486 if (url_feof(pb)) | |
| 487 break; | |
| 488 v = get_byte(pb); | |
| 489 n--; | |
| 490 if (state == 0x000001) { | |
| 491 state = ((state << 8) | v) & 0xffffff; | |
| 492 val = state; | |
| 493 goto found; | |
| 494 } | |
| 495 state = ((state << 8) | v) & 0xffffff; | |
| 496 } | |
| 497 val = -1; | |
| 498 found: | |
| 499 *header_state = state; | |
| 500 *size_ptr = n; | |
| 501 return val; | |
| 502 } | |
| 503 | |
| 310 | 504 /* XXX: optimize */ |
| 505 static int find_prev_start_code(ByteIOContext *pb, int *size_ptr) | |
| 0 | 506 { |
| 310 | 507 int64_t pos, pos_start; |
| 508 int max_size, start_code; | |
| 509 | |
| 510 max_size = *size_ptr; | |
| 511 pos_start = url_ftell(pb); | |
| 512 | |
| 513 /* in order to go faster, we fill the buffer */ | |
| 514 pos = pos_start - 16386; | |
| 515 if (pos < 0) | |
| 516 pos = 0; | |
| 517 url_fseek(pb, pos, SEEK_SET); | |
| 518 get_byte(pb); | |
|
293
62cec412a186
make AVFMT_NOHEADER flag dynamic - added av_open_input_stream()
bellard
parents:
291
diff
changeset
|
519 |
| 310 | 520 pos = pos_start; |
| 521 for(;;) { | |
| 522 pos--; | |
| 523 if (pos < 0 || (pos_start - pos) >= max_size) { | |
| 524 start_code = -1; | |
| 525 goto the_end; | |
| 526 } | |
| 527 url_fseek(pb, pos, SEEK_SET); | |
| 528 start_code = get_be32(pb); | |
| 529 if ((start_code & 0xffffff00) == 0x100) | |
| 530 break; | |
| 531 } | |
| 532 the_end: | |
| 533 *size_ptr = pos_start - pos; | |
| 534 return start_code; | |
| 0 | 535 } |
| 536 | |
| 310 | 537 /* read the next (or previous) PES header. Return its position in ppos |
| 538 (if not NULL), and its start code, pts and dts. | |
| 539 */ | |
| 540 static int mpegps_read_pes_header(AVFormatContext *s, | |
| 541 int64_t *ppos, int *pstart_code, | |
| 542 int64_t *ppts, int64_t *pdts, int find_next) | |
| 0 | 543 { |
| 544 MpegDemuxContext *m = s->priv_data; | |
| 310 | 545 int len, size, startcode, c, flags, header_len; |
| 546 int64_t pts, dts, last_pos; | |
| 0 | 547 |
| 310 | 548 last_pos = -1; |
| 0 | 549 redo: |
| 310 | 550 if (find_next) { |
| 551 /* next start code (should be immediately after) */ | |
| 552 m->header_state = 0xff; | |
| 553 size = MAX_SYNC_SIZE; | |
| 554 startcode = find_next_start_code(&s->pb, &size, &m->header_state); | |
| 555 } else { | |
| 556 if (last_pos >= 0) | |
| 557 url_fseek(&s->pb, last_pos, SEEK_SET); | |
| 558 size = MAX_SYNC_SIZE; | |
| 559 startcode = find_prev_start_code(&s->pb, &size); | |
| 560 last_pos = url_ftell(&s->pb) - 4; | |
| 561 } | |
| 0 | 562 //printf("startcode=%x pos=0x%Lx\n", startcode, url_ftell(&s->pb)); |
| 563 if (startcode < 0) | |
| 564 return -EIO; | |
| 565 if (startcode == PACK_START_CODE) | |
| 566 goto redo; | |
| 567 if (startcode == SYSTEM_HEADER_START_CODE) | |
| 568 goto redo; | |
| 569 if (startcode == PADDING_STREAM || | |
| 570 startcode == PRIVATE_STREAM_2) { | |
| 571 /* skip them */ | |
| 572 len = get_be16(&s->pb); | |
| 573 url_fskip(&s->pb, len); | |
| 574 goto redo; | |
| 575 } | |
| 576 /* find matching stream */ | |
| 577 if (!((startcode >= 0x1c0 && startcode <= 0x1df) || | |
| 578 (startcode >= 0x1e0 && startcode <= 0x1ef) || | |
| 579 (startcode == 0x1bd))) | |
| 580 goto redo; | |
| 310 | 581 if (ppos) { |
| 582 *ppos = url_ftell(&s->pb) - 4; | |
| 583 } | |
| 0 | 584 len = get_be16(&s->pb); |
| 585 pts = AV_NOPTS_VALUE; | |
| 586 dts = AV_NOPTS_VALUE; | |
| 587 /* stuffing */ | |
| 588 for(;;) { | |
| 310 | 589 if (len < 1) |
| 590 goto redo; | |
| 0 | 591 c = get_byte(&s->pb); |
| 592 len--; | |
| 593 /* XXX: for mpeg1, should test only bit 7 */ | |
| 594 if (c != 0xff) | |
| 595 break; | |
| 596 } | |
| 597 if ((c & 0xc0) == 0x40) { | |
| 598 /* buffer scale & size */ | |
| 310 | 599 if (len < 2) |
| 600 goto redo; | |
| 0 | 601 get_byte(&s->pb); |
| 602 c = get_byte(&s->pb); | |
| 603 len -= 2; | |
| 604 } | |
| 605 if ((c & 0xf0) == 0x20) { | |
| 310 | 606 if (len < 4) |
| 607 goto redo; | |
| 608 dts = pts = get_pts(&s->pb, c); | |
| 0 | 609 len -= 4; |
| 610 } else if ((c & 0xf0) == 0x30) { | |
| 310 | 611 if (len < 9) |
| 612 goto redo; | |
| 0 | 613 pts = get_pts(&s->pb, c); |
| 614 dts = get_pts(&s->pb, -1); | |
| 615 len -= 9; | |
| 616 } else if ((c & 0xc0) == 0x80) { | |
| 617 /* mpeg 2 PES */ | |
| 618 if ((c & 0x30) != 0) { | |
| 310 | 619 /* Encrypted multiplex not handled */ |
| 620 goto redo; | |
| 0 | 621 } |
| 622 flags = get_byte(&s->pb); | |
| 623 header_len = get_byte(&s->pb); | |
| 624 len -= 2; | |
| 625 if (header_len > len) | |
| 626 goto redo; | |
| 627 if ((flags & 0xc0) == 0x80) { | |
| 310 | 628 dts = pts = get_pts(&s->pb, -1); |
| 629 if (header_len < 5) | |
| 630 goto redo; | |
| 0 | 631 header_len -= 5; |
| 632 len -= 5; | |
| 633 } if ((flags & 0xc0) == 0xc0) { | |
| 634 pts = get_pts(&s->pb, -1); | |
| 635 dts = get_pts(&s->pb, -1); | |
| 310 | 636 if (header_len < 10) |
| 637 goto redo; | |
| 0 | 638 header_len -= 10; |
| 639 len -= 10; | |
| 640 } | |
| 641 len -= header_len; | |
| 642 while (header_len > 0) { | |
| 643 get_byte(&s->pb); | |
| 644 header_len--; | |
| 645 } | |
| 646 } | |
| 647 if (startcode == 0x1bd) { | |
| 310 | 648 if (len < 1) |
| 649 goto redo; | |
| 0 | 650 startcode = get_byte(&s->pb); |
| 651 len--; | |
| 652 if (startcode >= 0x80 && startcode <= 0xbf) { | |
| 653 /* audio: skip header */ | |
| 310 | 654 if (len < 3) |
| 655 goto redo; | |
| 0 | 656 get_byte(&s->pb); |
| 657 get_byte(&s->pb); | |
| 658 get_byte(&s->pb); | |
| 659 len -= 3; | |
| 660 } | |
| 661 } | |
| 310 | 662 *pstart_code = startcode; |
| 663 *ppts = pts; | |
| 664 *pdts = dts; | |
| 665 return len; | |
| 666 } | |
| 667 | |
| 668 static int mpegps_read_packet(AVFormatContext *s, | |
| 669 AVPacket *pkt) | |
| 670 { | |
| 671 AVStream *st; | |
| 672 int len, startcode, i, type, codec_id; | |
| 673 int64_t pts, dts; | |
| 674 | |
| 675 redo: | |
| 676 len = mpegps_read_pes_header(s, NULL, &startcode, &pts, &dts, 1); | |
| 677 if (len < 0) | |
| 678 return len; | |
| 0 | 679 |
| 680 /* now find stream */ | |
| 681 for(i=0;i<s->nb_streams;i++) { | |
| 682 st = s->streams[i]; | |
| 683 if (st->id == startcode) | |
| 684 goto found; | |
| 685 } | |
| 686 if (startcode >= 0x1e0 && startcode <= 0x1ef) { | |
| 687 type = CODEC_TYPE_VIDEO; | |
| 688 codec_id = CODEC_ID_MPEG1VIDEO; | |
| 689 } else if (startcode >= 0x1c0 && startcode <= 0x1df) { | |
| 690 type = CODEC_TYPE_AUDIO; | |
| 691 codec_id = CODEC_ID_MP2; | |
| 692 } else if (startcode >= 0x80 && startcode <= 0x9f) { | |
| 693 type = CODEC_TYPE_AUDIO; | |
| 694 codec_id = CODEC_ID_AC3; | |
| 41 | 695 } else if (startcode >= 0xa0 && startcode <= 0xbf) { |
| 696 type = CODEC_TYPE_AUDIO; | |
| 697 codec_id = CODEC_ID_PCM_S16BE; | |
| 0 | 698 } else { |
| 699 skip: | |
| 700 /* skip packet */ | |
| 701 url_fskip(&s->pb, len); | |
| 702 goto redo; | |
| 703 } | |
| 704 /* no stream found: add a new stream */ | |
| 705 st = av_new_stream(s, startcode); | |
| 706 if (!st) | |
| 707 goto skip; | |
| 708 st->codec.codec_type = type; | |
| 709 st->codec.codec_id = codec_id; | |
| 310 | 710 if (codec_id != CODEC_ID_PCM_S16BE) |
| 711 st->need_parsing = 1; | |
| 0 | 712 found: |
| 41 | 713 if (startcode >= 0xa0 && startcode <= 0xbf) { |
| 714 int b1, freq; | |
| 715 static const int lpcm_freq_tab[4] = { 48000, 96000, 44100, 32000 }; | |
| 716 | |
| 717 /* for LPCM, we just skip the header and consider it is raw | |
| 718 audio data */ | |
| 719 if (len <= 3) | |
| 720 goto skip; | |
| 721 get_byte(&s->pb); /* emphasis (1), muse(1), reserved(1), frame number(5) */ | |
| 722 b1 = get_byte(&s->pb); /* quant (2), freq(2), reserved(1), channels(3) */ | |
| 723 get_byte(&s->pb); /* dynamic range control (0x80 = off) */ | |
| 724 len -= 3; | |
| 725 freq = (b1 >> 4) & 3; | |
| 726 st->codec.sample_rate = lpcm_freq_tab[freq]; | |
| 727 st->codec.channels = 1 + (b1 & 7); | |
| 728 st->codec.bit_rate = st->codec.channels * st->codec.sample_rate * 2; | |
| 729 } | |
| 0 | 730 av_new_packet(pkt, len); |
| 731 //printf("\nRead Packet ID: %x PTS: %f Size: %d", startcode, | |
| 732 // (float)pts/90000, len); | |
| 733 get_buffer(&s->pb, pkt->data, pkt->size); | |
| 734 pkt->pts = pts; | |
| 310 | 735 pkt->dts = dts; |
| 0 | 736 pkt->stream_index = st->index; |
| 737 return 0; | |
| 738 } | |
| 739 | |
| 740 static int mpegps_read_close(AVFormatContext *s) | |
| 741 { | |
| 742 return 0; | |
| 743 } | |
| 744 | |
| 310 | 745 static int64_t mpegps_read_dts(AVFormatContext *s, int stream_index, |
| 746 int64_t *ppos, int find_next) | |
| 747 { | |
| 748 int len, startcode; | |
| 749 int64_t pos, pts, dts; | |
| 750 | |
| 751 pos = *ppos; | |
| 752 #ifdef DEBUG_SEEK | |
| 753 printf("read_dts: pos=0x%llx next=%d -> ", pos, find_next); | |
| 754 #endif | |
| 755 url_fseek(&s->pb, pos, SEEK_SET); | |
| 756 for(;;) { | |
| 757 len = mpegps_read_pes_header(s, &pos, &startcode, &pts, &dts, find_next); | |
| 758 if (len < 0) { | |
| 759 #ifdef DEBUG_SEEK | |
| 760 printf("none (ret=%d)\n", len); | |
| 761 #endif | |
| 762 return AV_NOPTS_VALUE; | |
| 763 } | |
| 764 if (startcode == s->streams[stream_index]->id && | |
| 765 dts != AV_NOPTS_VALUE) { | |
| 766 break; | |
| 767 } | |
| 768 if (find_next) { | |
| 769 url_fskip(&s->pb, len); | |
| 770 } else { | |
| 771 url_fseek(&s->pb, pos, SEEK_SET); | |
| 772 } | |
| 773 } | |
| 774 #ifdef DEBUG_SEEK | |
| 775 printf("pos=0x%llx dts=0x%llx %0.3f\n", pos, dts, dts / 90000.0); | |
| 776 #endif | |
| 777 *ppos = pos; | |
| 778 return dts; | |
| 779 } | |
| 780 | |
| 781 static int find_stream_index(AVFormatContext *s) | |
| 782 { | |
| 783 int i; | |
| 784 AVStream *st; | |
| 785 | |
| 786 if (s->nb_streams <= 0) | |
| 787 return -1; | |
| 788 for(i = 0; i < s->nb_streams; i++) { | |
| 789 st = s->streams[i]; | |
| 790 if (st->codec.codec_type == CODEC_TYPE_VIDEO) { | |
| 791 return i; | |
| 792 } | |
| 793 } | |
| 794 return 0; | |
| 795 } | |
| 796 | |
| 797 static int mpegps_read_seek(AVFormatContext *s, | |
| 798 int stream_index, int64_t timestamp) | |
| 799 { | |
| 800 int64_t pos_min, pos_max, pos; | |
| 801 int64_t dts_min, dts_max, dts; | |
| 802 | |
| 803 timestamp = (timestamp * 90000) / AV_TIME_BASE; | |
| 804 | |
| 805 #ifdef DEBUG_SEEK | |
| 806 printf("read_seek: %d %0.3f\n", stream_index, timestamp / 90000.0); | |
| 807 #endif | |
| 808 | |
| 809 /* XXX: find stream_index by looking at the first PES packet found */ | |
| 810 if (stream_index < 0) { | |
| 811 stream_index = find_stream_index(s); | |
| 812 if (stream_index < 0) | |
| 813 return -1; | |
| 814 } | |
| 815 pos_min = 0; | |
| 816 dts_min = mpegps_read_dts(s, stream_index, &pos_min, 1); | |
| 817 if (dts_min == AV_NOPTS_VALUE) { | |
| 818 /* we can reach this case only if no PTS are present in | |
| 819 the whole stream */ | |
| 820 return -1; | |
| 821 } | |
| 822 pos_max = url_filesize(url_fileno(&s->pb)) - 1; | |
| 823 dts_max = mpegps_read_dts(s, stream_index, &pos_max, 0); | |
| 824 | |
| 825 while (pos_min <= pos_max) { | |
| 826 #ifdef DEBUG_SEEK | |
| 827 printf("pos_min=0x%llx pos_max=0x%llx dts_min=%0.3f dts_max=%0.3f\n", | |
| 828 pos_min, pos_max, | |
| 829 dts_min / 90000.0, dts_max / 90000.0); | |
| 830 #endif | |
| 831 if (timestamp <= dts_min) { | |
| 832 pos = pos_min; | |
| 833 goto found; | |
| 834 } else if (timestamp >= dts_max) { | |
| 835 pos = pos_max; | |
| 836 goto found; | |
| 837 } else { | |
| 838 /* interpolate position (better than dichotomy) */ | |
| 839 pos = (int64_t)((double)(pos_max - pos_min) * | |
| 840 (double)(timestamp - dts_min) / | |
| 841 (double)(dts_max - dts_min)) + pos_min; | |
| 842 } | |
| 843 #ifdef DEBUG_SEEK | |
| 844 printf("pos=0x%llx\n", pos); | |
| 845 #endif | |
| 846 /* read the next timestamp */ | |
| 847 dts = mpegps_read_dts(s, stream_index, &pos, 1); | |
| 848 /* check if we are lucky */ | |
| 849 if (dts == AV_NOPTS_VALUE) { | |
| 850 /* should never happen */ | |
| 851 pos = pos_min; | |
| 852 goto found; | |
| 853 } else if (timestamp == dts) { | |
| 854 goto found; | |
| 855 } else if (timestamp < dts) { | |
| 856 pos_max = pos; | |
| 857 dts_max = mpegps_read_dts(s, stream_index, &pos_max, 0); | |
| 858 if (dts_max == AV_NOPTS_VALUE) { | |
| 859 /* should never happen */ | |
| 860 break; | |
| 861 } else if (timestamp >= dts_max) { | |
| 862 pos = pos_max; | |
| 863 goto found; | |
| 864 } | |
| 865 } else { | |
| 866 pos_min = pos + 1; | |
| 867 dts_min = mpegps_read_dts(s, stream_index, &pos_min, 1); | |
| 868 if (dts_min == AV_NOPTS_VALUE) { | |
| 869 /* should never happen */ | |
| 870 goto found; | |
| 871 } else if (timestamp <= dts_min) { | |
| 872 goto found; | |
| 873 } | |
| 874 } | |
| 875 } | |
| 876 pos = pos_min; | |
| 877 found: | |
| 878 #ifdef DEBUG_SEEK | |
| 879 pos_min = pos; | |
| 880 dts_min = mpegps_read_dts(s, stream_index, &pos_min, 1); | |
| 881 pos_min++; | |
| 882 dts_max = mpegps_read_dts(s, stream_index, &pos_min, 1); | |
| 883 printf("pos=0x%llx %0.3f<=%0.3f<=%0.3f\n", | |
| 884 pos, dts_min / 90000.0, timestamp / 90000.0, dts_max / 90000.0); | |
| 885 #endif | |
| 886 /* do the seek */ | |
| 887 url_fseek(&s->pb, pos, SEEK_SET); | |
| 888 return 0; | |
| 889 } | |
| 890 | |
|
277
a313e1080322
disable encoders where appropriate (patch courtesy of BERO
melanson
parents:
276
diff
changeset
|
891 #ifdef CONFIG_ENCODERS |
| 0 | 892 static AVOutputFormat mpeg1system_mux = { |
| 893 "mpeg", | |
| 894 "MPEG1 System format", | |
|
14
b167760cd0aa
mimetype fixes patch by (Ryutaroh Matsumoto <ryutaroh at it dot ss dot titech dot ac dot jp>)
michaelni
parents:
0
diff
changeset
|
895 "video/mpeg", |
| 0 | 896 "mpg,mpeg", |
| 897 sizeof(MpegMuxContext), | |
| 898 CODEC_ID_MP2, | |
| 899 CODEC_ID_MPEG1VIDEO, | |
| 900 mpeg_mux_init, | |
| 901 mpeg_mux_write_packet, | |
| 902 mpeg_mux_end, | |
| 903 }; | |
| 904 | |
| 905 static AVOutputFormat mpeg1vcd_mux = { | |
| 906 "vcd", | |
| 907 "MPEG1 System format (VCD)", | |
|
14
b167760cd0aa
mimetype fixes patch by (Ryutaroh Matsumoto <ryutaroh at it dot ss dot titech dot ac dot jp>)
michaelni
parents:
0
diff
changeset
|
908 "video/mpeg", |
| 0 | 909 NULL, |
| 910 sizeof(MpegMuxContext), | |
| 911 CODEC_ID_MP2, | |
| 912 CODEC_ID_MPEG1VIDEO, | |
| 913 mpeg_mux_init, | |
| 914 mpeg_mux_write_packet, | |
| 915 mpeg_mux_end, | |
| 916 }; | |
| 917 | |
| 918 static AVOutputFormat mpeg2vob_mux = { | |
| 919 "vob", | |
| 920 "MPEG2 PS format (VOB)", | |
|
14
b167760cd0aa
mimetype fixes patch by (Ryutaroh Matsumoto <ryutaroh at it dot ss dot titech dot ac dot jp>)
michaelni
parents:
0
diff
changeset
|
921 "video/mpeg", |
| 0 | 922 "vob", |
| 923 sizeof(MpegMuxContext), | |
| 924 CODEC_ID_MP2, | |
| 925 CODEC_ID_MPEG1VIDEO, | |
| 926 mpeg_mux_init, | |
| 927 mpeg_mux_write_packet, | |
| 928 mpeg_mux_end, | |
| 929 }; | |
|
277
a313e1080322
disable encoders where appropriate (patch courtesy of BERO
melanson
parents:
276
diff
changeset
|
930 #endif //CONFIG_ENCODERS |
| 0 | 931 |
| 190 | 932 AVInputFormat mpegps_demux = { |
| 0 | 933 "mpeg", |
| 934 "MPEG PS format", | |
| 935 sizeof(MpegDemuxContext), | |
| 936 mpegps_probe, | |
| 937 mpegps_read_header, | |
| 938 mpegps_read_packet, | |
| 939 mpegps_read_close, | |
| 310 | 940 mpegps_read_seek, |
| 0 | 941 }; |
| 942 | |
| 943 int mpegps_init(void) | |
| 944 { | |
|
277
a313e1080322
disable encoders where appropriate (patch courtesy of BERO
melanson
parents:
276
diff
changeset
|
945 #ifdef CONFIG_ENCODERS |
| 0 | 946 av_register_output_format(&mpeg1system_mux); |
| 947 av_register_output_format(&mpeg1vcd_mux); | |
| 948 av_register_output_format(&mpeg2vob_mux); | |
|
277
a313e1080322
disable encoders where appropriate (patch courtesy of BERO
melanson
parents:
276
diff
changeset
|
949 #endif //CONFIG_ENCODERS |
| 0 | 950 av_register_input_format(&mpegps_demux); |
| 951 return 0; | |
| 952 } |
