/* * Copyright (C) 2000-2004 the xine project * * This file is part of xine, a free video player. * * xine 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. * * xine 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 */ /* * $Id: demux_ogg.c,v 1.160 2004/12/21 05:30:40 conrad Exp $ * * demultiplexer for ogg streams * */ /* 2003.02.09 (dilb) update of the handling for audio/video infos for strongarm cpus. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #ifdef HAVE_SPEEX #ifdef HAVE_SPEEX_SUBDIR #include #include #include #include #else #include #include #include #include #endif #endif #ifdef HAVE_THEORA #include #endif #define LOG_MODULE "demux_ogg" #define LOG_VERBOSE /* #define LOG */ #define DEBUG_PACKETS 0 #define DEBUG_PREVIEWS 0 #define DEBUG_PTS 0 #define DEBUG_VIDEO_PACKETS 0 #include "xine_internal.h" #include "xineutils.h" #include "demux.h" #include "bswap.h" #define CHUNKSIZE 8500 #define PACKET_TYPE_HEADER 0x01 #define PACKET_TYPE_COMMENT 0x03 #define PACKET_TYPE_CODEBOOK 0x05 #define PACKET_TYPE_BITS 0x07 #define PACKET_LEN_BITS01 0xc0 #define PACKET_LEN_BITS2 0x02 #define PACKET_IS_SYNCPOINT 0x08 #define MAX_STREAMS 32 #define PTS_AUDIO 0 #define PTS_VIDEO 1 #define WRAP_THRESHOLD 900000 #define SUB_BUFSIZE 1024 typedef struct chapter_entry_s { int64_t start_pts; char *name; } chapter_entry_t; typedef struct chapter_info_s { int current_chapter; int max_chapter; chapter_entry_t *entries; } chapter_info_t; typedef struct stream_info_s { ogg_stream_state oss; uint32_t buf_types; int headers; int64_t header_granulepos; int64_t factor; int64_t quotient; int resync; char *language; /* Annodex-specific stream information */ int hide_first_header; int delivered_bos; int delivered_eos; } stream_info_t; typedef struct demux_ogg_s { demux_plugin_t demux_plugin; xine_stream_t *stream; fifo_buffer_t *audio_fifo; fifo_buffer_t *video_fifo; input_plugin_t *input; int status; #ifdef HAVE_THEORA theora_info t_info; theora_comment t_comment; #endif int frame_duration; ogg_sync_state oy; ogg_page og; int64_t start_pts; int num_streams; stream_info_t *si[MAX_STREAMS]; /* stream info */ int num_audio_streams; int num_video_streams; int num_spu_streams; off_t avg_bitrate; int64_t last_pts[2]; int send_newpts; int buf_flag_seek; int keyframe_needed; int ignore_keyframes; int time_length; char *title; chapter_info_t *chapter_info; xine_event_queue_t *event_queue; } demux_ogg_t ; typedef struct { demux_class_t demux_class; } demux_ogg_class_t; typedef struct { demux_class_t demux_class; } demux_anx_class_t; #ifdef HAVE_THEORA static int intlog(int num) { int ret=0; while(num>0){ num=num/2; ret=ret+1; } return(ret); } #endif static int get_stream (demux_ogg_t *this, int serno) { /*finds the stream_num, which belongs to a ogg serno*/ int i; for (i = 0; inum_streams; i++) { if (this->si[i]->oss.serialno == serno) { return i; } } return -1; } static int new_stream_info (demux_ogg_t *this, const int cur_serno) { int stream_num; this->si[this->num_streams] = (stream_info_t *)xine_xmalloc(sizeof(stream_info_t)); ogg_stream_init(&this->si[this->num_streams]->oss, cur_serno); stream_num = this->num_streams; this->si[stream_num]->buf_types = 0; this->si[stream_num]->header_granulepos = -1; this->si[stream_num]->headers = 0; this->num_streams++; return stream_num; } static int64_t get_pts (demux_ogg_t *this, int stream_num , int64_t granulepos ) { /*calculates an pts from an granulepos*/ if (granulepos<0) { if ( this->si[stream_num]->header_granulepos>=0 ) { /*return the smallest valid pts*/ return 1; } else return 0; #ifdef HAVE_THEORA } else if (this->si[stream_num]->buf_types == BUF_VIDEO_THEORA) { int64_t iframe,pframe; int keyframe_granule_shift; keyframe_granule_shift=intlog(this->t_info.keyframe_frequency_force-1); iframe=granulepos>>keyframe_granule_shift; pframe=granulepos-(iframe<frame_duration); #endif } else if (this->si[stream_num]->quotient) return 1+(granulepos * this->si[stream_num]->factor / this->si[stream_num]->quotient); else return 0; } static int read_ogg_packet (demux_ogg_t *this) { char *buffer; long bytes; while (ogg_sync_pageout(&this->oy,&this->og)!=1) { buffer = ogg_sync_buffer(&this->oy, CHUNKSIZE); bytes = this->input->read(this->input, buffer, CHUNKSIZE); ogg_sync_wrote(&this->oy, bytes); if (bytes < CHUNKSIZE/2) { return 0; } } return 1; } static void get_stream_length (demux_ogg_t *this) { /*determine the streamlenght and set this->time_length accordingly. ATTENTION:current_pos and oggbuffers will be destroyed by this function, there will be no way to continue playback uninterrupted. You have to seek afterwards, because after get_stream_length, the current_position is at the end of the file */ off_t filelength; int done=0; int stream_num; this->time_length=-1; if (this->input->get_capabilities(this->input) & INPUT_CAP_SEEKABLE) { filelength=this->input->get_length(this->input); if (filelength!=-1) { if (filelength>70000) { this->demux_plugin.seek(&this->demux_plugin, (off_t) ( (double)(filelength-65536)/filelength*65535), 0, 0); } done=0; while (!done) { if (!read_ogg_packet (this)) { if (this->time_length) { _x_stream_info_set(this->stream, XINE_STREAM_INFO_BITRATE, ((int64_t) 8000*filelength)/this->time_length); /*this is a fine place to compute avg_bitrate*/ this->avg_bitrate= 8000*filelength/this->time_length; } return; } stream_num=get_stream(this, ogg_page_serialno (&this->og) ); if (stream_num!=-1) { if (this->time_length < (get_pts(this, stream_num, ogg_page_granulepos(&this->og) / 90))) this->time_length = get_pts(this, stream_num, ogg_page_granulepos(&this->og)) / 90; } } } } } #ifdef HAVE_THEORA static void send_ogg_packet (demux_ogg_t *this, fifo_buffer_t *fifo, ogg_packet *op, int64_t pts, uint32_t decoder_flags, int stream_num) { buf_element_t *buf; int done=0,todo=op->bytes; int op_size = sizeof(ogg_packet); while (donebuffer_pool_alloc (fifo); buf->decoder_flags = decoder_flags; if (done==0) { memcpy (buf->content, op, op_size); offset=op_size; buf->decoder_flags = buf->decoder_flags | BUF_FLAG_FRAME_START; } if (done+buf->max_size-offset < todo) { memcpy (buf->content+offset, op->packet+done, buf->max_size-offset); buf->size = buf->max_size; done=done+buf->max_size-offset; } else { memcpy (buf->content+offset , op->packet+done, todo-done); buf->size = todo-done+offset; done=todo; buf->decoder_flags = buf->decoder_flags | BUF_FLAG_FRAME_END; } buf->pts = pts; if( this->input->get_length (this->input) ) buf->extra_info->input_normpos = (int)( (double) this->input->get_current_pos (this->input) * 65535 / this->input->get_length (this->input) ); buf->extra_info->input_time = buf->pts / 90 ; buf->type = this->si[stream_num]->buf_types; fifo->put (fifo, buf); } } #endif /* redefine abs as macro to handle 64-bit diffs. i guess llabs may not be available everywhere */ #define abs(x) ( ((x)<0) ? -(x) : (x) ) static void check_newpts (demux_ogg_t *this, int64_t pts, int video, int preview) { int64_t diff; llprintf(DEBUG_PTS, "new pts %" PRId64 " found in stream\n",pts); diff = pts - this->last_pts[video]; if (!preview && (pts>=0) && (this->send_newpts || (this->last_pts[video] && abs(diff)>WRAP_THRESHOLD) ) ) { xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "diff=%" PRId64 " (pts=%" PRId64 ", last_pts=%" PRId64 ")\n", diff, pts, this->last_pts[video]); if (this->buf_flag_seek) { _x_demux_control_newpts(this->stream, pts, BUF_FLAG_SEEK); this->buf_flag_seek = 0; } else { _x_demux_control_newpts(this->stream, pts, 0); } this->send_newpts = 0; this->last_pts[1-video] = 0; } if (!preview && (pts>=0) ) this->last_pts[video] = pts; /* use pts for bitrate measurement */ /*compute avg_bitrate if time_length isn't set*/ if ((pts>180000) && !(this->time_length)) { this->avg_bitrate = this->input->get_current_pos (this->input) * 8 * 90000/ pts; if (this->avg_bitrate<1) this->avg_bitrate = 1; } } static void ogg_handle_event (demux_ogg_t *this) { xine_event_t *event; while ((event = xine_event_get(this->event_queue))) { switch(event->type) { case XINE_EVENT_INPUT_NEXT: { if (this->chapter_info) { int c_chap = this->chapter_info->current_chapter; if (c_chap+1 < this->chapter_info->max_chapter) { int start_time = this->chapter_info->entries[c_chap+1].start_pts / 90; this->demux_plugin.seek((demux_plugin_t *)this, 0, start_time, 1); } } } break; case XINE_EVENT_INPUT_PREVIOUS: { if (this->chapter_info) { int c_chap = this->chapter_info->current_chapter; if (c_chap >= 1) { int start_time = this->chapter_info->entries[c_chap-1].start_pts / 90; this->demux_plugin.seek((demux_plugin_t *)this, 0, start_time, 1); } } } break; } xine_event_free(event); } return; } /* * utility function to read a LANGUAGE= line from the user_comments, * to label audio and spu streams */ static void read_language_comment (demux_ogg_t *this, ogg_packet *op, int stream_num) { char **ptr; char *comment; vorbis_comment vc; vorbis_info vi; vorbis_comment_init(&vc); vorbis_info_init(&vi); /* this is necessary to make libvorbis accept this vorbis_info*/ vi.rate=1; if ( vorbis_synthesis_headerin(&vi, &vc, op) >= 0) { ptr=vc.user_comments; while(*ptr) { comment=*ptr; if ( !strncasecmp ("LANGUAGE=", comment, 9) ) { this->si[stream_num]->language = strdup (comment + strlen ("LANGUAGE=") ); } ++ptr; } } vorbis_comment_clear(&vc); vorbis_info_clear(&vi); } /* * utility function to read CHAPTER*= and TITLE= from the user_comments, * to name parts of the videostream */ static void read_chapter_comment (demux_ogg_t *this, ogg_packet *op) { char **ptr; char *comment; vorbis_comment vc; vorbis_info vi; vorbis_comment_init(&vc); vorbis_info_init(&vi); /* this is necessary to make libvorbis accept this vorbis_info*/ vi.rate=1; if ( vorbis_synthesis_headerin(&vi, &vc, op) >= 0) { char *chapter_time = 0; char *chapter_name = 0; int chapter_no = 0; ptr=vc.user_comments; while(*ptr) { comment=*ptr; if ( !strncasecmp ("TITLE=", comment,6) ) { this->title = strdup (comment + strlen ("TITLE=") ); _x_meta_info_set(this->stream, XINE_META_INFO_TITLE, this->title); } if ( !chapter_time && strlen(comment) == 22 && !strncasecmp ("CHAPTER" , comment, 7) && isdigit(*(comment+7)) && isdigit(*(comment+8)) && (*(comment+9) == '=')) { chapter_time = strdup(comment+10); chapter_no = strtol(comment+7, NULL, 10); } if ( !chapter_name && !strncasecmp("CHAPTER", comment, 7) && isdigit(*(comment+7)) && isdigit(*(comment+8)) && !strncasecmp ("NAME=", comment+9, 5)) { if (strtol(comment+7,NULL,10) == chapter_no) { chapter_name = strdup(comment+14); } } if (chapter_time && chapter_name && chapter_no){ int hour, min, sec, msec; lprintf("create chapter entry: no=%d name=%s time=%s\n", chapter_no, chapter_name, chapter_time); hour= strtol(chapter_time, NULL, 10); min = strtol(chapter_time+3, NULL, 10); sec = strtol(chapter_time+6, NULL, 10); msec = strtol(chapter_time+9, NULL, 10); lprintf("time: %d %d %d %d\n", hour, min,sec,msec); if (!this->chapter_info) { this->chapter_info = (chapter_info_t *)xine_xmalloc(sizeof(chapter_info_t)); this->chapter_info->current_chapter = -1; } this->chapter_info->max_chapter = chapter_no; this->chapter_info->entries = realloc( this->chapter_info->entries, chapter_no*sizeof(chapter_entry_t)); this->chapter_info->entries[chapter_no-1].name = chapter_name; this->chapter_info->entries[chapter_no-1].start_pts = (msec + (1000.0 * sec) + (60000.0 * min) + (3600000.0 * hour))*90; free (chapter_time); chapter_no = 0; chapter_time = chapter_name = 0; } ++ptr; } } vorbis_comment_clear(&vc); vorbis_info_clear(&vi); } /* * update the display of the title, if needed */ static void update_chapter_display (demux_ogg_t *this, int stream_num, ogg_packet *op) { int chapter = 0; int64_t pts = get_pts(this, stream_num, op->granulepos ); while (chapter < this->chapter_info->max_chapter && this->chapter_info->entries[chapter].start_pts < pts) { chapter++; } chapter--; if (chapter != this->chapter_info->current_chapter){ xine_event_t uevent; xine_ui_data_t data; int title_len; char *title; this->chapter_info->current_chapter = chapter; if (chapter >= 0) { char t_title[256]; snprintf(t_title, sizeof (t_title), "%s / %s", this->title, this->chapter_info->entries[chapter].name); title = t_title; } else { title = this->title; } _x_meta_info_set(this->stream, XINE_META_INFO_TITLE, title); lprintf("new TITLE: %s\n", title); uevent.type = XINE_EVENT_UI_SET_TITLE; uevent.stream = this->stream; uevent.data = &data; uevent.data_length = sizeof(data); title_len = strlen(title) + 1; memcpy(data.str, title, title_len); data.str_len = title_len; xine_event_send(this->stream, &uevent); } } /* * utility function to pack one ogg_packet into a xine * buffer, fill out all needed fields * and send it to the right fifo */ static void send_ogg_buf (demux_ogg_t *this, ogg_packet *op, int stream_num, uint32_t decoder_flags) { int hdrlen; int normpos = 0; if( this->input->get_length (this->input) ) normpos = (int)( (double) this->input->get_current_pos (this->input) * 65535 / this->input->get_length (this->input) ); hdrlen = (*op->packet & PACKET_LEN_BITS01) >> 6; hdrlen |= (*op->packet & PACKET_LEN_BITS2) << 1; /* for Annodex files: the first packet after the AnxData info packet needs * to have its BOS flag set: we set it here */ if (!this->si[stream_num]->delivered_bos) { op->b_o_s = 1; this->si[stream_num]->delivered_bos = 1; } if ( this->audio_fifo && (this->si[stream_num]->buf_types & 0xFF000000) == BUF_AUDIO_BASE) { uint8_t *data; int size; int64_t pts; if (op->packet[0] == PACKET_TYPE_COMMENT ) { read_language_comment(this, op, stream_num); } if ((this->si[stream_num]->buf_types & 0xFFFF0000) == BUF_AUDIO_SPEEX || (this->si[stream_num]->buf_types & 0xFFFF0000) == BUF_AUDIO_VORBIS) { data = op->packet; size = op->bytes; } else { data = op->packet+1+hdrlen; size = op->bytes-1-hdrlen; } llprintf(DEBUG_PACKETS, "audio data size %d\n", size); if ((op->granulepos != -1) || (this->si[stream_num]->header_granulepos != -1)) { pts = get_pts(this, stream_num, op->granulepos ); check_newpts( this, pts, PTS_AUDIO, decoder_flags ); } else pts = 0; llprintf(DEBUG_PACKETS, "audiostream %d op-gpos %" PRId64 " hdr-gpos %" PRId64 " pts %" PRId64 " \n", stream_num, op->granulepos, this->si[stream_num]->header_granulepos, pts); _x_demux_send_data(this->audio_fifo, data, size, pts, this->si[stream_num]->buf_types, decoder_flags, normpos, pts / 90, this->time_length, 0); #ifdef HAVE_THEORA } else if ((this->si[stream_num]->buf_types & 0xFFFF0000) == BUF_VIDEO_THEORA) { int64_t pts; theora_info t_info; theora_comment t_comment; theora_info_init (&t_info); theora_comment_init (&t_comment); /*Lets see if this is an Header*/ if ((theora_decode_header(&t_info, &t_comment, op))>=0) { decoder_flags=decoder_flags|BUF_FLAG_HEADER; lprintf ("found an header\n"); } if ((op->granulepos != -1) || (this->si[stream_num]->header_granulepos != -1)) { pts = get_pts(this, stream_num, op->granulepos ); check_newpts( this, pts, PTS_VIDEO, decoder_flags ); } else pts = 0; llprintf(DEBUG_PACKETS, "theorastream %d op-gpos %" PRId64 " hdr-gpos %" PRId64 " pts %" PRId64 " \n", stream_num, op->granulepos, this->si[stream_num]->header_granulepos, pts); send_ogg_packet (this, this->video_fifo, op, pts, decoder_flags, stream_num); theora_comment_clear (&t_comment); theora_info_clear (&t_info); #endif } else if ((this->si[stream_num]->buf_types & 0xFF000000) == BUF_VIDEO_BASE) { uint8_t *data; int size; int64_t pts; llprintf(DEBUG_VIDEO_PACKETS, "video buffer, type=%08x\n", this->si[stream_num]->buf_types); if (op->packet[0] == PACKET_TYPE_COMMENT ) { read_chapter_comment(this, op); }else{ data = op->packet+1+hdrlen; size = op->bytes-1-hdrlen; if ((op->granulepos != -1) || (this->si[stream_num]->header_granulepos != -1)) { pts = get_pts(this, stream_num, op->granulepos ); check_newpts( this, pts, PTS_VIDEO, decoder_flags ); } else pts = 0; llprintf(DEBUG_VIDEO_PACKETS, "videostream %d op-gpos %" PRId64 " hdr-gpos %" PRId64 " pts %" PRId64 " \n", stream_num, op->granulepos, this->si[stream_num]->header_granulepos, pts); _x_demux_send_data(this->video_fifo, data, size, pts, this->si[stream_num]->buf_types, decoder_flags, normpos, pts / 90, this->time_length, 0); if (this->chapter_info && op->granulepos != -1) { update_chapter_display(this, stream_num, op); } } } else if ((this->si[stream_num]->buf_types & 0xFFFF0000) == BUF_SPU_CMML) { buf_element_t *buf; uint32_t *val; char *str; buf = this->video_fifo->buffer_pool_alloc (this->video_fifo); buf->type = this->si[stream_num]->buf_types; /* CMML granulepos is (1000 * seconds); pts is (90000 * seconds) */ buf->pts = op->granulepos * (90000 / 1000); val = (uint32_t * )buf->content; str = (char *)val; memcpy(str, op->packet, op->bytes); str[op->bytes] = '\0'; buf->size = 12 + op->bytes + 1; lprintf ("CMML stream %d (bytes=%ld): PTS %lld: %s\n", stream_num, op->bytes, buf->pts, str); this->video_fifo->put (this->video_fifo, buf); } else if ((this->si[stream_num]->buf_types & 0xFF000000) == BUF_SPU_BASE) { buf_element_t *buf; int i; char *subtitle,*str; int lenbytes; int start,end; uint32_t *val; for (i = 0, lenbytes = 0; i < hdrlen; i++) { lenbytes = lenbytes << 8; lenbytes += *((unsigned char *) op->packet + hdrlen - i); } if (op->packet[0] == PACKET_TYPE_HEADER ) { lprintf ("Textstream-header-packet\n"); } else if (op->packet[0] == PACKET_TYPE_COMMENT ) { lprintf ("Textstream-comment-packet\n"); read_language_comment(this, op, stream_num); } else { subtitle = (char *)&op->packet[hdrlen + 1]; if ((strlen(subtitle) > 1) || (*subtitle != ' ')) { start = op->granulepos; end = start+lenbytes; lprintf ("subtitlestream %d: %d -> %d :%s\n",stream_num,start,end,subtitle); buf = this->video_fifo->buffer_pool_alloc (this->video_fifo); buf->type = this->si[stream_num]->buf_types; buf->pts = 0; val = (uint32_t * )buf->content; *val++ = start; *val++ = end; str = (char *)val; memcpy (str, subtitle, 1+strlen(subtitle)); this->video_fifo->put (this->video_fifo, buf); } } } else { lprintf("unknown stream type %x\n", this->si[stream_num]->buf_types); } } static void decode_vorbis_header (demux_ogg_t *this, const int stream_num, ogg_packet *op) { vorbis_info vi; vorbis_comment vc; this->si[stream_num]->buf_types = BUF_AUDIO_VORBIS +this->num_audio_streams++; this->si[stream_num]->headers = 3; vorbis_info_init(&vi); vorbis_comment_init(&vc); if (vorbis_synthesis_headerin(&vi, &vc, op) >= 0) { _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_BITRATE, vi.bitrate_nominal); _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_SAMPLERATE, vi.rate); this->si[stream_num]->factor = 90000; this->si[stream_num]->quotient = vi.rate; if (vi.bitrate_nominal<1) this->avg_bitrate += 100000; /* assume 100 kbit */ else this->avg_bitrate += vi.bitrate_nominal; } else { this->si[stream_num]->factor = 900; this->si[stream_num]->quotient = 441; this->si[stream_num]->headers = 0; xine_log (this->stream->xine, XINE_LOG_MSG, _("ogg: vorbis audio track indicated but no vorbis stream header found.\n")); } vorbis_comment_clear(&vc); vorbis_info_clear(&vi); } static void decode_speex_header (demux_ogg_t *this, const int stream_num, ogg_packet *op) { #ifdef HAVE_SPEEX void *st; SpeexMode *mode; SpeexHeader *header; this->si[stream_num]->buf_types = BUF_AUDIO_SPEEX +this->num_audio_streams++; this->si[stream_num]->headers = 1; header = speex_packet_to_header (op->packet, op->bytes); if (header) { int bitrate; mode = (SpeexMode *) speex_mode_list[header->mode]; st = speex_decoder_init (mode); speex_decoder_ctl (st, SPEEX_GET_BITRATE, &bitrate); if (bitrate <= 1) bitrate = 16000; /* assume 16 kbit */ _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_BITRATE, bitrate); this->si[stream_num]->factor = 90000; this->si[stream_num]->quotient = header->rate; this->avg_bitrate += bitrate; lprintf ("detected Speex stream,\trate %d\tbitrate %d\n", header->rate, bitrate); _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_SAMPLERATE, header->rate); this->si[stream_num]->headers += header->extra_headers; } #else xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "Speex stream detected, unable to play\n"); this->si[stream_num]->buf_types = BUF_CONTROL_NOP; #endif } static void decode_video_header (demux_ogg_t *this, const int stream_num, ogg_packet *op) { buf_element_t *buf; xine_bmiheader bih; int channel; int16_t locbits_per_sample; uint32_t locsubtype; int32_t locsize, locdefault_len, locbuffersize, locwidth, locheight; int64_t loctime_unit, locsamples_per_unit; /* read fourcc with machine endianness */ locsubtype = *((uint32_t *)&op->packet[9]); /* everything else little endian */ locsize = LE_32(&op->packet[13]); loctime_unit = LE_64(&op->packet[17]); locsamples_per_unit = LE_64(&op->packet[25]); locdefault_len = LE_32(&op->packet[33]); locbuffersize = LE_32(&op->packet[37]); locbits_per_sample = LE_16(&op->packet[41]); locwidth = LE_32(&op->packet[45]); locheight = LE_32(&op->packet[49]); lprintf ("direct show filter created stream detected, hexdump:\n"); #ifdef LOG xine_hexdump (op->packet, op->bytes); #endif channel = this->num_video_streams++; this->si[stream_num]->buf_types = _x_fourcc_to_buf_video (locsubtype); if( !this->si[stream_num]->buf_types ) this->si[stream_num]->buf_types = BUF_VIDEO_UNKNOWN; this->si[stream_num]->buf_types |= channel; this->si[stream_num]->headers = 0; /* header is sent below */ lprintf ("subtype %.4s\n", (char*)&locsubtype); lprintf ("time_unit %" PRId64 "\n", loctime_unit); lprintf ("samples_per_unit %" PRId64 "\n", locsamples_per_unit); lprintf ("default_len %d\n", locdefault_len); lprintf ("buffersize %d\n", locbuffersize); lprintf ("bits_per_sample %d\n", locbits_per_sample); lprintf ("width %d\n", locwidth); lprintf ("height %d\n", locheight); lprintf ("buf_type %08x\n",this->si[stream_num]->buf_types); bih.biSize=sizeof(xine_bmiheader); bih.biWidth = locwidth; bih.biHeight= locheight; bih.biPlanes= 0; memcpy(&bih.biCompression, &locsubtype, 4); bih.biBitCount= 0; bih.biSizeImage=locwidth*locheight; bih.biXPelsPerMeter=1; bih.biYPelsPerMeter=1; bih.biClrUsed=0; bih.biClrImportant=0; buf = this->video_fifo->buffer_pool_alloc (this->video_fifo); buf->decoder_flags = BUF_FLAG_HEADER|BUF_FLAG_STDHEADER|BUF_FLAG_FRAMERATE| BUF_FLAG_FRAME_END; this->frame_duration = loctime_unit * 9 / 1000; this->si[stream_num]->factor = loctime_unit * 9; this->si[stream_num]->quotient = 1000; buf->decoder_info[0] = this->frame_duration; memcpy (buf->content, &bih, sizeof (xine_bmiheader)); buf->size = sizeof (xine_bmiheader); buf->type = this->si[stream_num]->buf_types; /* video metadata */ _x_stream_info_set(this->stream, XINE_STREAM_INFO_VIDEO_FOURCC, locsubtype); _x_stream_info_set(this->stream, XINE_STREAM_INFO_VIDEO_WIDTH, locwidth); _x_stream_info_set(this->stream, XINE_STREAM_INFO_VIDEO_HEIGHT, locheight); _x_stream_info_set(this->stream, XINE_STREAM_INFO_FRAME_DURATION, this->frame_duration); this->avg_bitrate += 500000; /* FIXME */ this->video_fifo->put (this->video_fifo, buf); } static void decode_audio_header (demux_ogg_t *this, const int stream_num, ogg_packet *op) { if (this->audio_fifo) { buf_element_t *buf; int codec; char str[5]; int channel; int16_t locbits_per_sample, locchannels, locblockalign; int32_t locsize, locdefault_len, locbuffersize, locavgbytespersec; int64_t loctime_unit, locsamples_per_unit; locsize = LE_32(&op->packet[13]); loctime_unit = LE_64(&op->packet[17]); locsamples_per_unit = LE_64(&op->packet[25]); locdefault_len = LE_32(&op->packet[33]); locbuffersize = LE_32(&op->packet[37]); locbits_per_sample = LE_16(&op->packet[41]); locchannels = LE_16(&op->packet[45]); locblockalign = LE_16(&op->packet[47]); locavgbytespersec= LE_32(&op->packet[49]); lprintf ("direct show filter created audio stream detected, hexdump:\n"); #ifdef LOG xine_hexdump (op->packet, op->bytes); #endif memcpy(str, &op->packet[9], 4); str[4] = 0; codec = strtoul(str, NULL, 16); channel= this->num_audio_streams++; this->si[stream_num]->buf_types = _x_formattag_to_buf_audio(codec); if( this->si[stream_num]->buf_types ) { this->si[stream_num]->buf_types |= channel; } else { xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "demux_ogg: unknown audio codec type 0x%x\n", codec); this->si[stream_num]->buf_types = BUF_AUDIO_UNKNOWN; /*break;*/ } lprintf ("subtype 0x%x\n", codec); lprintf ("time_unit %" PRId64 "\n", loctime_unit); lprintf ("samples_per_unit %" PRId64 "\n", locsamples_per_unit); lprintf ("default_len %d\n", locdefault_len); lprintf ("buffersize %d\n", locbuffersize); lprintf ("bits_per_sample %d\n", locbits_per_sample); lprintf ("channels %d\n", locchannels); lprintf ("blockalign %d\n", locblockalign); lprintf ("avgbytespersec %d\n", locavgbytespersec); lprintf ("buf_type %08x\n",this->si[stream_num]->buf_types); buf = this->audio_fifo->buffer_pool_alloc (this->audio_fifo); buf->type = this->si[stream_num]->buf_types; buf->decoder_flags = BUF_FLAG_HEADER|BUF_FLAG_STDHEADER|BUF_FLAG_FRAME_END; buf->decoder_info[0] = 0; buf->decoder_info[1] = locsamples_per_unit; buf->decoder_info[2] = locbits_per_sample; buf->decoder_info[3] = locchannels; this->audio_fifo->put (this->audio_fifo, buf); this->si[stream_num]->headers = 0; /* header already sent */ this->si[stream_num]->factor = 90000; this->si[stream_num]->quotient = locsamples_per_unit; this->avg_bitrate += locavgbytespersec*8; /* audio metadata */ _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_FOURCC, codec); _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_CHANNELS, locchannels); _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_BITS, locbits_per_sample); _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_SAMPLERATE, locsamples_per_unit); _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_BITRATE, locavgbytespersec * 8); } else /* no audio_fifo there */ this->si[stream_num]->buf_types = BUF_CONTROL_NOP; } static void decode_dshow_header (demux_ogg_t *this, const int stream_num, ogg_packet *op) { lprintf ("older Direct Show filter-generated stream header detected. Hexdump:\n"); #ifdef LOG xine_hexdump (op->packet, op->bytes); #endif this->si[stream_num]->headers = 0; /* header is sent below */ if ( (LE_32(&op->packet[96]) == 0x05589f80) && (op->bytes >= 184)) { buf_element_t *buf; xine_bmiheader bih; int channel; uint32_t fcc; lprintf ("seems to be a video stream.\n"); channel = this->num_video_streams++; fcc = *(uint32_t*)(op->packet+68); lprintf ("fourcc %08x\n", fcc); this->si[stream_num]->buf_types = _x_fourcc_to_buf_video (fcc); if( !this->si[stream_num]->buf_types ) this->si[stream_num]->buf_types = BUF_VIDEO_UNKNOWN; this->si[stream_num]->buf_types |= channel; bih.biSize = sizeof(xine_bmiheader); bih.biWidth = LE_32(&op->packet[176]); bih.biHeight = LE_32(&op->packet[180]); bih.biPlanes = 0; memcpy (&bih.biCompression, op->packet+68, 4); bih.biBitCount = LE_16(&op->packet[182]); if (!bih.biBitCount) bih.biBitCount = 24; /* FIXME ? */ bih.biSizeImage = (bih.biBitCount>>3)*bih.biWidth*bih.biHeight; bih.biXPelsPerMeter = 1; bih.biYPelsPerMeter = 1; bih.biClrUsed = 0; bih.biClrImportant = 0; buf = this->video_fifo->buffer_pool_alloc (this->video_fifo); buf->decoder_flags = BUF_FLAG_HEADER|BUF_FLAG_STDHEADER|BUF_FLAG_FRAMERATE| BUF_FLAG_FRAME_END; this->frame_duration = (*(int64_t*)(op->packet+164)) * 9 / 1000; this->si[stream_num]->factor = (*(int64_t*)(op->packet+164)) * 9; this->si[stream_num]->quotient = 1000; buf->decoder_info[0] = this->frame_duration; memcpy (buf->content, &bih, sizeof (xine_bmiheader)); buf->size = sizeof (xine_bmiheader); buf->type = this->si[stream_num]->buf_types; this->video_fifo->put (this->video_fifo, buf); lprintf ("subtype %.4s\n", (char*)&fcc); lprintf ("buf_type %08x\n", this->si[stream_num]->buf_types); lprintf ("video size %d x %d\n", bih.biWidth, bih.biHeight); lprintf ("frame duration %d\n", this->frame_duration); /* video metadata */ _x_stream_info_set(this->stream, XINE_STREAM_INFO_VIDEO_WIDTH, bih.biWidth); _x_stream_info_set(this->stream, XINE_STREAM_INFO_VIDEO_HEIGHT, bih.biHeight); _x_stream_info_set(this->stream, XINE_STREAM_INFO_FRAME_DURATION, this->frame_duration); this->avg_bitrate += 500000; /* FIXME */ this->ignore_keyframes = 1; } else if (LE_32(&op->packet[96]) == 0x05589F81) { #if 0 /* FIXME: no test streams */ buf_element_t *buf; int codec; char str[5]; int channel; int extra_size; extra_size = *(int16_t*)(op->packet+140); format = *(int16_t*)(op->packet+124); channels = *(int16_t*)(op->packet+126); samplerate = *(int32_t*)(op->packet+128); nAvgBytesPerSec = *(int32_t*)(op->packet+132); nBlockAlign = *(int16_t*)(op->packet+136); wBitsPerSample = *(int16_t*)(op->packet+138); samplesize = (sh_a->wf->wBitsPerSample+7)/8; cbSize = extra_size; if(extra_size > 0) memcpy(wf+sizeof(WAVEFORMATEX),op->packet+142,extra_size); #endif xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "FIXME, old audio format not handled\n"); this->si[stream_num]->buf_types = BUF_CONTROL_NOP; } else { xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "old header detected but stream type is unknown\n"); this->si[stream_num]->buf_types = BUF_CONTROL_NOP; } } static void decode_text_header (demux_ogg_t *this, const int stream_num, ogg_packet *op) { int channel=0; uint32_t *val; buf_element_t *buf; lprintf ("textstream detected.\n"); this->si[stream_num]->headers = 2; channel = this->num_spu_streams++; this->si[stream_num]->buf_types = BUF_SPU_OGM | channel; /*send an empty spu to inform the video_decoder, that there is a stream*/ buf = this->video_fifo->buffer_pool_alloc (this->video_fifo); buf->type = this->si[stream_num]->buf_types; buf->pts = 0; val = (uint32_t * )buf->content; *val++=0; *val++=0; *val++=0; this->video_fifo->put (this->video_fifo, buf); } static void decode_theora_header (demux_ogg_t *this, const int stream_num, ogg_packet *op) { #ifdef HAVE_THEORA xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "demux_ogg: Theorastreamsupport is highly alpha at the moment\n"); if (theora_decode_header(&this->t_info, &this->t_comment, op) >= 0) { this->num_video_streams++; this->si[stream_num]->factor = (int64_t) 90000 * (int64_t) this->t_info.fps_denominator; this->si[stream_num]->quotient = this->t_info.fps_numerator; this->frame_duration = ((int64_t) 90000*this->t_info.fps_denominator)/this->t_info.fps_numerator; this->si[stream_num]->headers=3; this->si[stream_num]->buf_types = BUF_VIDEO_THEORA; _x_meta_info_set(this->stream, XINE_META_INFO_VIDEOCODEC, "theora"); _x_stream_info_set(this->stream, XINE_STREAM_INFO_VIDEO_WIDTH, this->t_info.frame_width); _x_stream_info_set(this->stream, XINE_STREAM_INFO_VIDEO_HEIGHT, this->t_info.frame_height); _x_stream_info_set(this->stream, XINE_STREAM_INFO_FRAME_DURATION, ((int64_t) 90000 * this->t_info.fps_denominator) / this->t_info.fps_numerator); /*currently aspect_nominator and -denumerator are 0?*/ if (this->t_info.aspect_denominator) _x_stream_info_set(this->stream, XINE_STREAM_INFO_VIDEO_RATIO, ((int64_t) this->t_info.aspect_numerator * 10000) / this->t_info.aspect_denominator); lprintf ("decoded theora header \n"); lprintf ("frameduration %d\n",this->frame_duration); lprintf ("w:%d h:%d \n",this->t_info.frame_width,this->t_info.frame_height); lprintf ("an:%d ad:%d \n",this->t_info.aspect_numerator,this->t_info.aspect_denominator); } else { /*Rejected stream*/ xprintf (this->stream->xine, XINE_VERBOSITY_DEBUG, "A theora header was rejected by libtheora\n"); this->si[stream_num]->buf_types = BUF_CONTROL_NOP; this->si[stream_num]->headers = 0; /* FIXME: don't know */ } #else this->si[stream_num]->buf_types = BUF_VIDEO_THEORA; _x_meta_info_set(this->stream, XINE_META_INFO_VIDEOCODEC, "theora"); #endif } static void decode_annodex_header (demux_ogg_t *this, const int stream_num, ogg_packet *op) { lprintf ("Annodex stream detected\n"); this->si[stream_num]->buf_types = BUF_CONTROL_NOP; this->si[stream_num]->headers = 1; this->si[stream_num]->header_granulepos = op->granulepos; _x_meta_info_set(this->stream, XINE_META_INFO_SYSTEMLAYER, "Annodex"); } static void decode_anxdata_header (demux_ogg_t *this, const int stream_num, ogg_packet *op) { int64_t granule_rate_n, granule_rate_d; uint32_t secondary_headers; char content_type[1024]; int content_type_length; lprintf("AnxData stream detected\n"); /* read granule rate */ granule_rate_n = LE_64(&op->packet[8]); granule_rate_d = LE_64(&op->packet[16]); secondary_headers = LE_32(&op->packet[24]); lprintf("granule_rate %" PRId64 "/%" PRId64 ", %d secondary headers\n", granule_rate_n, granule_rate_d, secondary_headers); /* read "Content-Tyoe" MIME header */ sscanf(&op->packet[28], "Content-Type: %1023s\r\n", content_type); content_type_length = strlen(content_type); lprintf("Content-Type: %s (length:%d)\n", content_type, content_type_length); /* how many header packets in the AnxData stream? */ this->si[stream_num]->headers = secondary_headers + 1; this->si[stream_num]->hide_first_header = 1; /* set factor and quotient */ this->si[stream_num]->factor = (int64_t) 90000 * granule_rate_d; this->si[stream_num]->quotient = granule_rate_n; lprintf("factor: %" PRId64 ", quotient: %" PRId64 "\n", this->si[stream_num]->factor, this->si[stream_num]->quotient); /* what type of stream are we dealing with? */ if (!strncmp(content_type, "audio/x-vorbis", content_type_length)) { this->si[stream_num]->buf_types = BUF_AUDIO_VORBIS; this->num_audio_streams++; } else if (!strncmp(content_type, "audio/x-speex", content_type_length)) { #ifdef HAVE_SPEEX this->si[stream_num]->buf_types = BUF_AUDIO_SPEEX; this->num_audio_streams++; #else this->si[stream_num]->buf_types = BUF_CONTROL_NOP; #endif } else if (!strncmp(content_type, "video/x-theora", content_type_length)) { #ifdef HAVE_THEORA this->si[stream_num]->buf_types = BUF_VIDEO_THEORA; this->num_video_streams++; #else this->si[stream_num]->buf_types = BUF_CONTROL_NOP; #endif } else if (!strncmp(content_type, "text/x-cmml", content_type_length)) { unsigned int channel = this->num_spu_streams++; this->si[stream_num]->headers = 0; this->si[stream_num]->buf_types = BUF_SPU_CMML | channel; } else { this->si[stream_num]->buf_types = BUF_CONTROL_NOP; } } static void decode_cmml_header (demux_ogg_t *this, const int stream_num, ogg_packet *op) { unsigned int channel = this->num_spu_streams++; this->si[stream_num]->headers = 0; this->si[stream_num]->buf_types = BUF_SPU_CMML | channel; } /* * interpret stream start packages, send headers */ static void send_header (demux_ogg_t *this) { int stream_num = -1; int cur_serno; int done = 0; ogg_packet op; xine_event_t ui_event; lprintf ("detecting stream types...\n"); this->ignore_keyframes = 0; while (!done) { if (!read_ogg_packet(this)) { this->status = DEMUX_FINISHED; return; } /* now we've got at least one new page */ cur_serno = ogg_page_serialno (&this->og); if (ogg_page_bos(&this->og)) { lprintf ("beginning of stream\n"); lprintf ("serial number %d\n", cur_serno); if( this->num_streams == MAX_STREAMS ) { xprintf (this->stream->xine, XINE_VERBOSITY_LOG, "demux_ogg: MAX_STREAMS exceeded, aborting.\n"); this->status = DEMUX_FINISHED; return; } stream_num = new_stream_info(this, cur_serno); } else { stream_num = get_stream(this, cur_serno); if (stream_num == -1) { xprintf (this->stream->xine, XINE_VERBOSITY_LOG, "demux_ogg: stream with no beginning!\n"); this->status = DEMUX_FINISHED; return; } } ogg_stream_pagein(&this->si[stream_num]->oss, &this->og); while (ogg_stream_packetout(&this->si[stream_num]->oss, &op) == 1) { if (!this->si[stream_num]->buf_types) { /* detect buftype */ if (!strncmp (&op.packet[1], "vorbis", 6)) { decode_vorbis_header(this, stream_num, &op); } else if (!strncmp (&op.packet[0], "Speex", 5)) { decode_speex_header(this, stream_num, &op); } else if (!strncmp (&op.packet[1], "video", 5)) { decode_video_header(this, stream_num, &op); } else if (!strncmp (&op.packet[1], "audio", 5)) { decode_audio_header(this, stream_num, &op); } else if (op.bytes >= 142 && !strncmp (&op.packet[1], "Direct Show Samples embedded in Ogg", 35) ) { decode_dshow_header(this, stream_num, &op); } else if (!strncmp (&op.packet[1], "text", 4)) { decode_text_header(this, stream_num, &op); } else if (!strncmp (&op.packet[1], "theora", 6)) { decode_theora_header(this, stream_num, &op); } else if (!strncmp (&op.packet[0], "Annodex", 7)) { decode_annodex_header(this, stream_num, &op); } else if (!strncmp (&op.packet[0], "AnxData", 7)) { decode_anxdata_header(this, stream_num, &op); } else if (!strncmp (&op.packet[0], "CMML", 4)) { decode_cmml_header(this, stream_num, &op); } else { xprintf(this->stream->xine, XINE_VERBOSITY_DEBUG, "demux_ogg: unknown stream type (signature >%.8s<). hex dump of bos packet follows:\n", op.packet); if(this->stream->xine->verbosity >= XINE_VERBOSITY_DEBUG) xine_hexdump (op.packet, op.bytes); this->si[stream_num]->buf_types = BUF_CONTROL_NOP; } } /* send preview buffer */ if (this->si[stream_num]->headers > 0 || op.packet[0] == PACKET_TYPE_COMMENT) { if (this->si[stream_num]->hide_first_header) this->si[stream_num]->hide_first_header = 0; else { lprintf ("sending preview buffer of stream type %08x\n", this->si[stream_num]->buf_types); send_ogg_buf (this, &op, stream_num, BUF_FLAG_HEADER); this->si[stream_num]->headers --; } } /* are we finished ? */ if (!ogg_page_bos(&this->og)) { int i; done = 1; for (i=0; inum_streams; i++) { if (this->si[i]->headers > 0) done = 0; llprintf(DEBUG_PREVIEWS, "%d preview buffers left to send from stream %d\n", this->si[i]->headers, i); } } } } ui_event.type = XINE_EVENT_UI_CHANNELS_CHANGED; ui_event.data_length = 0; xine_event_send(this->stream, &ui_event); /*get the streamlength*/ get_stream_length (this); } static int demux_ogg_send_chunk (demux_plugin_t *this_gen) { demux_ogg_t *this = (demux_ogg_t *) this_gen; int stream_num; int cur_serno; ogg_packet op; ogg_handle_event(this); llprintf(DEBUG_PACKETS, "send package...\n"); if (!read_ogg_packet(this)) { this->status = DEMUX_FINISHED; lprintf ("EOF\n"); return this->status; } /* now we've got one new page */ cur_serno = ogg_page_serialno (&this->og); stream_num = get_stream(this, cur_serno); if (stream_num < 0) { lprintf ("error: unknown stream, serialnumber %d\n", cur_serno); if (!ogg_page_bos(&this->og)) { lprintf ("help, stream with no beginning!\n"); } lprintf ("adding late stream with serial number %d (all content will be discarded)\n", cur_serno); if( this->num_streams == MAX_STREAMS ) { xprintf (this->stream->xine, XINE_VERBOSITY_LOG, "demux_ogg: MAX_STREAMS exceeded, aborting.\n"); this->status = DEMUX_FINISHED; return this->status; } stream_num = new_stream_info(this, cur_serno); } ogg_stream_pagein(&this->si[stream_num]->oss, &this->og); if (ogg_page_bos(&this->og)) { lprintf ("beginning of stream: serial number %d - discard\n", ogg_page_serialno (&this->og)); while (ogg_stream_packetout(&this->si[stream_num]->oss, &op) == 1) ; return this->status; } /*while keyframeseeking only process videostream*/ if (!this->ignore_keyframes && this->keyframe_needed && ((this->si[stream_num]->buf_types & 0xFF000000) != BUF_VIDEO_BASE)) return this->status; while (ogg_stream_packetout(&this->si[stream_num]->oss, &op) == 1) { /* printf("demux_ogg: packet: %.8s\n", op.packet); */ /* printf("demux_ogg: got a packet\n"); */ if ((*op.packet & PACKET_TYPE_HEADER) && (this->si[stream_num]->buf_types!=BUF_VIDEO_THEORA) && (this->si[stream_num]->buf_types!=BUF_AUDIO_SPEEX)) { if (op.granulepos != -1) { this->si[stream_num]->header_granulepos = op.granulepos; lprintf ("header with granulepos, remembering granulepos\n"); } else { lprintf ("header => discard\n"); } continue; } /*discard granulepos-less packets and to early audiopackets*/ if (this->si[stream_num]->resync) { if ((this->si[stream_num]->buf_types & 0xFF000000) == BUF_SPU_BASE) { /*never drop subtitles*/ this->si[stream_num]->resync=0; } else if ((op.granulepos == -1) && (this->si[stream_num]->header_granulepos == -1)) { continue; } else { /*dump too early packets*/ if ((get_pts(this,stream_num,op.granulepos)-this->start_pts) > -90000) this->si[stream_num]->resync=0; else continue; } } if (!this->ignore_keyframes && this->keyframe_needed) { lprintf ("keyframe needed... buf_type=%08x\n", this->si[stream_num]->buf_types); if (this->si[stream_num]->buf_types == BUF_VIDEO_THEORA) { #ifdef HAVE_THEORA int keyframe_granule_shift; int64_t pframe=-1,iframe=-1; keyframe_granule_shift=intlog(this->t_info.keyframe_frequency_force-1); if(op.granulepos>=0){ iframe=op.granulepos>>keyframe_granule_shift; pframe=op.granulepos-(iframe<stream->xine, XINE_VERBOSITY_DEBUG, "seeking keyframe i %" PRId64 " p %" PRId64 "\n", iframe, pframe); if (pframe!=0) continue; } else continue; this->keyframe_needed = 0; this->start_pts=get_pts(this,stream_num,op.granulepos); #endif } else if ((this->si[stream_num]->buf_types & 0xFF000000) == BUF_VIDEO_BASE) { /*calculate the current pts*/ if (op.granulepos!=-1) { this->start_pts=get_pts(this, stream_num, op.granulepos); } else if (this->start_pts!=-1) this->start_pts=this->start_pts+this->frame_duration; /*seek the keyframe*/ if ((*op.packet == PACKET_IS_SYNCPOINT) && (this->start_pts!=-1)) this->keyframe_needed = 0; else continue; } else if ((this->si[stream_num]->buf_types & 0xFF000000) == BUF_VIDEO_BASE) continue; } send_ogg_buf (this, &op, stream_num, 0); /*delete used header_granulepos*/ if (op.granulepos == -1) this->si[stream_num]->header_granulepos = -1; } if (ogg_page_eos(&this->og)) { int i; int finished_streams = 0; lprintf("end of stream, serialnumber %d\n", cur_serno); this->si[stream_num]->delivered_eos = 1; /* check if all logical streams are finished */ for (i = 0; i < this->num_streams; i++) { finished_streams += this->si[i]->delivered_eos; } /* if all streams are finished, perhaps a chained stream follows */ if (finished_streams == this->num_streams) { /* delete current logical streams */ for (i = 0; i < this->num_streams; i++) { ogg_stream_clear(&this->si[i]->oss); if (this->si[i]->language) { free (this->si[i]->language); } free (this->si[i]); } this->num_streams = 0; this->num_audio_streams = 0; this->num_video_streams = 0; this->num_spu_streams = 0; this->avg_bitrate = 1; /* try to read a chained stream */ this->send_newpts = 1; this->last_pts[0] = 0; this->last_pts[1] = 0; /* send control buffer to avoid buffer leak */ _x_demux_control_end(this->stream, 0); _x_demux_control_start(this->stream); send_header(this); } } return this->status; } static void demux_ogg_dispose (demux_plugin_t *this_gen) { demux_ogg_t *this = (demux_ogg_t *) this_gen; int i; for (i=0; inum_streams; i++) { ogg_stream_clear(&this->si[i]->oss); if (this->si[i]->language) { free (this->si[i]->language); } free(this->si[i]); } ogg_sync_clear(&this->oy); #ifdef HAVE_THEORA theora_comment_clear (&this->t_comment); theora_info_clear (&this->t_info); #endif if (this->chapter_info){ free (this->chapter_info->entries); free (this->chapter_info); } if (this->title){ free (this->title); } if (this->event_queue) xine_event_dispose_queue (this->event_queue); free (this); } static int demux_ogg_get_status (demux_plugin_t *this_gen) { demux_ogg_t *this = (demux_ogg_t *) this_gen; return this->status; } static void demux_ogg_send_headers (demux_plugin_t *this_gen) { demux_ogg_t *this = (demux_ogg_t *) this_gen; this->video_fifo = this->stream->video_fifo; this->audio_fifo = this->stream->audio_fifo; this->status = DEMUX_OK; /* * send start buffers */ this->last_pts[0] = 0; this->last_pts[1] = 0; /* * initialize ogg engine */ ogg_sync_init(&this->oy); this->num_streams = 0; this->num_audio_streams = 0; this->num_video_streams = 0; this->num_spu_streams = 0; this->avg_bitrate = 1; this->input->seek (this->input, 0, SEEK_SET); if (this->status == DEMUX_OK) { _x_demux_control_start(this->stream); send_header (this); lprintf ("headers sent, avg bitrate is %" PRId64 "\n", this->avg_bitrate); } _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_VIDEO, this->num_video_streams > 0); _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_AUDIO, this->num_audio_streams > 0); _x_stream_info_set(this->stream, XINE_STREAM_INFO_MAX_SPU_CHANNEL, this->num_spu_streams); } static int demux_ogg_seek (demux_plugin_t *this_gen, off_t start_pos, int start_time, int playing) { demux_ogg_t *this = (demux_ogg_t *) this_gen; int i; start_time /= 1000; start_pos = (off_t) ( (double) start_pos / 65535 * this->input->get_length (this->input) ); /* * seek to start position */ if (INPUT_IS_SEEKABLE(this->input)) { this->keyframe_needed = (this->num_video_streams>0); if ( (!start_pos) && (start_time)) { if (this->time_length != -1) { /*do the seek via time*/ int current_time=-1; off_t current_pos; current_pos=this->input->get_current_pos(this->input); /*try to find out the current time*/ if (this->last_pts[PTS_VIDEO]) { current_time=this->last_pts[PTS_VIDEO]/90000; } else if (this->last_pts[PTS_AUDIO]) { current_time=this->last_pts[PTS_AUDIO]/90000; } /*fixme, the file could grow, do something about this->time_length using get_lenght to verify, that the stream hasn` changed its length, otherwise no seek to "new" data is possible*/ lprintf ("seek to time %d called\n",start_time); lprintf ("current time is %d\n",current_time); if (current_time > start_time) { /*seek between beginning and current_pos*/ /*fixme - sometimes we seek backwards and during keyframeseeking, we undo the seek*/ start_pos = start_time * current_pos / current_time ; } else { /*seek between current_pos and end*/ start_pos = current_pos + ((start_time - current_time) * ( this->input->get_length(this->input) - current_pos ) / ( (this->time_length / 1000) - current_time) ); } lprintf ("current_pos is %" PRId64 "\n",current_pos); lprintf ("new_pos is %" PRId64 "\n",start_pos); } else { /*seek using avg_bitrate*/ start_pos = start_time * this->avg_bitrate/8; } lprintf ("seeking to %d seconds => %" PRId64 " bytes\n", start_time, start_pos); } ogg_sync_reset(&this->oy); for (i=0; inum_streams; i++) { this->si[i]->header_granulepos = -1; ogg_stream_reset(&this->si[i]->oss); } /*some strange streams have no syncpoint flag set at the beginning*/ if (start_pos == 0) this->keyframe_needed = 0; lprintf ("seek to %" PRId64 " called\n",start_pos); this->input->seek (this->input, start_pos, SEEK_SET); } /* fixme - this would be a nice position to do the following tasks 1. adjust an ogg videostream to a keyframe 2. compare the keyframe_pts with start_time. if the difference is to high (e.g. larger than max keyframe_intervall, do a new seek or continue reading 3. adjust the audiostreams in such a way, that the difference is not to high. In short words, do all the cleanups necessary to continue playback without further actions */ this->send_newpts = 1; this->status = DEMUX_OK; if( !playing ) { this->buf_flag_seek = 0; } else { if (start_pos!=0) { this->buf_flag_seek = 1; /*each stream has to continue with a packet that has an granulepos*/ for (i=0; inum_streams; i++) { this->si[i]->resync = 1; } this->start_pts=-1; } _x_demux_flush_engine(this->stream); } return this->status; } static int demux_ogg_get_stream_length (demux_plugin_t *this_gen) { demux_ogg_t *this = (demux_ogg_t *) this_gen; if (this->time_length==-1){ if (this->avg_bitrate) { return (int)((int64_t)1000 * this->input->get_length (this->input) * 8 / this->avg_bitrate); } else { return 0; } } else { return this->time_length; } } static uint32_t demux_ogg_get_capabilities(demux_plugin_t *this_gen) { demux_ogg_t *this = (demux_ogg_t *) this_gen; int cap_chapter = 0; if (this->chapter_info) cap_chapter = DEMUX_CAP_CHAPTERS; return DEMUX_CAP_SPULANG | DEMUX_CAP_AUDIOLANG | cap_chapter; } static int format_lang_string (demux_ogg_t * this, uint32_t buf_mask, uint32_t buf_type, int channel, char *str) { int stream_num; for (stream_num=0; stream_numnum_streams; stream_num++) { if ((this->si[stream_num]->buf_types & buf_mask) == buf_type) { if (this->si[stream_num]->language) { strncpy (str, this->si[stream_num]->language, XINE_LANG_MAX); str[XINE_LANG_MAX - 1] = '\0'; if (strlen(this->si[stream_num]->language) >= XINE_LANG_MAX) /* the string got truncated */ str[XINE_LANG_MAX - 2] = str[XINE_LANG_MAX - 3] = str[XINE_LANG_MAX - 4] = '.'; /* TODO: provide long version in XINE_META_INFO_FULL_LANG */ } else { snprintf(str, XINE_LANG_MAX, "channel %d",channel); } return DEMUX_OPTIONAL_SUCCESS; } } return DEMUX_OPTIONAL_UNSUPPORTED; } static int demux_ogg_get_optional_data(demux_plugin_t *this_gen, void *data, int data_type) { demux_ogg_t *this = (demux_ogg_t *) this_gen; char *str=(char *) data; int channel = *((int *)data); switch (data_type) { case DEMUX_OPTIONAL_DATA_SPULANG: lprintf ("DEMUX_OPTIONAL_DATA_SPULANG channel = %d\n",channel); if (channel==-1) { strcpy( str, "none"); return DEMUX_OPTIONAL_SUCCESS; } else if ((channel>=0) && (channelnum_streams)) { return format_lang_string (this, 0xFFFFFFFF, BUF_SPU_OGM+channel, channel, str); } return DEMUX_OPTIONAL_UNSUPPORTED; case DEMUX_OPTIONAL_DATA_AUDIOLANG: lprintf ("DEMUX_OPTIONAL_DATA_AUDIOLANG channel = %d\n",channel); if (channel==-1) { return format_lang_string (this, 0xFF00001F, BUF_AUDIO_BASE, channel, str); } else if ((channel>=0) && (channelnum_streams)) { return format_lang_string (this, 0xFF00001F, BUF_AUDIO_BASE+channel, channel, str); } return DEMUX_OPTIONAL_UNSUPPORTED; default: return DEMUX_OPTIONAL_UNSUPPORTED; } } static int detect_ogg_content (int detection_method, demux_class_t *class_gen, input_plugin_t *input) { switch (detection_method) { case METHOD_BY_CONTENT: { uint8_t buf[4]; if (_x_demux_read_header(input, buf, 4) != 4) return 0; if ((buf[0] == 'O') && (buf[1] == 'g') && (buf[2] == 'g') && (buf[3] == 'S')) return 1; else return 0; } case METHOD_BY_EXTENSION: { char *extensions, *mrl; mrl = input->get_mrl (input); extensions = class_gen->get_extensions (class_gen); if (_x_demux_check_extension (mrl, extensions)) return 1; else return 0; } case METHOD_EXPLICIT: return 1; default: return 0; } } static int detect_anx_content (int detection_method, demux_class_t *class_gen, input_plugin_t *input) { if (detect_ogg_content(detection_method, class_gen, input) == 0) return 0; switch (detection_method) { #define ANNODEX_SIGNATURE_SEARCH 128 case METHOD_BY_CONTENT: { uint8_t buf[ANNODEX_SIGNATURE_SEARCH]; int found_annodex_signature = 0; const char *annodex_signature = "Annodex"; int annodex_signature_length = 7; /* = strlen(annodex_signature) */ int i, j; if (_x_demux_read_header(input, buf, ANNODEX_SIGNATURE_SEARCH) != ANNODEX_SIGNATURE_SEARCH) return 0; /* scan for 'Annodex' signature in the first 64 bytes */ for (i = 0, j = 0; i < ANNODEX_SIGNATURE_SEARCH; i++) { if (buf[i] == annodex_signature[j]) { if (j >= annodex_signature_length) { /* found signature */ found_annodex_signature = 1; break; } else { j++; } } } if (found_annodex_signature) return 1; else return 0; } #undef ANNODEX_SIGNATURE_SEARCH case METHOD_BY_EXTENSION: { char *extensions, *mrl; mrl = input->get_mrl (input); extensions = class_gen->get_extensions (class_gen); if (_x_demux_check_extension (mrl, extensions)) return 1; else return 0; } case METHOD_EXPLICIT: return 1; default: return 0; } } static demux_plugin_t *anx_open_plugin (demux_class_t *class_gen, xine_stream_t *stream, input_plugin_t *input) { demux_ogg_t *this; if (detect_anx_content(stream->content_detection_method, class_gen, input) == 0) return NULL; /* * if we reach this point, the input has been accepted. */ this = xine_xmalloc (sizeof (demux_ogg_t)); memset (this, 0, sizeof(demux_ogg_t)); this->stream = stream; this->input = input; /* the Annodex demuxer currently calls into exactly the same functions as * the Ogg demuxer, which seems to make this function a bit redundant, but * this design leaves us a bit more room to change an Annodex demuxer's * behaviour in the future if necessary */ this->demux_plugin.send_headers = demux_ogg_send_headers; this->demux_plugin.send_chunk = demux_ogg_send_chunk; this->demux_plugin.seek = demux_ogg_seek; this->demux_plugin.dispose = demux_ogg_dispose; this->demux_plugin.get_status = demux_ogg_get_status; this->demux_plugin.get_stream_length = demux_ogg_get_stream_length; this->demux_plugin.get_capabilities = demux_ogg_get_capabilities; this->demux_plugin.get_optional_data = demux_ogg_get_optional_data; this->demux_plugin.demux_class = class_gen; this->status = DEMUX_FINISHED; #ifdef HAVE_THEORA theora_info_init (&this->t_info); theora_comment_init (&this->t_comment); #endif this->chapter_info = 0; this->title = 0; this->event_queue = xine_event_new_queue (this->stream); return &this->demux_plugin; } static demux_plugin_t *ogg_open_plugin (demux_class_t *class_gen, xine_stream_t *stream, input_plugin_t *input) { demux_ogg_t *this; if (detect_ogg_content(stream->content_detection_method, class_gen, input) == 0) return NULL; /* * if we reach this point, the input has been accepted. */ this = xine_xmalloc (sizeof (demux_ogg_t)); memset (this, 0, sizeof(demux_ogg_t)); this->stream = stream; this->input = input; this->demux_plugin.send_headers = demux_ogg_send_headers; this->demux_plugin.send_chunk = demux_ogg_send_chunk; this->demux_plugin.seek = demux_ogg_seek; this->demux_plugin.dispose = demux_ogg_dispose; this->demux_plugin.get_status = demux_ogg_get_status; this->demux_plugin.get_stream_length = demux_ogg_get_stream_length; this->demux_plugin.get_capabilities = demux_ogg_get_capabilities; this->demux_plugin.get_optional_data = demux_ogg_get_optional_data; this->demux_plugin.demux_class = class_gen; this->status = DEMUX_FINISHED; #ifdef HAVE_THEORA theora_info_init (&this->t_info); theora_comment_init (&this->t_comment); #endif this->chapter_info = 0; this->title = 0; this->event_queue = xine_event_new_queue (this->stream); return &this->demux_plugin; } /* * Annodex demuxer class */ static char *anx_get_description (demux_class_t *this_gen) { return "Annodex demux plugin"; } static char *anx_get_identifier (demux_class_t *this_gen) { return "Annodex"; } static char *anx_get_extensions (demux_class_t *this_gen) { return "anx axa axv"; } static char *anx_get_mimetypes (demux_class_t *this_gen) { return "application/x-annodex: ogg: Annodex media;"; } static void anx_class_dispose (demux_class_t *this_gen) { demux_anx_class_t *this = (demux_anx_class_t *) this_gen; free (this); } static void *anx_init_class (xine_t *xine, void *data) { demux_anx_class_t *this; this = xine_xmalloc (sizeof (demux_anx_class_t)); this->demux_class.open_plugin = anx_open_plugin; this->demux_class.get_description = anx_get_description; this->demux_class.get_identifier = anx_get_identifier; this->demux_class.get_mimetypes = anx_get_mimetypes; this->demux_class.get_extensions = anx_get_extensions; this->demux_class.dispose = anx_class_dispose; return this; } /* * ogg demuxer class */ static char *ogg_get_description (demux_class_t *this_gen) { return "OGG demux plugin"; } static char *ogg_get_identifier (demux_class_t *this_gen) { return "OGG"; } static char *ogg_get_extensions (demux_class_t *this_gen) { return "ogg ogm spx"; } static char *ogg_get_mimetypes (demux_class_t *this_gen) { return "audio/x-ogg: ogg: OggVorbis Audio;" "audio/x-speex: ogg: Speex Audio;" "application/x-ogg: ogg: OggVorbis Audio;"; } static void ogg_class_dispose (demux_class_t *this_gen) { demux_ogg_class_t *this = (demux_ogg_class_t *) this_gen; free (this); } static void *ogg_init_class (xine_t *xine, void *data) { demux_ogg_class_t *this; this = xine_xmalloc (sizeof (demux_ogg_class_t)); this->demux_class.open_plugin = ogg_open_plugin; this->demux_class.get_description = ogg_get_description; this->demux_class.get_identifier = ogg_get_identifier; this->demux_class.get_mimetypes = ogg_get_mimetypes; this->demux_class.get_extensions = ogg_get_extensions; this->demux_class.dispose = ogg_class_dispose; return this; } /* * exported plugin catalog entry */ demuxer_info_t demux_info_anx = { 20 /* priority */ }; demuxer_info_t demux_info_ogg = { 10 /* priority */ }; plugin_info_t xine_plugin_info[] = { /* type, API, "name", version, special_info, init_function */ { PLUGIN_DEMUX, 25, "ogg", XINE_VERSION_CODE, &demux_info_ogg, ogg_init_class }, { PLUGIN_DEMUX, 25, "anx", XINE_VERSION_CODE, &demux_info_anx, anx_init_class }, { PLUGIN_NONE, 0, "", 0, NULL, NULL } };