/* * Copyright (C) 2000-2007 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_mpgaudio.c,v 1.156 2007/03/29 16:32:12 dgp85 Exp $ * * demultiplexer for mpeg audio (i.e. mp3) streams * * mp3 file structure: * [id3v2][Xing|Vbri] Frame1 Frame2 Frame3...FrameX [Lyrics][id3v2][id3v1] * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #define LOG_MODULE "demux_mpeg_audio" #define LOG_VERBOSE /* #define LOG */ #include "xine_internal.h" #include "xineutils.h" #include "demux.h" #include "compat.h" #include "bswap.h" #include "group_audio.h" #include "id3.h" /* 2 preview buffers are enough, * the first mp3 frame is potentially a vbr header (xing, vbri) * the second mp3 frame is sent to the decoder */ #define NUM_PREVIEW_BUFFERS 2 #define WRAP_THRESHOLD 120000 #define FOURCC_TAG BE_FOURCC #define RIFF_CHECK_BYTES 1024 #define RIFF_TAG FOURCC_TAG('R', 'I', 'F', 'F') #define AVI_TAG FOURCC_TAG('A', 'V', 'I', ' ') #define CDXA_TAG FOURCC_TAG('C', 'D', 'X', 'A') #define MPEG_MARKER FOURCC_TAG( 0x00, 0x00, 0x01, 0xBA ) /* Xing header stuff */ #define XING_TAG FOURCC_TAG('X', 'i', 'n', 'g') #define XING_FRAMES_FLAG 0x0001 #define XING_BYTES_FLAG 0x0002 #define XING_TOC_FLAG 0x0004 #define XING_VBR_SCALE_FLAG 0x0008 #define XING_TOC_LENGTH 100 /* Xing header stuff */ #define VBRI_TAG FOURCC_TAG('V', 'B', 'R', 'I') /* mp3 frame struct */ typedef struct { /* header */ double duration; uint32_t size; /* in bytes */ uint32_t bitrate; /* in bit per second */ uint16_t freq; /* in Hz */ uint8_t layer; uint8_t version_idx:2; /* 0: mpeg1, 1: mpeg2, 2: mpeg2.5 */ uint8_t lsf_bit:1; uint8_t channel_mode:3; } mpg_audio_frame_t; /* Xing Vbr Header struct */ typedef struct { uint32_t flags; uint32_t stream_frames; uint32_t stream_size; uint8_t toc[XING_TOC_LENGTH]; uint32_t vbr_scale; } xing_header_t; /* Vbri Vbr Header struct */ typedef struct { uint16_t version; uint16_t delai; uint16_t quality; uint32_t stream_size; uint32_t stream_frames; uint16_t toc_entries; uint16_t toc_scale_factor; uint16_t entry_size; uint16_t entry_frames; int *toc; } vbri_header_t; /* demuxer instance struct */ typedef struct { demux_plugin_t demux_plugin; xine_stream_t *stream; fifo_buffer_t *audio_fifo; input_plugin_t *input; int status; uint32_t stream_length; /* in seconds */ int br; /* bitrate in bits/second */ uint32_t blocksize; mpg_audio_frame_t cur_frame; double cur_time; /* in milliseconds */ off_t mpg_frame_start; /* offset */ off_t mpg_frame_end; /* offset */ off_t mpg_size; /* in bytes */ int check_vbr_header; xing_header_t *xing_header; vbri_header_t *vbri_header; } demux_mpgaudio_t ; /* demuxer class struct */ typedef struct { demux_class_t demux_class; /* class-wide, global variables here */ xine_t *xine; } demux_mpgaudio_class_t; /* * Parse a mp3 frame * return 1 on success */ static int parse_frame_header(mpg_audio_frame_t *const frame, const uint8_t *const buf) { /* bitrate table[mpeg version][layer][bitrate index] * values stored in kbps */ static const uint16_t mp3_bitrates[3][3][16] = { { {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448,}, {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384,}, {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,} }, { {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256,}, {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,}, {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,} }, { {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256,}, {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,}, {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,} } }; /* frequency table[mpeg version][frequence index] (in KHz) */ static const uint16_t mp3_freqs[3][3] = { { 44100, 48000, 32000 }, { 22050, 24000, 16000 }, { 11025, 12000, 8000 } }; /* samples per frame table[mpeg version][layer] */ static const uint16_t mp3_samples[3][3] = { { 384, 1152, 1152 }, { 384, 1152, 576 }, { 384, 1152, 576 } }; struct { uint16_t mpeg25_bit:1; uint16_t bitrate_idx:4; uint16_t freq_idx:3; uint16_t padding_bit:1; uint16_t channel_mode:3; #if 0 /* Unused */ uint16_t protection_bit:1; uint16_t private_bit:1; uint16_t mode_extension:3; uint16_t copyright:1; uint16_t original:1; #endif #if defined(OPT_STRICT) uint16_t emphasis:3; #endif } frame_header; const uint32_t head = BE_32(buf); const uint16_t frame_sync = head >> 21; lprintf("header: %08X\n", head); if (frame_sync != 0x7ff) { lprintf("invalid frame sync\n"); return 0; } frame_header.mpeg25_bit = (head >> 20) & 0x1; frame->lsf_bit = (head >> 19) & 0x1; if (!frame_header.mpeg25_bit) { if (frame->lsf_bit) { lprintf("reserved mpeg25 lsf combination\n"); return 0; } else frame->version_idx = 2; /* MPEG Version 2.5 */ } else { if (!frame->lsf_bit) frame->version_idx = 1; /* MPEG Version 2 */ else frame->version_idx = 0; /* MPEG Version 1 */ } frame->layer = 4 - ((head >> 17) & 0x3); if (frame->layer == 4) { lprintf("reserved layer\n"); return 0; } frame_header.bitrate_idx = (head >> 12) & 0xf; if ((frame_header.bitrate_idx == 0) || (frame_header.bitrate_idx == 15)) { lprintf("invalid bitrate index\n"); return 0; } frame_header.freq_idx = (head >> 10) & 0x3; if (frame_header.freq_idx == 3) { lprintf("invalid frequence index\n"); return 0; } frame_header.padding_bit = (head >> 9) & 0x1; frame_header.channel_mode = (head >> 6) & 0x3; #if 0 /* Unused */ frame_header.protection_bit = (head >> 16) & 0x1; frame_header.private_bit = (head >> 8) & 0x1; frame_header.mode_extension = (head >> 4) & 0x3; frame_header.copyright = (head >> 3) & 0x1; frame_header.original = (head >> 2) & 0x1; #endif #if defined(OPT_STRICT) frame_header.emphasis = head & 0x3; /* * ISO/IEC 11172-3 says this is a reserved emphasis value, but * streams exist which use it anyway. Since the value is not important * to the decoder proper, we allow it unless OPT_STRICT is defined. */ if (frame_header.emphasis == 2) { lprintf("reserved emphasis\n"); return 0; } #endif { const uint16_t samples = mp3_samples[frame->version_idx][frame->layer - 1]; frame->bitrate = mp3_bitrates[frame->version_idx][frame->layer - 1][frame_header.bitrate_idx] * 1000; frame->freq = mp3_freqs[frame->version_idx][frame_header.freq_idx]; frame->size = samples * (frame->bitrate / 8); frame->size /= frame->freq; /* Padding: only if padding_bit is set; 4 bytes for Layer 1 and 1 byte for others */ frame->size += ( frame_header.padding_bit ? ( frame->layer == 1 ? 4 : 1 ) : 0 ); frame->duration = 1000.0f * (double)samples / (double)frame->freq; } lprintf("mpeg %d, layer %d\n", frame->version_idx + 1, frame->layer); lprintf("bitrate: %d bps, samplerate: %d Hz\n", frame->bitrate, frame->freq); lprintf("length: %d bytes, %f ms\n", frame->size, frame->duration); lprintf("padding: %d bytes\n", ( frame_header.padding_bit ? ( frame->layer == 1 ? 4 : 1 ) : 0 )); return 1; } /* * Parse a Xing header * return the Xing header or NULL on error */ static xing_header_t* parse_xing_header(mpg_audio_frame_t *frame, uint8_t *buf, int bufsize) { #ifdef LOG int i; #endif uint8_t *ptr = buf; xing_header_t *xing; xing = xine_xmalloc (sizeof (xing_header_t)); if (!xing) return NULL; /* offset of the Xing header */ if (frame->lsf_bit) { if (frame->channel_mode != 3) ptr += (32 + 4); else ptr += (17 + 4); } else { if (frame->channel_mode != 3) ptr += (17 + 4); else ptr += (9 + 4); } if (ptr >= (buf + bufsize - 4)) return 0; lprintf("checking %08X\n", *ptr); if (BE_32(ptr) == XING_TAG) { lprintf("Xing header found\n"); ptr += 4; if (ptr >= (buf + bufsize - 4)) return 0; xing->flags = BE_32(ptr); ptr += 4; if (xing->flags & XING_FRAMES_FLAG) { if (ptr >= (buf + bufsize - 4)) return 0; xing->stream_frames = BE_32(ptr); ptr += 4; lprintf("stream frames: %d\n", xing->stream_frames); } if (xing->flags & XING_BYTES_FLAG) { if (ptr >= (buf + bufsize - 4)) return 0; xing->stream_size = BE_32(ptr); ptr += 4; lprintf("stream size: %d\n", xing->stream_size); } if (xing->flags & XING_TOC_FLAG) { lprintf("toc found\n"); if (ptr >= (buf + bufsize - XING_TOC_LENGTH)) return 0; memcpy(xing->toc, ptr, XING_TOC_LENGTH); #ifdef LOG for (i = 0; i < XING_TOC_LENGTH; i++) { lprintf("%d ", xing->toc[i]); } lprintf("\n"); #endif ptr += XING_TOC_LENGTH; } xing->vbr_scale = -1; if (xing->flags & XING_VBR_SCALE_FLAG) { if (ptr >= (buf + bufsize - 4)) return 0; xing->vbr_scale = BE_32(ptr); lprintf("vbr_scale: %d\n", xing->vbr_scale); } return xing; } else { lprintf("Xing header not found\n"); free(xing); return NULL; } } /* * Parse a Vbri header * return the Vbri header or NULL on error */ static vbri_header_t* parse_vbri_header(mpg_audio_frame_t *frame, uint8_t *buf, int bufsize) { int i; uint8_t *ptr = buf; vbri_header_t *vbri; vbri = xine_xmalloc (sizeof (vbri_header_t)); if (!vbri) return NULL; ptr += (32 + 4); if ((ptr + 4) >= (buf + bufsize)) return 0; lprintf("Checking %08X\n", *ptr); if (BE_32(ptr) == VBRI_TAG) { lprintf("Vbri header found\n"); ptr += 4; if ((ptr + 22) >= (buf + bufsize)) return 0; vbri->version = BE_16(ptr); ptr += 2; vbri->delai = BE_16(ptr); ptr += 2; vbri->quality = BE_16(ptr); ptr += 2; vbri->stream_size = BE_32(ptr); ptr += 4; vbri->stream_frames = BE_32(ptr); ptr += 4; vbri->toc_entries = BE_16(ptr); ptr += 2; vbri->toc_scale_factor = BE_16(ptr); ptr += 2; vbri->entry_size = BE_16(ptr); ptr += 2; vbri->entry_frames = BE_16(ptr); ptr += 2; lprintf("version: %d\n", vbri->version); lprintf("delai: %d\n", vbri->delai); lprintf("quality: %d\n", vbri->quality); lprintf("stream_size: %d\n", vbri->stream_size); lprintf("stream_frames: %d\n", vbri->stream_frames); lprintf("toc_entries: %d\n", vbri->toc_entries); lprintf("toc_scale_factor: %d\n", vbri->toc_scale_factor); lprintf("entry_size: %d\n", vbri->entry_size); lprintf("entry_frames: %d\n", vbri->entry_frames); if ((ptr + (vbri->toc_entries + 1) * vbri->entry_size) >= (buf + bufsize)) return 0; vbri->toc = xine_xmalloc (sizeof(int) * (vbri->toc_entries + 1)); if (!vbri->toc) { free (vbri); return NULL; } lprintf("toc entries: %d\n", vbri->toc_entries); for (i = 0; i <= vbri->toc_entries; i++) { int j; uint32_t value = 0; for (j = 0; j < vbri->entry_size; j++) { value = (value << 8) | *(ptr + i * vbri->entry_size + j); } vbri->toc[i] = value; #ifdef LOG printf("%d ", vbri->toc[i]); #endif } #ifdef LOG printf("\n"); #endif { int64_t toc_stream_size = 0; /* Compute the stream size using the toc */ for (i = 0; i <= vbri->toc_entries; i++) { toc_stream_size += vbri->toc[i]; } lprintf("stream size from toc: %"PRId64"\n", toc_stream_size); } return vbri; } else { lprintf("Vbri header not found\n"); free (vbri); return NULL; } } /* * Parse a mp3 frame paylod * return 1 on success, 0 on error */ static int parse_frame_payload(demux_mpgaudio_t *this, uint8_t *frame_header, int decoder_flags) { buf_element_t *buf; off_t frame_pos, len; uint64_t pts = 0; frame_pos = this->input->get_current_pos(this->input) - 4; lprintf("frame_pos = %"PRId64"\n", frame_pos); buf = this->audio_fifo->buffer_pool_alloc(this->audio_fifo); if (this->cur_frame.size > buf->max_size) { xprintf(this->stream->xine, XINE_VERBOSITY_LOG, "demux_mpgaudio: frame size is greater than fifo buffer size\n"); buf->free_buffer(buf); return 0; } /* the decoder needs the frame header */ memcpy(buf->mem, frame_header, 4); len = this->input->read(this->input, buf->mem + 4, this->cur_frame.size - 4); if (len != (this->cur_frame.size - 4)) { buf->free_buffer(buf); return 0; } if (this->check_vbr_header) { this->check_vbr_header = 0; this->mpg_frame_start = frame_pos; this->xing_header = parse_xing_header(&this->cur_frame, buf->mem, this->cur_frame.size); if (this->xing_header) { buf->free_buffer(buf); return 1; } this->vbri_header = parse_vbri_header(&this->cur_frame, buf->mem, this->cur_frame.size); if (this->vbri_header) { buf->free_buffer(buf); return 1; } } pts = (int64_t)(this->cur_time * 90.0f); if (this->stream_length) buf->extra_info->input_normpos = (this->cur_time * 65535.0f) / this->stream_length; buf->extra_info->input_time = this->cur_time; buf->pts = pts; buf->size = len + 4; buf->content = buf->mem; buf->type = BUF_AUDIO_MPEG; buf->decoder_info[0] = 1; buf->decoder_flags = decoder_flags|BUF_FLAG_FRAME_END; this->audio_fifo->put(this->audio_fifo, buf); lprintf("send buffer: pts=%"PRId64"\n", pts); this->cur_time += this->cur_frame.duration; return 1; } /* Scan through the preview buffer to find a potential * 32-bit MP3 frame header. * return 1 if found, 0 if not found */ static int sniff_buffer_looks_like_mp3 (uint8_t *buf, int buflen) { int offset; mpg_audio_frame_t frame; if (buf == NULL) return 0; for (offset = 0; (offset + 4) < buflen; offset++) { if (parse_frame_header(&frame, buf + offset)) { size_t size = frame.size; /* Since one frame is available, is there another frame * just to be sure this is more likely to be a real MP3 * buffer? */ offset += size; if (offset + 4 >= buflen) { return 0; } if (parse_frame_header(&frame, buf + offset)) { lprintf("mpeg audio frame detected\n"); return 1; } break; } } return 0; } /* * Read a mp3 frame header (4 bytes) */ static int read_frame_header(demux_mpgaudio_t *this, uint8_t *header_buf, int bytes) { off_t len; int i; for (i = 0; i < (4 - bytes); i++) { header_buf[i] = header_buf[i + bytes]; } len = this->input->read(this->input, header_buf + 4 - bytes, bytes); if (len != ((off_t) bytes)) { return 0; } return 1; } /* * Parse next mp3 frame */ static int demux_mpgaudio_next (demux_mpgaudio_t *this, int decoder_flags, int send_header) { uint8_t header_buf[4]; int bytes = 4; for (;;) { if (read_frame_header(this, header_buf, bytes)) { if (parse_frame_header(&this->cur_frame, header_buf)) { /* send header buffer */ if ( send_header ) { buf_element_t *buf; buf = this->audio_fifo->buffer_pool_alloc(this->audio_fifo); buf->type = BUF_AUDIO_MPEG; buf->decoder_flags = BUF_FLAG_HEADER|BUF_FLAG_STDHEADER|BUF_FLAG_FRAME_END; buf->decoder_info[0] = 0; buf->decoder_info[1] = this->cur_frame.freq; buf->decoder_info[2] = 0; /* bits_per_sample */ /* Only for channel_mode == 3 (mono) there is one channel, for any other case, there are 2 */ buf->decoder_info[3] = ( this->cur_frame.channel_mode == 3 ) ? 1 : 2; buf->size = 0; /* No extra header data */ this->audio_fifo->put(this->audio_fifo, buf); } return parse_frame_payload(this, header_buf, decoder_flags); } else if ( id3v2_istag(header_buf) ) { if (!id3v2_parse_tag(this->input, this->stream, header_buf)) { xprintf(this->stream->xine, XINE_VERBOSITY_LOG, LOG_MODULE ": ID3V2 tag parsing error\n"); bytes = 1; /* resync */ } else { bytes = 4; } } else { /* skip */ bytes = 1; } } else { lprintf("read error\n"); return 0; } } } static int demux_mpgaudio_send_chunk (demux_plugin_t *this_gen) { demux_mpgaudio_t *this = (demux_mpgaudio_t *) this_gen; if (!demux_mpgaudio_next (this, 0, 0)) this->status = DEMUX_FINISHED; return this->status; } static int demux_mpgaudio_get_status (demux_plugin_t *this_gen) { demux_mpgaudio_t *this = (demux_mpgaudio_t *) this_gen; return this->status; } static int demux_mpgaudio_read_head(input_plugin_t *input, uint8_t *buf) { int bs = 0; if (INPUT_IS_SEEKABLE(input)) { input->seek(input, 0, SEEK_SET); bs = input->read(input, buf, MAX_PREVIEW_SIZE); lprintf("stream is seekable\n"); } else if ((input->get_capabilities(input) & INPUT_CAP_PREVIEW) != 0) { lprintf("input plugin provides preview\n"); bs = input->get_optional_data (input, buf, INPUT_OPTIONAL_DATA_PREVIEW); } else { lprintf("not seekable, no preview\n"); return 0; } return bs; } /* * mp3 stream detection * return 1 if detected, 0 otherwise */ static int detect_mpgaudio_file(input_plugin_t *input) { mpg_audio_frame_t frame; uint8_t buf[MAX_PREVIEW_SIZE]; int preview_len; uint32_t head; preview_len = demux_mpgaudio_read_head(input, buf); if (preview_len < 4) return 0; lprintf("got preview %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3]); head = BE_32(buf); if ((head == ID3V22_TAG) || (head == ID3V23_TAG) || (head == ID3V24_TAG)) { /* check if a mp3 frame follows the tag * id3v2 are not specific to mp3 files, * flac files can contain id3v2 tags */ uint8_t *ptr = &buf[6]; uint32_t tag_size = id3v2_tagsize(ptr); lprintf("try to skip id3v2 tag (%d bytes)\n", tag_size); if ((10 + tag_size) >= preview_len) { lprintf("cannot skip id3v2 tag\n"); return 0; } if ((10 + tag_size + 4) >= preview_len) { lprintf("cannot read mp3 frame header\n"); return 0; } if (!parse_frame_header(&frame, &buf[10 + tag_size])) { lprintf ("invalid mp3 frame header\n"); return 0; } else { lprintf ("a valid mp3 frame follows the id3v2 tag\n"); } } else if (head == MPEG_MARKER) { return 0; } else if (!sniff_buffer_looks_like_mp3(buf, preview_len)) { lprintf ("sniff_buffer_looks_like_mp3 failed\n"); return 0; } return 1; } static void demux_mpgaudio_send_headers (demux_plugin_t *this_gen) { demux_mpgaudio_t *this = (demux_mpgaudio_t *) this_gen; int i; this->stream_length = 0; this->status = DEMUX_OK; _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_VIDEO, 0); _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_AUDIO, 1); _x_demux_control_start (this->stream); /* read id3 info only from inputs with seeking and without "live" flag */ if (INPUT_IS_SEEKABLE(this->input)) { off_t pos; /* check ID3 v1 at the end of the stream */ pos = this->input->get_length(this->input) - 128; if(pos > 0) { if (pos == this->input->seek (this->input, pos, SEEK_SET)) id3v1_parse_tag (this->input, this->stream); } /* seek back to the beginning */ this->input->seek (this->input, 0, SEEK_SET); /* * send preview buffers */ this->check_vbr_header = 1; for (i = 0; i < NUM_PREVIEW_BUFFERS; i++) { if (!demux_mpgaudio_next (this, BUF_FLAG_PREVIEW, i == 0)) { break; } } if (this->xing_header) { xing_header_t *xing = this->xing_header; this->mpg_size = xing->stream_size; this->mpg_frame_end = this->mpg_frame_start + this->mpg_size; this->stream_length = (double)xing->stream_frames * this->cur_frame.duration; /* compute abr */ if (this->stream_length) { this->br = ((uint64_t)xing->stream_size * 8 * 1000) / this->stream_length; } } else if (this->vbri_header) { vbri_header_t *vbri = this->vbri_header; this->mpg_size = vbri->stream_size; this->mpg_frame_end = this->mpg_frame_start + this->mpg_size; this->stream_length = (double)vbri->stream_frames * this->cur_frame.duration; /* compute abr */ if (this->stream_length) { this->br = ((uint64_t)vbri->stream_size * 8 * 1000) / this->stream_length; } } /* Set to default if Vbr header is incomplete or not present */ if (!this->br) { /* assume CBR */ this->br = this->cur_frame.bitrate; } if (!this->mpg_frame_end) { this->mpg_frame_end = this->input->get_length(this->input); } if (!this->mpg_size) { this->mpg_size = this->mpg_frame_end - this->mpg_frame_start; } if (!this->stream_length && this->br) { this->stream_length = (this->mpg_size * 1000) / (this->br / 8); } _x_stream_info_set(this->stream, XINE_STREAM_INFO_BITRATE, this->br); _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_BITRATE, this->br); lprintf("frame_start: %"PRId64", frame_end: %"PRId64"\n", this->mpg_frame_start, this->mpg_frame_end); lprintf("stream size: %"PRId64", mp3 size: %"PRId64"\n", this->input->get_length(this->input), this->mpg_size); lprintf("stream_length: %d ms\n", this->stream_length); /* set codec infos here * the decoder doesn't know if the stream is VBR */ { char scratch_buf[256]; char *mpeg_ver[3] = {"1", "2", "2.5"}; snprintf(scratch_buf, 256, "MPEG %s Layer %1d%s", mpeg_ver[this->cur_frame.version_idx], this->cur_frame.layer, (this->xing_header)? " VBR" : " CBR" ); _x_meta_info_set_utf8(this->stream, XINE_META_INFO_AUDIOCODEC, scratch_buf); } } else { for (i = 0; i < NUM_PREVIEW_BUFFERS; i++) { if (!demux_mpgaudio_next (this, BUF_FLAG_PREVIEW, i == 0)) { break; } } } this->status = DEMUX_OK; } /* * interpolate in Xing TOC to get file seek point in bytes * return the stream offset of the seekpoint */ static off_t xing_get_seek_point(xing_header_t *xing, int time, int stream_length) { off_t seekpoint; int a; float fa, fb, fx; float percent; percent = ((float)time * 100.0f)/ (float)stream_length; if (percent < 0.0f) percent = 0.0f; if (percent > 100.0f) percent = 100.0f; a = (int)percent; if (a > 99) a = 99; fa = xing->toc[a]; if (a < 99) { fb = xing->toc[a + 1]; } else { fb = 256.0f; } fx = fa + (fb - fa) * (percent - a); seekpoint = (off_t)((1.0f / 256.0f) * fx * xing->stream_size); return seekpoint; } /* * Interpolate in Vbri TOC to get file seek point in bytes * return the stream offset of the seekpoint */ static off_t vbri_get_seek_point(vbri_header_t *vbri, int time, int stream_length) { double fa, fb, fx; double toc_entry; int i; int a; toc_entry = ((float)time * (float)(vbri->toc_entries + 1)) / (float)stream_length; lprintf("time: %d, stream length: %d, toc entry: %f\n", time, stream_length, toc_entry); if (toc_entry < 0.0f) toc_entry = 0.0f; if (toc_entry > (float)vbri->toc_entries) toc_entry = (float)vbri->toc_entries; a = (int)toc_entry; if (a > (vbri->toc_entries - 1)) a = vbri->toc_entries - 1; /* compute the stream offset of the toc entry */ fa = 0.0f; for (i = 0; i < a; i++) { fa += (double)vbri->toc[i]; } /* compute the stream offset of the next toc entry */ fb = fa + (double)vbri->toc[a]; /* interpolate */ fx = fa + (fb - fa) * (toc_entry - (double)a); return (off_t)fx; } /* * Seeking function * Try to use the Vbr header if present. * If no Vbr header is present then use a CBR formula * * Position seek is relative to the total time of the stream, the position * is converted to a time at the beginning of the function */ static int demux_mpgaudio_seek (demux_plugin_t *this_gen, off_t start_pos, int start_time, int playing) { demux_mpgaudio_t *this = (demux_mpgaudio_t *) this_gen; off_t seek_pos = this->mpg_frame_start; if ((this->input->get_capabilities(this->input) & INPUT_CAP_SEEKABLE) != 0) { /* Convert position seek to time seek */ if (!start_time) { start_time = (int)((double)start_pos * (double)this->stream_length / 65535.0f); lprintf("position seek: start_pos=%"PRId64" => start_time=%d\n", start_pos, start_time); } if (start_time < 0) start_time = 0; if (start_time > this->stream_length) start_time = this->stream_length; if (this->stream_length > 0) { if (this->xing_header && (this->xing_header->flags & (XING_TOC_FLAG | XING_BYTES_FLAG))) { seek_pos += xing_get_seek_point(this->xing_header, start_time, this->stream_length); lprintf("time seek: xing: time=%d, pos=%"PRId64"\n", start_time, seek_pos); } else if (this->vbri_header) { seek_pos += vbri_get_seek_point(this->vbri_header, start_time, this->stream_length); lprintf("time seek: vbri: time=%d, pos=%"PRId64"\n", start_time, seek_pos); } else { /* cbr */ seek_pos += ((double)start_time / 1000.0) * ((double)this->br / 8.0); lprintf("time seek: cbr: time=%d, pos=%"PRId64"\n", start_time, seek_pos); } } /* assume seeking is always perfect... */ this->cur_time = start_time; this->input->seek (this->input, seek_pos, SEEK_SET); if (playing) { _x_demux_flush_engine(this->stream); } _x_demux_control_newpts(this->stream, (int64_t)(this->cur_time * 90.0f), (playing) ? BUF_FLAG_SEEK : 0); } this->status = DEMUX_OK; return this->status; } static void demux_mpgaudio_dispose (demux_plugin_t *this) { free (this); } static int demux_mpgaudio_get_stream_length (demux_plugin_t *this_gen) { demux_mpgaudio_t *this = (demux_mpgaudio_t *) this_gen; if (this->stream_length > 0) { return this->stream_length; } else return 0; } static uint32_t demux_mpgaudio_get_capabilities(demux_plugin_t *this_gen) { return DEMUX_CAP_NOCAP; } static int demux_mpgaudio_get_optional_data(demux_plugin_t *this_gen, void *data, int data_type) { return DEMUX_OPTIONAL_UNSUPPORTED; } static demux_plugin_t *open_plugin (demux_class_t *class_gen, xine_stream_t *stream, input_plugin_t *input) { demux_mpgaudio_t *this; lprintf("trying to open %s...\n", input->get_mrl(input)); switch (stream->content_detection_method) { case METHOD_BY_CONTENT: { if (!detect_mpgaudio_file(input)) return NULL; } break; case METHOD_BY_EXTENSION: { const char *const mrl = input->get_mrl(input); const char *const extensions = class_gen->get_extensions (class_gen); lprintf ("stage by extension %s\n", mrl); if (!_x_demux_check_extension (mrl, extensions)) return NULL; } break; case METHOD_EXPLICIT: break; default: return NULL; } this = xine_xmalloc (sizeof (demux_mpgaudio_t)); this->demux_plugin.send_headers = demux_mpgaudio_send_headers; this->demux_plugin.send_chunk = demux_mpgaudio_send_chunk; this->demux_plugin.seek = demux_mpgaudio_seek; this->demux_plugin.dispose = demux_mpgaudio_dispose; this->demux_plugin.get_status = demux_mpgaudio_get_status; this->demux_plugin.get_stream_length = demux_mpgaudio_get_stream_length; this->demux_plugin.get_capabilities = demux_mpgaudio_get_capabilities; this->demux_plugin.get_optional_data = demux_mpgaudio_get_optional_data; this->demux_plugin.demux_class = class_gen; this->input = input; this->audio_fifo = stream->audio_fifo; this->status = DEMUX_FINISHED; this->stream = stream; return &this->demux_plugin; } /* * demux mpegaudio class */ static const char *get_description (demux_class_t *this_gen) { return "MPEG audio demux plugin"; } static const char *get_identifier (demux_class_t *this_gen) { return "MPEGAUDIO"; } static const char *get_extensions (demux_class_t *this_gen) { demux_mpgaudio_class_t *this = (demux_mpgaudio_class_t *) this_gen; if( _x_decoder_available(this->xine, BUF_AUDIO_MPEG) ) return "mp3 mp2 mpa mpega"; else return ""; } static const char *get_mimetypes (demux_class_t *this_gen) { demux_mpgaudio_class_t *this = (demux_mpgaudio_class_t *) this_gen; if( _x_decoder_available(this->xine, BUF_AUDIO_MPEG) ) return "audio/mpeg2: mp2: MPEG audio;" "audio/x-mpeg2: mp2: MPEG audio;" "audio/mpeg3: mp3: MPEG audio;" "audio/x-mpeg3: mp3: MPEG audio;" "audio/mpeg: mpa,abs,mpega: MPEG audio;" "audio/x-mpeg: mpa,abs,mpega: MPEG audio;" "audio/x-mpegurl: mp3: MPEG audio;" "audio/mpegurl: mp3: MPEG audio;" "audio/mp3: mp3: MPEG audio;" "audio/x-mp3: mp3: MPEG audio;"; else return ""; } static void class_dispose (demux_class_t *this_gen) { demux_mpgaudio_class_t *this = (demux_mpgaudio_class_t *) this_gen; free (this); } void *demux_mpgaudio_init_class (xine_t *xine, void *data) { demux_mpgaudio_class_t *this; this = xine_xmalloc (sizeof (demux_mpgaudio_class_t)); this->xine = xine; this->demux_class.open_plugin = open_plugin; this->demux_class.get_description = get_description; this->demux_class.get_identifier = get_identifier; this->demux_class.get_mimetypes = get_mimetypes; this->demux_class.get_extensions = get_extensions; this->demux_class.dispose = class_dispose; return this; }