1 //=================== DEMUXER v2.5 =========================
16 #include "libvo/fastmemcpy.h"
18 #include "stream/stream.h"
23 #include "libaf/af_format.h"
25 extern void resync_video_stream(sh_video_t
*sh_video
);
26 extern void resync_audio_stream(sh_audio_t
*sh_audio
);
29 extern demuxer_desc_t demuxer_desc_rawaudio
;
30 extern demuxer_desc_t demuxer_desc_rawvideo
;
31 extern demuxer_desc_t demuxer_desc_tv
;
32 extern demuxer_desc_t demuxer_desc_mf
;
33 extern demuxer_desc_t demuxer_desc_avi
;
34 extern demuxer_desc_t demuxer_desc_y4m
;
35 extern demuxer_desc_t demuxer_desc_asf
;
36 extern demuxer_desc_t demuxer_desc_nuv
;
37 extern demuxer_desc_t demuxer_desc_real
;
38 extern demuxer_desc_t demuxer_desc_smjpeg
;
39 extern demuxer_desc_t demuxer_desc_matroska
;
40 extern demuxer_desc_t demuxer_desc_realaudio
;
41 extern demuxer_desc_t demuxer_desc_vqf
;
42 extern demuxer_desc_t demuxer_desc_mov
;
43 extern demuxer_desc_t demuxer_desc_vivo
;
44 extern demuxer_desc_t demuxer_desc_fli
;
45 extern demuxer_desc_t demuxer_desc_film
;
46 extern demuxer_desc_t demuxer_desc_roq
;
47 extern demuxer_desc_t demuxer_desc_gif
;
48 extern demuxer_desc_t demuxer_desc_ogg
;
49 extern demuxer_desc_t demuxer_desc_avs
;
50 extern demuxer_desc_t demuxer_desc_pva
;
51 extern demuxer_desc_t demuxer_desc_nsv
;
52 extern demuxer_desc_t demuxer_desc_mpeg_ts
;
53 extern demuxer_desc_t demuxer_desc_lmlm4
;
54 extern demuxer_desc_t demuxer_desc_mpeg_ps
;
55 extern demuxer_desc_t demuxer_desc_mpeg_pes
;
56 extern demuxer_desc_t demuxer_desc_mpeg_es
;
57 extern demuxer_desc_t demuxer_desc_mpeg_gxf
;
58 extern demuxer_desc_t demuxer_desc_mpeg4_es
;
59 extern demuxer_desc_t demuxer_desc_h264_es
;
60 extern demuxer_desc_t demuxer_desc_rawdv
;
61 extern demuxer_desc_t demuxer_desc_mpc
;
62 extern demuxer_desc_t demuxer_desc_audio
;
63 extern demuxer_desc_t demuxer_desc_xmms
;
64 extern demuxer_desc_t demuxer_desc_mpeg_ty
;
65 extern demuxer_desc_t demuxer_desc_rtp
;
66 extern demuxer_desc_t demuxer_desc_rtp_nemesi
;
67 extern demuxer_desc_t demuxer_desc_lavf
;
68 extern demuxer_desc_t demuxer_desc_lavf_preferred
;
69 extern demuxer_desc_t demuxer_desc_aac
;
70 extern demuxer_desc_t demuxer_desc_nut
;
72 demuxer_desc_t
* demuxer_list
[] = {
73 &demuxer_desc_rawaudio
,
74 &demuxer_desc_rawvideo
,
79 #ifdef USE_LIBAVFORMAT
80 &demuxer_desc_lavf_preferred
,
89 &demuxer_desc_matroska
,
90 &demuxer_desc_realaudio
,
100 #ifdef HAVE_OGGVORBIS
107 &demuxer_desc_mpeg_ts
,
109 &demuxer_desc_mpeg_ps
,
110 &demuxer_desc_mpeg_pes
,
111 &demuxer_desc_mpeg_es
,
112 &demuxer_desc_mpeg_gxf
,
113 &demuxer_desc_mpeg4_es
,
114 &demuxer_desc_h264_es
,
119 &demuxer_desc_mpeg_ty
,
120 #ifdef STREAMING_LIVE555
124 &demuxer_desc_rtp_nemesi
,
126 #ifdef USE_LIBAVFORMAT
142 void free_demuxer_stream(demux_stream_t
*ds
){
147 demux_stream_t
* new_demuxer_stream(struct demuxer_st
*demuxer
,int id
){
148 demux_stream_t
* ds
=malloc(sizeof(demux_stream_t
));
149 ds
->buffer_pos
=ds
->buffer_size
=0;
160 ds
->first
=ds
->last
=ds
->current
=NULL
;
167 ds
->ss_mul
=ds
->ss_div
=0;
175 * Get demuxer description structure for a given demuxer type
177 * @param file_format type of the demuxer
178 * @return structure for the demuxer, NULL if not found
180 static demuxer_desc_t
* get_demuxer_desc_from_type(int file_format
)
184 for (i
= 0; demuxer_list
[i
]; i
++)
185 if (file_format
== demuxer_list
[i
]->type
)
186 return demuxer_list
[i
];
192 demuxer_t
* new_demuxer(stream_t
*stream
,int type
,int a_id
,int v_id
,int s_id
,char *filename
){
193 demuxer_t
*d
=malloc(sizeof(demuxer_t
));
194 memset(d
,0,sizeof(demuxer_t
));
196 d
->stream_pts
= MP_NOPTS_VALUE
;
197 d
->movi_start
=stream
->start_pos
;
198 d
->movi_end
=stream
->end_pos
;
202 d
->audio
=new_demuxer_stream(d
,a_id
);
203 d
->video
=new_demuxer_stream(d
,v_id
);
204 d
->sub
=new_demuxer_stream(d
,s_id
);
207 if (!(d
->desc
= get_demuxer_desc_from_type(type
)))
208 mp_msg(MSGT_DEMUXER
,MSGL_ERR
,"BUG! Invalid demuxer type in new_demuxer(), big troubles ahead.");
209 if(filename
) // Filename hack for avs_check_file
210 d
->filename
=strdup(filename
);
211 stream_reset(stream
);
212 stream_seek(stream
,stream
->start_pos
);
216 extern int dvdsub_id
;
218 sh_sub_t
*new_sh_sub_sid(demuxer_t
*demuxer
, int id
, int sid
) {
219 if (id
> MAX_S_STREAMS
- 1 || id
< 0) {
220 mp_msg(MSGT_DEMUXER
,MSGL_WARN
,"Requested sub stream id overflow (%d > %d)\n",
224 if (demuxer
->s_streams
[id
])
225 mp_msg(MSGT_DEMUXER
, MSGL_WARN
, "Sub stream %i redefined\n", id
);
227 sh_sub_t
*sh
= calloc(1, sizeof(sh_sub_t
));
228 demuxer
->s_streams
[id
] = sh
;
230 mp_msg(MSGT_IDENTIFY
, MSGL_INFO
, "ID_SUBTITLE_ID=%d\n", sid
);
231 if (dvdsub_id
== id
) {
232 demuxer
->sub
->id
= id
;
233 demuxer
->sub
->sh
= sh
;
236 return demuxer
->s_streams
[id
];
239 void free_sh_sub(sh_sub_t
*sh
) {
240 mp_msg(MSGT_DEMUXER
, MSGL_DBG2
, "DEMUXER: freeing sh_sub at %p\n", sh
);
244 sh_audio_t
* new_sh_audio_aid(demuxer_t
*demuxer
,int id
,int aid
){
245 if(id
> MAX_A_STREAMS
-1 || id
< 0)
247 mp_msg(MSGT_DEMUXER
,MSGL_WARN
,"Requested audio stream id overflow (%d > %d)\n",
251 if(demuxer
->a_streams
[id
]){
252 mp_msg(MSGT_DEMUXER
,MSGL_WARN
,MSGTR_AudioStreamRedefined
,id
);
255 mp_msg(MSGT_DEMUXER
,MSGL_V
,MSGTR_FoundAudioStream
,id
);
256 demuxer
->a_streams
[id
]=calloc(1, sizeof(sh_audio_t
));
257 sh
= demuxer
->a_streams
[id
];
260 sh
->sample_format
=AF_FORMAT_S16_NE
;
261 sh
->audio_out_minsize
=8192;/* default size, maybe not enough for Win32/ACM*/
262 sh
->pts
=MP_NOPTS_VALUE
;
263 mp_msg(MSGT_IDENTIFY
, MSGL_INFO
, "ID_AUDIO_ID=%d\n", aid
);
265 ((sh_audio_t
*)demuxer
->a_streams
[id
])->aid
= aid
;
266 return demuxer
->a_streams
[id
];
269 void free_sh_audio(demuxer_t
*demuxer
, int id
) {
270 sh_audio_t
*sh
= demuxer
->a_streams
[id
];
271 demuxer
->a_streams
[id
] = NULL
;
272 mp_msg(MSGT_DEMUXER
,MSGL_DBG2
,"DEMUXER: freeing sh_audio at %p\n",sh
);
273 if(sh
->wf
) free(sh
->wf
);
277 sh_video_t
* new_sh_video_vid(demuxer_t
*demuxer
,int id
,int vid
){
278 if(id
> MAX_V_STREAMS
-1 || id
< 0)
280 mp_msg(MSGT_DEMUXER
,MSGL_WARN
,"Requested video stream id overflow (%d > %d)\n",
284 if(demuxer
->v_streams
[id
]){
285 mp_msg(MSGT_DEMUXER
,MSGL_WARN
,MSGTR_VideoStreamRedefined
,id
);
287 mp_msg(MSGT_DEMUXER
,MSGL_V
,MSGTR_FoundVideoStream
,id
);
288 demuxer
->v_streams
[id
]=calloc(1, sizeof(sh_video_t
));
289 mp_msg(MSGT_IDENTIFY
, MSGL_INFO
, "ID_VIDEO_ID=%d\n", vid
);
291 ((sh_video_t
*)demuxer
->v_streams
[id
])->vid
= vid
;
292 return demuxer
->v_streams
[id
];
295 void free_sh_video(sh_video_t
* sh
){
296 mp_msg(MSGT_DEMUXER
,MSGL_DBG2
,"DEMUXER: freeing sh_video at %p\n",sh
);
297 if(sh
->bih
) free(sh
->bih
);
301 void free_demuxer(demuxer_t
*demuxer
){
303 mp_msg(MSGT_DEMUXER
,MSGL_DBG2
,"DEMUXER: freeing demuxer at %p\n",demuxer
);
304 if(demuxer
->desc
->close
)
305 demuxer
->desc
->close(demuxer
);
306 // Very ugly hack to make it behave like old implementation
307 if (demuxer
->desc
->type
== DEMUXER_TYPE_DEMUXERS
)
308 goto skip_streamfree
;
310 for(i
= 0; i
< MAX_A_STREAMS
; i
++)
311 if(demuxer
->a_streams
[i
]) free_sh_audio(demuxer
, i
);
312 for(i
= 0; i
< MAX_V_STREAMS
; i
++)
313 if(demuxer
->v_streams
[i
]) free_sh_video(demuxer
->v_streams
[i
]);
314 for(i
= 0; i
< MAX_S_STREAMS
; i
++)
315 if(demuxer
->s_streams
[i
]) free_sh_sub(demuxer
->s_streams
[i
]);
317 free_demuxer_stream(demuxer
->audio
);
318 free_demuxer_stream(demuxer
->video
);
319 free_demuxer_stream(demuxer
->sub
);
322 for(i
=0;demuxer
->info
[i
] != NULL
; i
++)
323 free(demuxer
->info
[i
]);
326 if(demuxer
->filename
)
327 free(demuxer
->filename
);
328 if (demuxer
->chapters
) {
329 for (i
=0; i
<demuxer
->num_chapters
; i
++)
330 if (demuxer
->chapters
[i
].name
)
331 free(demuxer
->chapters
[i
].name
);
332 free(demuxer
->chapters
);
338 void ds_add_packet(demux_stream_t
*ds
,demux_packet_t
* dp
){
339 // demux_packet_t* dp=new_demux_packet(len);
340 // stream_read(stream,dp->buffer,len);
341 // dp->pts=pts; //(float)pts/90000.0f;
343 // append packet to DS stream:
347 // next packet in stream
351 // first packet in stream
352 ds
->first
=ds
->last
=dp
;
354 mp_dbg(MSGT_DEMUXER
,MSGL_DBG2
,"DEMUX: Append packet to %s, len=%d pts=%5.3f pos=%u [packs: A=%d V=%d]\n",
355 (ds
==ds
->demuxer
->audio
)?"d_audio":"d_video",
356 dp
->len
,dp
->pts
,(unsigned int)dp
->pos
,ds
->demuxer
->audio
->packs
,ds
->demuxer
->video
->packs
);
359 void ds_read_packet(demux_stream_t
*ds
, stream_t
*stream
, int len
, double pts
, off_t pos
, int flags
) {
360 demux_packet_t
* dp
=new_demux_packet(len
);
361 len
= stream_read(stream
,dp
->buffer
,len
);
362 resize_demux_packet(dp
, len
);
363 dp
->pts
=pts
; //(float)pts/90000.0f;
366 // append packet to DS stream:
367 ds_add_packet(ds
,dp
);
371 // 0 = EOF or no stream found or invalid type
372 // 1 = successfully read a packet
374 int demux_fill_buffer(demuxer_t
*demux
,demux_stream_t
*ds
){
375 // Note: parameter 'ds' can be NULL!
376 // printf("demux->type=%d\n",demux->type);
377 return demux
->desc
->fill_buffer(demux
, ds
);
383 int ds_fill_buffer(demux_stream_t
*ds
){
384 demuxer_t
*demux
=ds
->demuxer
;
385 if(ds
->current
) free_demux_packet(ds
->current
);
387 if( mp_msg_test(MSGT_DEMUXER
,MSGL_DBG3
) ){
388 if(ds
==demux
->audio
) mp_dbg(MSGT_DEMUXER
,MSGL_DBG3
,"ds_fill_buffer(d_audio) called\n");else
389 if(ds
==demux
->video
) mp_dbg(MSGT_DEMUXER
,MSGL_DBG3
,"ds_fill_buffer(d_video) called\n");else
390 if(ds
==demux
->sub
) mp_dbg(MSGT_DEMUXER
,MSGL_DBG3
,"ds_fill_buffer(d_sub) called\n");else
391 mp_dbg(MSGT_DEMUXER
,MSGL_DBG3
,"ds_fill_buffer(unknown 0x%X) called\n",(unsigned int)ds
);
395 demux_packet_t
*p
=ds
->first
;
397 ds
->buffer
=p
->buffer
;
399 ds
->buffer_size
=p
->len
;
401 ds
->dpos
+=p
->len
; // !!!
403 if (p
->pts
!= (correct_pts
? MP_NOPTS_VALUE
: 0)) {
407 ds
->pts_bytes
+=p
->len
; // !!!
408 if(p
->stream_pts
!= MP_NOPTS_VALUE
) demux
->stream_pts
=p
->stream_pts
;
414 if(!ds
->first
) ds
->last
=NULL
;
416 return 1; //ds->buffer_size;
418 if(demux
->audio
->packs
>=MAX_PACKS
|| demux
->audio
->bytes
>=MAX_PACK_BYTES
){
419 mp_msg(MSGT_DEMUXER
,MSGL_ERR
,MSGTR_TooManyAudioInBuffer
,demux
->audio
->packs
,demux
->audio
->bytes
);
420 mp_msg(MSGT_DEMUXER
,MSGL_HINT
,MSGTR_MaybeNI
);
423 if(demux
->video
->packs
>=MAX_PACKS
|| demux
->video
->bytes
>=MAX_PACK_BYTES
){
424 mp_msg(MSGT_DEMUXER
,MSGL_ERR
,MSGTR_TooManyVideoInBuffer
,demux
->video
->packs
,demux
->video
->bytes
);
425 mp_msg(MSGT_DEMUXER
,MSGL_HINT
,MSGTR_MaybeNI
);
428 if(!demux_fill_buffer(demux
,ds
)){
429 mp_dbg(MSGT_DEMUXER
,MSGL_DBG2
,"ds_fill_buffer()->demux_fill_buffer() failed\n");
433 ds
->buffer_pos
=ds
->buffer_size
=0;
435 mp_msg(MSGT_DEMUXER
,MSGL_V
,"ds_fill_buffer: EOF reached (stream: %s) \n",ds
==demux
->audio
?"audio":"video");
440 int demux_read_data(demux_stream_t
*ds
,unsigned char* mem
,int len
){
444 x
=ds
->buffer_size
-ds
->buffer_pos
;
446 if(!ds_fill_buffer(ds
)) return bytes
;
449 if(mem
) fast_memcpy(mem
+bytes
,&ds
->buffer
[ds
->buffer_pos
],x
);
450 bytes
+=x
;len
-=x
;ds
->buffer_pos
+=x
;
456 int demux_read_data_pack(demux_stream_t
*ds
,unsigned char* mem
,int len
){
460 x
=ds
->buffer_size
-ds
->buffer_pos
;
462 if(!ds_fill_buffer(ds
)) return bytes
;
465 if(mem
) fast_memcpy(mem
+bytes
,&ds
->buffer
[ds
->buffer_pos
],x
);
466 bytes
+=x
;len
-=x
;ds
->buffer_pos
+=x
;
467 return bytes
; // stop at end of package! (for correct timestamping)
474 * \brief read data until the given 3-byte pattern is encountered, up to maxlen
475 * \param mem memory to read data into, may be NULL to discard data
476 * \param maxlen maximum number of bytes to read
477 * \param read number of bytes actually read
478 * \param pattern pattern to search for (lowest 8 bits are ignored)
479 * \return whether pattern was found
481 int demux_pattern_3(demux_stream_t
*ds
, unsigned char *mem
, int maxlen
,
482 int *read
, uint32_t pattern
) {
483 register uint32_t head
= 0xffffff00;
484 register uint32_t pat
= pattern
& 0xffffff00;
487 register unsigned char *ds_buf
= &ds
->buffer
[ds
->buffer_size
];
488 int len
= ds
->buffer_size
- ds
->buffer_pos
;
489 register long pos
= -len
;
490 if (unlikely(pos
>= 0)) { // buffer is empty
497 } while (++pos
&& head
!= pat
);
499 if (total_len
+ len
> maxlen
)
500 len
= maxlen
- total_len
;
501 len
= demux_read_data(ds
, mem
? &mem
[total_len
] : NULL
, len
);
503 } while ((head
!= pat
|| total_len
< 3) && total_len
< maxlen
&& !ds
->eof
);
506 return total_len
>= 3 && head
== pat
;
509 void ds_free_packs(demux_stream_t
*ds
){
510 demux_packet_t
*dp
=ds
->first
;
512 demux_packet_t
*dn
=dp
->next
;
513 free_demux_packet(dp
);
517 // free unfinished .asf fragments:
518 free(ds
->asf_packet
->buffer
);
519 free(ds
->asf_packet
);
522 ds
->first
=ds
->last
=NULL
;
523 ds
->packs
=0; // !!!!!
525 if(ds
->current
) free_demux_packet(ds
->current
);
528 ds
->buffer_pos
=ds
->buffer_size
;
529 ds
->pts
=0; ds
->pts_bytes
=0;
532 int ds_get_packet(demux_stream_t
*ds
,unsigned char **start
){
534 if(ds
->buffer_pos
>=ds
->buffer_size
){
535 if(!ds_fill_buffer(ds
)){
541 len
=ds
->buffer_size
-ds
->buffer_pos
;
542 *start
= &ds
->buffer
[ds
->buffer_pos
];
547 int ds_get_packet_pts(demux_stream_t
*ds
,unsigned char **start
, double *pts
)
550 *pts
= MP_NOPTS_VALUE
;
551 if(ds
->buffer_pos
>=ds
->buffer_size
){
552 if (!ds_fill_buffer(ds
)) {
558 // Should use MP_NOPTS_VALUE for "unknown pts" in the packets too
559 // Return pts unless this read starts from the middle of a packet
560 if (!ds
->buffer_pos
&& (correct_pts
|| ds
->current
->pts
))
561 *pts
= ds
->current
->pts
;
562 len
=ds
->buffer_size
-ds
->buffer_pos
;
563 *start
= &ds
->buffer
[ds
->buffer_pos
];
568 int ds_get_packet_sub(demux_stream_t
*ds
,unsigned char **start
){
570 if(ds
->buffer_pos
>=ds
->buffer_size
){
572 if(!ds
->packs
) return -1; // no sub
573 if(!ds_fill_buffer(ds
)) return -1; // EOF
575 len
=ds
->buffer_size
-ds
->buffer_pos
;
576 *start
= &ds
->buffer
[ds
->buffer_pos
];
581 double ds_get_next_pts(demux_stream_t
*ds
)
583 demuxer_t
* demux
= ds
->demuxer
;
585 if(demux
->audio
->packs
>=MAX_PACKS
|| demux
->audio
->bytes
>=MAX_PACK_BYTES
){
586 mp_msg(MSGT_DEMUXER
,MSGL_ERR
,MSGTR_TooManyAudioInBuffer
,demux
->audio
->packs
,demux
->audio
->bytes
);
587 mp_msg(MSGT_DEMUXER
,MSGL_HINT
,MSGTR_MaybeNI
);
588 return MP_NOPTS_VALUE
;
590 if(demux
->video
->packs
>=MAX_PACKS
|| demux
->video
->bytes
>=MAX_PACK_BYTES
){
591 mp_msg(MSGT_DEMUXER
,MSGL_ERR
,MSGTR_TooManyVideoInBuffer
,demux
->video
->packs
,demux
->video
->bytes
);
592 mp_msg(MSGT_DEMUXER
,MSGL_HINT
,MSGTR_MaybeNI
);
593 return MP_NOPTS_VALUE
;
595 if(!demux_fill_buffer(demux
,ds
))
596 return MP_NOPTS_VALUE
;
598 return ds
->first
->pts
;
601 // ====================================================================
603 void demuxer_help(void)
607 mp_msg(MSGT_DEMUXER
, MSGL_INFO
, "Available demuxers:\n");
608 mp_msg(MSGT_DEMUXER
, MSGL_INFO
, " demuxer: type info: (comment)\n");
609 mp_msg(MSGT_IDENTIFY
, MSGL_INFO
, "ID_DEMUXERS\n");
610 for (i
= 0; demuxer_list
[i
]; i
++) {
611 if (demuxer_list
[i
]->type
> DEMUXER_TYPE_MAX
) // Don't display special demuxers
613 if (demuxer_list
[i
]->comment
&& strlen(demuxer_list
[i
]->comment
))
614 mp_msg(MSGT_DEMUXER
, MSGL_INFO
, "%10s %2d %s (%s)\n",
615 demuxer_list
[i
]->name
, demuxer_list
[i
]->type
, demuxer_list
[i
]->info
, demuxer_list
[i
]->comment
);
617 mp_msg(MSGT_DEMUXER
, MSGL_INFO
, "%10s %2d %s\n",
618 demuxer_list
[i
]->name
, demuxer_list
[i
]->type
, demuxer_list
[i
]->info
);
624 * Get demuxer type for a given demuxer name
626 * @param demuxer_name string with demuxer name of demuxer number
627 * @param force will be set if demuxer should be forced.
629 * @return DEMUXER_TYPE_xxx, -1 if error or not found
631 int get_demuxer_type_from_name(char *demuxer_name
, int *force
)
637 if (!demuxer_name
|| !demuxer_name
[0])
638 return DEMUXER_TYPE_UNKNOWN
;
639 if (force
) *force
= demuxer_name
[0] == '+';
640 if (demuxer_name
[0] == '+')
641 demuxer_name
= &demuxer_name
[1];
642 for (i
= 0; demuxer_list
[i
]; i
++) {
643 if (demuxer_list
[i
]->type
> DEMUXER_TYPE_MAX
) // Can't select special demuxers from commandline
645 if (strcmp(demuxer_name
, demuxer_list
[i
]->name
) == 0)
646 return demuxer_list
[i
]->type
;
649 // No match found, try to parse name as an integer (demuxer number)
650 type_int
= strtol(demuxer_name
, &endptr
, 0);
651 if (*endptr
) // Conversion failed
653 if ((type_int
> 0) && (type_int
<= DEMUXER_TYPE_MAX
))
654 return (int)type_int
;
659 int extension_parsing
=1; // 0=off 1=mixed (used only for unstable formats)
664 NOTE : Several demuxers may be opened at the same time so
665 demuxers should NEVER rely on an external var to enable them
666 self. If a demuxer can't do any auto-detection it should only use
667 file_format. The user can explicitly set file_format with the -demuxer
668 option so there is really no need for another extra var.
669 For convenience an option can be added to set file_format directly
670 to the right type (ex: rawaudio,rawvideo).
671 Also the stream can override the file_format so a demuxer which rely
672 on a special stream type can set file_format at the stream level
676 static demuxer_t
* demux_open_stream(stream_t
*stream
, int file_format
,
677 int force
, int audio_id
, int video_id
, int dvdsub_id
,
680 //int file_format=(*file_format_ptr);
682 demuxer_t
*demuxer
=NULL
;
684 sh_video_t
*sh_video
=NULL
;
686 demuxer_desc_t
*demuxer_desc
;
690 //printf("demux_open(%p,%d,%d,%d,%d) \n",stream,file_format,audio_id,video_id,dvdsub_id);
692 // If somebody requested a demuxer check it
694 if ((demuxer_desc
= get_demuxer_desc_from_type(file_format
))) {
695 demuxer
= new_demuxer(stream
,demuxer_desc
->type
,audio_id
,video_id
,dvdsub_id
,filename
);
696 if (demuxer_desc
->check_file
)
697 fformat
= demuxer_desc
->check_file(demuxer
);
698 if (force
|| !demuxer_desc
->check_file
)
699 fformat
= demuxer_desc
->type
;
701 if (fformat
== demuxer_desc
->type
) {
702 demuxer_t
*demux2
= demuxer
;
703 // Move messages to demuxer detection code?
704 mp_msg(MSGT_DEMUXER
, MSGL_INFO
, MSGTR_Detected_XXX_FileFormat
, demuxer_desc
->shortdesc
);
705 file_format
= demuxer_desc
->type
= fformat
;
706 if (!demuxer
->desc
->open
|| (demux2
= demuxer
->desc
->open(demuxer
))) {
711 // Format changed after check, recurse
712 free_demuxer(demuxer
);
713 return demux_open_stream(stream
, fformat
, force
,
714 audio_id
, video_id
, dvdsub_id
, filename
);
717 // Check failed for forced demuxer, quit
718 free_demuxer(demuxer
);
723 // Test demuxers with safe file checks
724 for (i
= 0; (demuxer_desc
= demuxer_list
[i
]); i
++) {
725 if (demuxer_desc
->safe_check
) {
726 demuxer
= new_demuxer(stream
,demuxer_desc
->type
,audio_id
,video_id
,dvdsub_id
,filename
);
727 if ((fformat
= demuxer_desc
->check_file(demuxer
)) != 0) {
728 if (fformat
== demuxer_desc
->type
) {
729 demuxer_t
*demux2
= demuxer
;
730 mp_msg(MSGT_DEMUXER
, MSGL_INFO
, MSGTR_Detected_XXX_FileFormat
, demuxer_desc
->shortdesc
);
731 file_format
= fformat
;
732 if (!demuxer
->desc
->open
|| (demux2
= demuxer
->desc
->open(demuxer
))) {
737 if (fformat
== DEMUXER_TYPE_PLAYLIST
)
738 return demuxer
; // handled in mplayer.c
739 // Format changed after check, recurse
740 free_demuxer(demuxer
);
741 demuxer
=demux_open_stream(stream
, fformat
, force
,
742 audio_id
, video_id
, dvdsub_id
, filename
);
743 if(demuxer
) return demuxer
; // done!
744 file_format
= DEMUXER_TYPE_UNKNOWN
;
747 free_demuxer(demuxer
);
752 // If no forced demuxer perform file extension based detection
753 // Ok. We're over the stable detectable fileformats, the next ones are a bit
754 // fuzzy. So by default (extension_parsing==1) try extension-based detection
756 if(file_format
==DEMUXER_TYPE_UNKNOWN
&& filename
&& extension_parsing
==1){
757 file_format
=demuxer_type_by_filename(filename
);
758 if(file_format
!=DEMUXER_TYPE_UNKNOWN
){
759 // we like recursion :)
760 demuxer
=demux_open_stream(stream
, file_format
, force
,
761 audio_id
, video_id
, dvdsub_id
, filename
);
762 if(demuxer
) return demuxer
; // done!
763 file_format
=DEMUXER_TYPE_UNKNOWN
; // continue fuzzy guessing...
764 mp_msg(MSGT_DEMUXER
,MSGL_V
,"demuxer: continue fuzzy content-based format guessing...\n");
768 // Try detection for all other demuxers
769 for (i
= 0; (demuxer_desc
= demuxer_list
[i
]); i
++) {
770 if (!demuxer_desc
->safe_check
&& demuxer_desc
->check_file
) {
771 demuxer
= new_demuxer(stream
,demuxer_desc
->type
,audio_id
,video_id
,dvdsub_id
,filename
);
772 if ((fformat
= demuxer_desc
->check_file(demuxer
)) != 0) {
773 if (fformat
== demuxer_desc
->type
) {
774 demuxer_t
*demux2
= demuxer
;
775 mp_msg(MSGT_DEMUXER
, MSGL_INFO
, MSGTR_Detected_XXX_FileFormat
, demuxer_desc
->shortdesc
);
776 file_format
= fformat
;
777 if (!demuxer
->desc
->open
|| (demux2
= demuxer
->desc
->open(demuxer
))) {
782 if (fformat
== DEMUXER_TYPE_PLAYLIST
)
783 return demuxer
; // handled in mplayer.c
784 // Format changed after check, recurse
785 free_demuxer(demuxer
);
786 demuxer
=demux_open_stream(stream
, fformat
, force
,
787 audio_id
, video_id
, dvdsub_id
, filename
);
788 if(demuxer
) return demuxer
; // done!
789 file_format
= DEMUXER_TYPE_UNKNOWN
;
792 free_demuxer(demuxer
);
798 //====== File format recognized, set up these for compatibility: =========
801 demuxer
->file_format
=file_format
;
803 if ((sh_video
=demuxer
->video
->sh
) && sh_video
->bih
){
804 int biComp
=le2me_32(sh_video
->bih
->biCompression
);
805 mp_msg(MSGT_DEMUX
,MSGL_INFO
,"VIDEO: [%.4s] %dx%d %dbpp %5.3f fps %5.1f kbps (%4.1f kbyte/s)\n",
807 sh_video
->bih
->biWidth
,
808 sh_video
->bih
->biHeight
,
809 sh_video
->bih
->biBitCount
,
811 sh_video
->i_bps
*0.008f
,
812 sh_video
->i_bps
/1024.0f
);
817 char* audio_stream
= NULL
;
818 char* sub_stream
= NULL
;
819 int demuxer_type
= 0; // used by rawaudio and rawvideo
820 int audio_stream_cache
= 0;
822 char *demuxer_name
= NULL
; // parameter from -demuxer
823 char *audio_demuxer_name
= NULL
; // parameter from -audio-demuxer
824 char *sub_demuxer_name
= NULL
; // parameter from -sub-demuxer
826 extern int hr_mp3_seek
;
828 extern float stream_cache_min_percent
;
829 extern float stream_cache_seek_min_percent
;
831 demuxer_t
* demux_open(stream_t
*vs
,int file_format
,int audio_id
,int video_id
,int dvdsub_id
,char* filename
){
832 stream_t
*as
= NULL
,*ss
= NULL
;
833 demuxer_t
*vd
,*ad
= NULL
,*sd
= NULL
;
834 int afmt
=DEMUXER_TYPE_UNKNOWN
,sfmt
= DEMUXER_TYPE_UNKNOWN
;
835 int audio_demuxer_type
= 0, sub_demuxer_type
= 0;
836 int demuxer_force
= 0, audio_demuxer_force
= 0,
837 sub_demuxer_force
= 0;
839 if ((demuxer_type
= get_demuxer_type_from_name(demuxer_name
, &demuxer_force
)) < 0) {
840 mp_msg(MSGT_DEMUXER
,MSGL_ERR
,"-demuxer %s does not exist.\n",demuxer_name
);
842 if ((audio_demuxer_type
= get_demuxer_type_from_name(audio_demuxer_name
, &audio_demuxer_force
)) < 0) {
843 mp_msg(MSGT_DEMUXER
,MSGL_ERR
,"-audio-demuxer %s does not exist.\n",audio_demuxer_name
);
845 if ((sub_demuxer_type
= get_demuxer_type_from_name(sub_demuxer_name
, &sub_demuxer_force
)) < 0) {
846 mp_msg(MSGT_DEMUXER
,MSGL_ERR
,"-sub-demuxer %s does not exist.\n",sub_demuxer_name
);
850 as
= open_stream(audio_stream
,0,&afmt
);
852 mp_msg(MSGT_DEMUXER
,MSGL_ERR
,MSGTR_CannotOpenAudioStream
,audio_stream
);
855 if(audio_stream_cache
) {
856 if(!stream_enable_cache(as
,audio_stream_cache
*1024,audio_stream_cache
*1024*(stream_cache_min_percent
/ 100.0),
857 audio_stream_cache
*1024*(stream_cache_seek_min_percent
/ 100.0))) {
859 mp_msg(MSGT_DEMUXER
,MSGL_ERR
,"Can't enable audio stream cache\n");
865 ss
= open_stream(sub_stream
,0,&sfmt
);
867 mp_msg(MSGT_DEMUXER
,MSGL_ERR
,MSGTR_CannotOpenSubtitlesStream
,sub_stream
);
872 vd
= demux_open_stream(vs
, demuxer_type
? demuxer_type
: file_format
,
873 demuxer_force
, audio_stream
? -2 : audio_id
, video_id
,
874 sub_stream
? -2 : dvdsub_id
, filename
);
876 if(as
) free_stream(as
);
877 if(ss
) free_stream(ss
);
881 ad
= demux_open_stream(as
, audio_demuxer_type
? audio_demuxer_type
: afmt
,
882 audio_demuxer_force
, audio_id
, -2, -2, audio_stream
);
884 mp_msg(MSGT_DEMUXER
,MSGL_WARN
,MSGTR_OpeningAudioDemuxerFailed
,audio_stream
);
887 else if(ad
->audio
->sh
&& ((sh_audio_t
*)ad
->audio
->sh
)->format
== 0x55) // MP3
888 hr_mp3_seek
=1; // Enable high res seeking
891 sd
= demux_open_stream(ss
, sub_demuxer_type
? sub_demuxer_type
: sfmt
,
892 sub_demuxer_force
, -2, -2, dvdsub_id
, sub_stream
);
894 mp_msg(MSGT_DEMUXER
,MSGL_WARN
,MSGTR_OpeningSubtitlesDemuxerFailed
,sub_stream
);
900 return new_demuxers_demuxer(vd
,ad
,sd
);
902 return new_demuxers_demuxer(vd
,ad
,vd
);
904 return new_demuxers_demuxer(vd
,vd
,sd
);
910 int demux_seek(demuxer_t
*demuxer
,float rel_seek_secs
,float audio_delay
,int flags
){
911 demux_stream_t
*d_audio
=demuxer
->audio
;
912 demux_stream_t
*d_video
=demuxer
->video
;
913 sh_audio_t
*sh_audio
=d_audio
->sh
;
914 sh_video_t
*sh_video
=d_video
->sh
;
918 if(!demuxer
->seekable
){
919 if(demuxer
->file_format
==DEMUXER_TYPE_AVI
)
920 mp_msg(MSGT_SEEK
,MSGL_WARN
,MSGTR_CantSeekRawAVI
);
922 else if (demuxer
->file_format
==DEMUXER_TYPE_TV
)
923 mp_msg(MSGT_SEEK
,MSGL_WARN
,MSGTR_TVInputNotSeekable
);
926 mp_msg(MSGT_SEEK
,MSGL_WARN
,MSGTR_CantSeekFile
);
930 // clear demux buffers:
931 if(sh_audio
){ ds_free_packs(d_audio
);sh_audio
->a_buffer_len
=0;}
932 ds_free_packs(d_video
);
933 ds_free_packs(demuxer
->sub
);
935 demuxer
->stream
->eof
=0; // clear eof flag
936 demuxer
->video
->eof
=0;
937 demuxer
->audio
->eof
=0;
940 if(sh_audio
) sh_audio
->timer
=sh_video
->timer
;
942 if(sh_video
) sh_video
->timer
=0; // !!!!!!
945 if(flags
& 1) // absolute seek
948 if(demuxer
->stream_pts
== MP_NOPTS_VALUE
)
950 pts
= demuxer
->stream_pts
;
953 if(flags
& 2) { // percent seek
954 if(stream_control(demuxer
->stream
, STREAM_CTRL_GET_TIME_LENGTH
, &tmp
) == STREAM_UNSUPPORTED
)
956 pts
+= tmp
* rel_seek_secs
;
958 pts
+= rel_seek_secs
;
960 if(stream_control(demuxer
->stream
, STREAM_CTRL_SEEK_TO_TIME
, &pts
) != STREAM_UNSUPPORTED
) {
961 demux_control(demuxer
, DEMUXER_CTRL_RESYNC
, NULL
);
966 if (demuxer
->desc
->seek
)
967 demuxer
->desc
->seek(demuxer
,rel_seek_secs
,audio_delay
,flags
);
969 if (sh_audio
) resync_audio_stream(sh_audio
);
974 int demux_info_add(demuxer_t
*demuxer
, const char *opt
, const char *param
)
976 char **info
= demuxer
->info
;
980 for(n
= 0; info
&& info
[2*n
] != NULL
; n
++)
982 if(!strcasecmp(opt
,info
[2*n
]))
984 mp_msg(MSGT_DEMUX
, MSGL_INFO
,MSGTR_DemuxerInfoChanged
,opt
,param
);
986 info
[2*n
+1] = strdup(param
);
991 info
= demuxer
->info
= (char**)realloc(info
,(2*(n
+2))*sizeof(char*));
992 info
[2*n
] = strdup(opt
);
993 info
[2*n
+1] = strdup(param
);
994 memset(&info
[2*(n
+1)],0,2*sizeof(char*));
999 int demux_info_print(demuxer_t
*demuxer
)
1001 char **info
= demuxer
->info
;
1007 mp_msg(MSGT_DEMUX
, MSGL_INFO
,MSGTR_ClipInfo
);
1008 for(n
= 0; info
[2*n
] != NULL
; n
++)
1010 mp_msg(MSGT_DEMUX
, MSGL_INFO
, " %s: %s\n",info
[2*n
],info
[2*n
+1]);
1011 mp_msg(MSGT_IDENTIFY
, MSGL_INFO
, "ID_CLIP_INFO_NAME%d=%s\n", n
, info
[2*n
]);
1012 mp_msg(MSGT_IDENTIFY
, MSGL_INFO
, "ID_CLIP_INFO_VALUE%d=%s\n", n
, info
[2*n
+1]);
1014 mp_msg(MSGT_IDENTIFY
, MSGL_INFO
, "ID_CLIP_INFO_N=%d\n", n
);
1019 char* demux_info_get(demuxer_t
*demuxer
, const char *opt
) {
1021 char **info
= demuxer
->info
;
1023 for(i
= 0; info
&& info
[2*i
] != NULL
; i
++) {
1024 if(!strcasecmp(opt
,info
[2*i
]))
1031 int demux_control(demuxer_t
*demuxer
, int cmd
, void *arg
) {
1033 if (demuxer
->desc
->control
)
1034 return demuxer
->desc
->control(demuxer
,cmd
,arg
);
1036 return DEMUXER_CTRL_NOTIMPL
;
1041 double demuxer_get_time_length(demuxer_t
*demuxer
){
1042 double get_time_ans
;
1043 sh_video_t
*sh_video
= demuxer
->video
->sh
;
1044 sh_audio_t
*sh_audio
= demuxer
->audio
->sh
;
1045 // <= 0 means DEMUXER_CTRL_NOTIMPL or DEMUXER_CTRL_DONTKNOW
1046 if (demux_control(demuxer
, DEMUXER_CTRL_GET_TIME_LENGTH
,(void *)&get_time_ans
)<=0) {
1047 if (sh_video
&& sh_video
->i_bps
&& sh_audio
&& sh_audio
->i_bps
)
1048 get_time_ans
= (double)(demuxer
->movi_end
-demuxer
->movi_start
)/(sh_video
->i_bps
+sh_audio
->i_bps
);
1049 else if (sh_video
&& sh_video
->i_bps
)
1050 get_time_ans
= (double)(demuxer
->movi_end
-demuxer
->movi_start
)/sh_video
->i_bps
;
1051 else if (sh_audio
&& sh_audio
->i_bps
)
1052 get_time_ans
= (double)(demuxer
->movi_end
-demuxer
->movi_start
)/sh_audio
->i_bps
;
1056 return get_time_ans
;
1060 * \brief demuxer_get_current_time() returns the time of the current play in three possible ways:
1061 * either when the stream reader satisfies STREAM_CTRL_GET_CURRENT_TIME (e.g. dvd)
1062 * or using sh_video->pts when the former method fails
1064 * \return the current play time
1066 int demuxer_get_current_time(demuxer_t
*demuxer
){
1067 double get_time_ans
= 0;
1068 sh_video_t
*sh_video
= demuxer
->video
->sh
;
1069 if(demuxer
->stream_pts
!= MP_NOPTS_VALUE
)
1070 get_time_ans
= demuxer
->stream_pts
;
1071 else if(sh_video
) get_time_ans
= sh_video
->pts
;
1072 return (int) get_time_ans
;
1075 int demuxer_get_percent_pos(demuxer_t
*demuxer
){
1077 int res
= demux_control(demuxer
, DEMUXER_CTRL_GET_PERCENT_POS
, &ans
);
1078 int len
= (demuxer
->movi_end
- demuxer
->movi_start
) / 100;
1081 ans
= (demuxer
->filepos
- demuxer
->movi_start
) / len
;
1085 if (ans
< 0) ans
= 0;
1086 if (ans
> 100) ans
= 100;
1090 int demuxer_switch_audio(demuxer_t
*demuxer
, int index
){
1091 int res
= demux_control(demuxer
, DEMUXER_CTRL_SWITCH_AUDIO
, &index
);
1092 if (res
== DEMUXER_CTRL_NOTIMPL
)
1093 index
= demuxer
->audio
->id
;
1097 int demuxer_switch_video(demuxer_t
*demuxer
, int index
){
1098 int res
= demux_control(demuxer
, DEMUXER_CTRL_SWITCH_VIDEO
, &index
);
1099 if (res
== DEMUXER_CTRL_NOTIMPL
)
1100 index
= demuxer
->video
->id
;
1104 int demuxer_add_chapter(demuxer_t
* demuxer
, const char* name
, uint64_t start
, uint64_t end
){
1105 if (demuxer
->chapters
== NULL
)
1106 demuxer
->chapters
= malloc (32*sizeof(*demuxer
->chapters
));
1107 else if (!(demuxer
->num_chapters
% 32))
1108 demuxer
->chapters
= realloc (demuxer
->chapters
, (demuxer
->num_chapters
+ 32) * sizeof(*demuxer
->chapters
));
1110 demuxer
->chapters
[demuxer
->num_chapters
].start
= start
;
1111 demuxer
->chapters
[demuxer
->num_chapters
].end
= end
;
1112 demuxer
->chapters
[demuxer
->num_chapters
].name
= strdup(name
);
1114 return demuxer
->num_chapters
++;
1118 * \brief demuxer_seek_chapter() seeks to a chapter in two possible ways:
1119 * either using the demuxer->chapters structure set by the demuxer
1120 * or asking help to the stream layer (e.g. dvd)
1121 * \param chapter - chapter number wished - 0-based
1122 * \param mode 0: relative to current main pts, 1: absolute
1123 * \param seek_pts set by the function to the pts to seek to (if demuxer->chapters is set)
1124 * \param num_chapters number of chapters present (set by this function is param is not null)
1125 * \param chapter_name name of chapter found (set by this function is param is not null)
1126 * \return -1 on error, current chapter if successful
1129 int demuxer_seek_chapter(demuxer_t
*demuxer
, int chapter
, int mode
, float *seek_pts
, int *num_chapters
, char **chapter_name
) {
1132 sh_video_t
*sh_video
= demuxer
->video
->sh
;
1133 sh_audio_t
*sh_audio
= demuxer
->audio
->sh
;
1135 if (!demuxer
->num_chapters
|| !demuxer
->chapters
) {
1137 ris
= stream_control(demuxer
->stream
, STREAM_CTRL_GET_CURRENT_CHAPTER
, ¤t
);
1138 if(ris
== STREAM_UNSUPPORTED
) return -1;
1142 if(demuxer
->video
->sh
)
1143 ds_free_packs(demuxer
->video
);
1145 if(demuxer
->audio
->sh
)
1146 ds_free_packs(demuxer
->audio
);
1148 if(demuxer
->sub
->id
>= 0)
1149 ds_free_packs(demuxer
->sub
);
1151 ris
= stream_control(demuxer
->stream
, STREAM_CTRL_SEEK_TO_CHAPTER
, &chapter
);
1152 if(ris
!= STREAM_UNSUPPORTED
)
1153 demux_control(demuxer
, DEMUXER_CTRL_RESYNC
, NULL
);
1155 ds_fill_buffer(demuxer
->video
);
1156 resync_video_stream(sh_video
);
1160 ds_fill_buffer(demuxer
->audio
);
1161 resync_audio_stream(sh_audio
);
1164 //exit status may be ok, but main() doesn't have to seek itself (because e.g. dvds depend on sectors, not on pts)
1168 if(stream_control(demuxer
->stream
, STREAM_CTRL_GET_NUM_CHAPTERS
, num_chapters
) == STREAM_UNSUPPORTED
)
1173 *chapter_name
= NULL
;
1174 if (num_chapters
&& *num_chapters
) {
1175 char *tmp
= malloc(16);
1177 sprintf(tmp
, " of %3d", *num_chapters
);
1178 *chapter_name
= tmp
;
1183 return (ris
!= STREAM_UNSUPPORTED
? chapter
: -1);
1184 } else { //chapters structure is set in the demuxer
1185 total
= demuxer
->num_chapters
;
1187 if (mode
==1) { //absolute seeking
1189 } else { //relative seeking
1191 now
= (sh_video
? sh_video
->pts
: (sh_audio
? sh_audio
->pts
: 0.)) * 1000 + .5;
1193 for (current
= total
- 1; current
>= 0; --current
) {
1194 demux_chapter_t
* chapter
= demuxer
->chapters
+ current
;
1195 if (chapter
->start
<= now
)
1201 if (current
>= total
)
1203 if (current
< 0) current
= 0;
1205 *seek_pts
= demuxer
->chapters
[current
].start
/ 1000.0;
1208 *num_chapters
= demuxer
->num_chapters
;
1211 if(demuxer
->chapters
[current
].name
)
1212 *chapter_name
= strdup(demuxer
->chapters
[current
].name
);
1213 else *chapter_name
= NULL
;
1220 int demuxer_get_current_chapter(demuxer_t
*demuxer
) {
1222 if (!demuxer
->num_chapters
|| !demuxer
->chapters
) {
1223 if (stream_control(demuxer
->stream
, STREAM_CTRL_GET_CURRENT_CHAPTER
,
1224 &chapter
) == STREAM_UNSUPPORTED
)
1228 sh_video_t
*sh_video
= demuxer
->video
->sh
;
1229 sh_audio_t
*sh_audio
= demuxer
->audio
->sh
;
1231 now
= (sh_video
? sh_video
->pts
: (sh_audio
?sh_audio
->pts
:0))*1000+0.5;
1232 for (chapter
= demuxer
->num_chapters
- 1; chapter
>= 0; --chapter
) {
1233 if (demuxer
->chapters
[chapter
].start
<= now
)
1240 char *demuxer_chapter_name(demuxer_t
*demuxer
, int chapter
) {
1241 if (demuxer
->num_chapters
&& demuxer
->chapters
) {
1242 if (chapter
>=0 && chapter
< demuxer
->num_chapters
&&
1243 demuxer
->chapters
[chapter
].name
)
1244 return strdup(demuxer
->chapters
[chapter
].name
);
1249 char *demuxer_chapter_display_name(demuxer_t
*demuxer
, int chapter
) {
1250 char *chapter_name
= demuxer_chapter_name(demuxer
, chapter
);
1252 char *tmp
= malloc(strlen(chapter_name
) + 14);
1253 snprintf(tmp
, 63, "(%d) %s", chapter
+ 1, chapter_name
);
1258 int chapter_num
= demuxer_chapter_count(demuxer
);
1260 if (chapter_num
<= 0)
1261 sprintf(tmp
, "(%d)", chapter
+ 1);
1263 sprintf(tmp
, "(%d) of %d", chapter
+ 1, chapter_num
);
1268 float demuxer_chapter_time(demuxer_t
*demuxer
, int chapter
, float *end
) {
1269 if (demuxer
->num_chapters
&& demuxer
->chapters
&& chapter
>= 0
1270 && chapter
< demuxer
->num_chapters
) {
1272 *end
= demuxer
->chapters
[chapter
].end
/ 1000.0;
1273 return demuxer
->chapters
[chapter
].start
/ 1000.0;
1278 int demuxer_chapter_count(demuxer_t
*demuxer
) {
1279 if (!demuxer
->num_chapters
|| !demuxer
->chapters
) {
1280 int num_chapters
= 0;
1281 if (stream_control(demuxer
->stream
, STREAM_CTRL_GET_NUM_CHAPTERS
,
1282 &num_chapters
) == STREAM_UNSUPPORTED
)
1284 return num_chapters
;
1287 return demuxer
->num_chapters
;