/* * Copyright (C) 2006-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., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA * * xine interface to libwavpack by Diego Pettenò */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #define LOG_MODULE "demux_wavpack" #define LOG_VERBOSE #define LOG #include "xine_internal.h" #include "xineutils.h" #include "demux.h" #include "bswap.h" #include "attributes.h" #include #include "combined_wavpack.h" typedef struct { demux_plugin_t demux_plugin; xine_stream_t *stream; fifo_buffer_t *audio_fifo; input_plugin_t *input; int status; uint32_t current_sample; uint32_t samples; uint32_t samplerate; uint16_t bits_per_sample:6; uint16_t channels:4; } demux_wv_t; typedef struct { demux_class_t demux_class; } demux_wv_class_t; static int32_t xine_input_read_bytes(void *const this_gen, void *const data, const int32_t bcount) { input_plugin_t *const this = (input_plugin_t*)this_gen; return this->read(this, data, bcount); } static uint32_t xine_input_get_pos(void *const this_gen) { input_plugin_t *const this = (input_plugin_t*)this_gen; return this->get_current_pos(this); } static int xine_input_set_pos_abs(void *const this_gen, const uint32_t pos) { input_plugin_t *const this = (input_plugin_t*)this_gen; return this->seek(this, pos, SEEK_SET); } static int xine_input_set_pos_rel(void *const this_gen, const int32_t delta, const int mode) { input_plugin_t *const this = (input_plugin_t*)this_gen; return this->seek(this, delta, mode); } static int xine_input_push_back_byte(void *const this_gen, const int c) { input_plugin_t *const this = (input_plugin_t*)this_gen; if ( this->seek(this, -1, SEEK_CUR) ) { return c; } else { lprintf("xine_input_push_back_byte: unable to seek.\n"); return EOF; } } static uint32_t xine_input_get_length(void *const this_gen) { input_plugin_t *const this = (input_plugin_t*)this_gen; return this->get_length(this); } static int xine_input_can_seek(void *const this_gen) { input_plugin_t *const this = (input_plugin_t*)this_gen; return INPUT_IS_SEEKABLE(this); } static int32_t xine_input_write_bytes(__attr_unused void *const id, __attr_unused void *const data, __attr_unused const int32_t bcount) { lprintf("xine_input_write_bytes: acces is read-only.\n"); return 0; } static WavpackStreamReader wavpack_input_reader = { .read_bytes = xine_input_read_bytes, .get_pos = xine_input_get_pos, .set_pos_abs = xine_input_set_pos_abs, .set_pos_rel = xine_input_set_pos_rel, .push_back_byte = xine_input_push_back_byte, .get_length = xine_input_get_length, .can_seek = xine_input_can_seek, .write_bytes = xine_input_write_bytes }; static int open_wv_file(demux_wv_t *const this) { WavpackContext *ctx = NULL; char error[256]; /* Current version of wavpack (4.31) does not write more than this */ wvheader_t header; uint32_t tmp; /* Right now we don't support non-seekable streams */ if (! INPUT_IS_SEEKABLE(this->input) ) { lprintf("open_wv_file: non-seekable inputs aren't supported yet.\n"); return 0; } /* Read the file header */ if (_x_demux_read_header(this->input, (uint8_t*)(&header), sizeof(wvheader_t)) != sizeof(wvheader_t)) return 0; /* Validate header, we currently support only Wavpack 4 */ if ( header.idcode != wvpk_signature || (le2me_16(header.wv_version) >> 8) != 4 ) return 0; /* Rewind */ this->input->seek(this->input, 0, SEEK_SET); ctx = WavpackOpenFileInputEx(&wavpack_input_reader, this->input, NULL, error, 0, 0); if ( ! ctx ) { lprintf("xine_open_wavpack_input: unable to open the stream: %s\n", error); return 0; } this->current_sample = 0; this->samples = WavpackGetNumSamples(ctx); lprintf("number of samples: %u\n", this->samples); this->samplerate = WavpackGetSampleRate(ctx); lprintf("samplerate: %u Hz\n", this->samplerate); tmp = WavpackGetBitsPerSample(ctx); _x_assert(tmp <= 32); lprintf("bits_per_sample: %u\n", tmp); this->bits_per_sample = tmp; tmp = WavpackGetNumChannels(ctx); _x_assert(tmp <= 8); lprintf("channels: %u\n", tmp); this->channels = tmp; _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_AUDIO, 1); _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_FOURCC, wvpk_signature); _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_CHANNELS, this->channels); _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_SAMPLERATE, this->samplerate); _x_stream_info_set(this->stream, XINE_STREAM_INFO_AUDIO_BITS, this->bits_per_sample); WavpackCloseFile(ctx); this->input->seek(this->input, SEEK_SET, 0); return 1; } static int demux_wv_send_chunk(demux_plugin_t *const this_gen) { demux_wv_t *const this = (demux_wv_t *) this_gen; uint32_t bytes_to_read; uint8_t header_sent = 0; wvheader_t header; lprintf("new frame\n"); /* Check if we've finished */ if (this->current_sample >= this->samples) { lprintf("all frames read\n"); this->status = DEMUX_FINISHED; return this->status; } lprintf("current sample: %u\n", this->current_sample); do { if ( this->input->read(this->input, (uint8_t*)(&header), sizeof(wvheader_t)) != sizeof(wvheader_t) ) { this->status = DEMUX_FINISHED; return this->status; } /* The size of the block is «of course» minus 8, and it also includes the size of the header. */ bytes_to_read = le2me_32(header.block_size) + 8 - sizeof(wvheader_t); lprintf("demux_wavpack: going to read %u bytes.\n", bytes_to_read); while(bytes_to_read) { off_t bytes_read = 0, bytes_to_read_now, offset = 0; buf_element_t *buf = NULL; int64_t input_time_guess; /* Get a buffer */ buf = this->audio_fifo->buffer_pool_alloc(this->audio_fifo); buf->type = BUF_AUDIO_WAVPACK; buf->decoder_flags = 0; /* Set normalised position */ buf->extra_info->input_normpos = (int) ((double) this->input->get_current_pos(this->input) * 65535 / this->input->get_length(this->input)); buf->pts = (((this->current_sample) / this->samplerate))*90000; lprintf("Sending buffer with PTS %"PRId64"\n", buf->pts); /* Set time */ input_time_guess = this->samples; input_time_guess /= this->samplerate; input_time_guess *= 1000; input_time_guess *= buf->extra_info->input_normpos; input_time_guess /= 65535; buf->extra_info->input_time = input_time_guess; if ( ! header_sent ) offset = sizeof(wvheader_t); bytes_to_read_now = ( bytes_to_read+offset > buf->max_size ) ? buf->max_size-offset : bytes_to_read; if ( ! header_sent ) { header_sent = 1; xine_fast_memcpy(buf->content, &header, sizeof(wvheader_t)); } bytes_read = this->input->read(this->input, &buf->content[offset], bytes_to_read_now); buf->size = offset + bytes_read; bytes_to_read -= bytes_read; if ( bytes_to_read <= 0 && (le2me_32(header.flags) & FINAL_BLOCK) == FINAL_BLOCK) buf->decoder_flags |= BUF_FLAG_FRAME_END; this->audio_fifo->put(this->audio_fifo, buf); } } while ( (le2me_32(header.flags) & FINAL_BLOCK) != FINAL_BLOCK ); this->current_sample += le2me_32(header.samples_count); return this->status; } static void demux_wv_send_headers(demux_plugin_t *const this_gen) { demux_wv_t *const this = (demux_wv_t *) this_gen; buf_element_t *buf; this->audio_fifo = this->stream->audio_fifo; this->status = DEMUX_OK; /* Send start buffers */ _x_demux_control_start(this->stream); /* Send header to decoder */ if (this->audio_fifo) { buf = this->audio_fifo->buffer_pool_alloc (this->audio_fifo); buf->type = BUF_AUDIO_WAVPACK; buf->decoder_flags = BUF_FLAG_HEADER|BUF_FLAG_STDHEADER|BUF_FLAG_FRAME_END; buf->decoder_info[0] = this->input->get_length(this->input); buf->decoder_info[1] = this->samplerate; buf->decoder_info[2] = this->bits_per_sample; buf->decoder_info[3] = this->channels; buf->size = 0; this->audio_fifo->put (this->audio_fifo, buf); } } static int demux_wv_seek (demux_plugin_t *this_gen, off_t start_pos, int start_time, int playing) { demux_wv_t *const this = (demux_wv_t *) this_gen; /* If thread is not running, initialize demuxer */ if( !playing ) { /* send new pts */ _x_demux_control_newpts(this->stream, 0, 0); this->status = DEMUX_OK; } return this->status; } static void demux_wv_dispose (demux_plugin_t *const this_gen) { demux_wv_t *const this = (demux_wv_t *) this_gen; free(this); } static int demux_wv_get_status (demux_plugin_t *const this_gen) { const demux_wv_t *const this = (const demux_wv_t *) this_gen; return this->status; } static int demux_wv_get_stream_length (demux_plugin_t *const this_gen) { const demux_wv_t *const this = (demux_wv_t *) this_gen; return (this->samples*1000) / this->samplerate; } static uint32_t demux_wv_get_capabilities(demux_plugin_t *const this_gen) { return DEMUX_CAP_NOCAP; } static int demux_wv_get_optional_data(demux_plugin_t *const this_gen, void *data, const int data_type) { return DEMUX_OPTIONAL_UNSUPPORTED; } static demux_plugin_t *open_plugin (demux_class_t *const class_gen, xine_stream_t *const stream, input_plugin_t *const input) { demux_wv_t *const this = calloc(1, sizeof (demux_wv_t)); this->stream = stream; this->input = input; this->demux_plugin.send_headers = demux_wv_send_headers; this->demux_plugin.send_chunk = demux_wv_send_chunk; this->demux_plugin.seek = demux_wv_seek; this->demux_plugin.dispose = demux_wv_dispose; this->demux_plugin.get_status = demux_wv_get_status; this->demux_plugin.get_stream_length = demux_wv_get_stream_length; this->demux_plugin.get_capabilities = demux_wv_get_capabilities; this->demux_plugin.get_optional_data = demux_wv_get_optional_data; this->demux_plugin.demux_class = class_gen; this->status = DEMUX_FINISHED; switch (stream->content_detection_method) { case METHOD_BY_EXTENSION: { const char *const mrl = input->get_mrl (input); const char *const extensions = class_gen->get_extensions (class_gen); if (!_x_demux_check_extension (mrl, extensions)) { free (this); return NULL; } } /* Falling through is intended */ case METHOD_BY_CONTENT: case METHOD_EXPLICIT: if (!open_wv_file(this)) { free (this); return NULL; } break; default: free (this); return NULL; } return &this->demux_plugin; } static const char *get_description (demux_class_t *const this_gen) { return "Wavpack demux plugin"; } static const char *get_identifier (demux_class_t *const this_gen) { return "Wavpack"; } static const char *get_extensions (demux_class_t *const this_gen) { return "wv wvp"; } static const char *get_mimetypes (demux_class_t *const this_gen) { return "audio/x-wavpack: wv,wvp: WavPack audio;"; } static void class_dispose (demux_class_t *const this_gen) { demux_wv_class_t *const this = (demux_wv_class_t *) this_gen; free (this); } void *demux_wv_init_plugin (xine_t *const xine, void *const data) { demux_wv_class_t *const this = calloc(1, sizeof (demux_wv_class_t)); 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; }