/*
* Copyright 2008-2013 Various Authors
* Copyright 2004-2005 Timo Hirvonen
*
* This program 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.
*
* This program 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, see .
*/
/*
* Gapless decoding added by Chun-Yu Shei
*/
/*
* Xing code copied from xmms-mad plugin.
* Lame code copied from mpd
*/
#include "nomad.h"
#include "../id3.h"
#include "../xmalloc.h"
#include "../debug.h"
#include "../misc.h"
#include
#include
#include
#include
#include
#include
#include
#define INPUT_BUFFER_SIZE (5 * 8192)
#define SEEK_IDX_INTERVAL 15
/* the number of samples of silence the decoder inserts at start */
#define DECODERDELAY 529
#define XING_MAGIC (('X' << 24) | ('i' << 16) | ('n' << 8) | 'g')
#define INFO_MAGIC (('I' << 24) | ('n' << 16) | ('f' << 8) | 'o')
struct seek_idx_entry {
off_t offset;
mad_timer_t timer;
};
struct nomad {
struct mad_stream stream;
struct mad_frame frame;
struct mad_synth synth;
mad_timer_t timer;
unsigned long cur_frame;
off_t input_offset;
/* MAD_BUFFER_GUARD zeros are required at the end of the stream to decode the last frame
ref: http://www.mars.org/mailman/public/mad-dev/2001-May/000262.html */
unsigned char input_buffer[INPUT_BUFFER_SIZE + MAD_BUFFER_GUARD];
int i;
unsigned int has_xing : 1;
unsigned int has_lame : 1;
unsigned int seen_first_frame : 1;
unsigned int readEOF : 1;
int start_drop_frames;
int start_drop_samples;
int end_drop_samples;
int end_drop_frames;
struct nomad_xing xing;
struct nomad_lame lame;
struct {
int size;
struct seek_idx_entry *table;
} seek_idx;
struct {
unsigned long long int bitrate_sum;
unsigned long nr_frames;
} current;
struct nomad_info info;
void *datasource;
int datasource_fd;
struct nomad_callbacks cbs;
};
static inline int scale(mad_fixed_t sample)
{
sample += 1L << (MAD_F_FRACBITS - 16);
if (sample >= MAD_F_ONE) {
sample = MAD_F_ONE - 1;
} else if (sample < -MAD_F_ONE) {
sample = -MAD_F_ONE;
}
return sample >> (MAD_F_FRACBITS - 15);
}
static inline double timer_to_seconds(mad_timer_t timer)
{
signed long ms;
ms = mad_timer_count(timer, MAD_UNITS_MILLISECONDS);
return (double)ms / 1000.0;
}
static int parse_lame(struct nomad *nomad, struct mad_bitptr ptr, int bitlen)
{
int i, adj = 0;
unsigned int version_major, version_minor;
float val;
/* Unlike the xing header, the lame tag has a fixed length. Fail if
* not all 36 bytes (288 bits) are there. */
if (bitlen < 288) return 0;
for (i = 0; i < 9; i++) nomad->lame.encoder[i] = (char)mad_bit_read(&ptr, 8);
nomad->lame.encoder[9] = '\0';
/* This is technically incorrect, since the encoder might not be these.
* But there's no other way to determine if this is a lame tag, and we
* wouldn't want to go reading a tag that's not there. */
if (strncmp(nomad->lame.encoder, "LAME", 4) != 0 &&
strncmp(nomad->lame.encoder, "Lavc", 4) != 0) return 0;
if (sscanf(nomad->lame.encoder + 4, "%u.%u", &version_major, &version_minor) != 2)
return 0;
#if defined(DEBUG_LAME)
d_print("detected LAME or Lavc version %s\n", nomad->lame.encoder);
#endif
i = mad_bit_read(&ptr, 4);
#if defined(DEBUG_LAME)
d_print("LAME tag revision: %d\n", i);
#endif
nomad->lame.vbr_method = mad_bit_read(&ptr, 4);
/* ReplayGain in LAME tag was added in 3.94 */
if (version_major > 3 || (version_major == 3 && version_minor >= 94)) {
/* lowpass */
mad_bit_read(&ptr, 8);
/* The reference volume was changed from the 83dB used in the
* ReplayGain spec to 89dB in lame 3.95.1. Bump the gain for older
* versions, since everyone else uses 89dB instead of 83dB.
* Unfortunately, lame didn't differentiate between 3.95 and 3.95.1, so
* it's impossible to make the proper adjustment for 3.95.
* Fortunately, 3.95 was only out for about a day before 3.95.1 was
* released. -- tmz */
if (version_major < 3 || (version_major == 3 && version_minor < 95))
adj = 6;
val = mad_bit_read(&ptr, 32) / (float) (1 << 23);
/* peak value of 0.0 means lame didn't calculate the peak at all
* (--replaygain-fast), even silence has a value > 0.0 */
if (val)
nomad->lame.peak = val;
for (i = 0; i < 2; i++) {
int gain, gain_type;
gain_type = replaygain_decode(mad_bit_read(&ptr, 16), &gain);
val = gain / 10.f + adj;
if (gain_type == 1)
nomad->lame.trackGain = val;
/* LAME currently doesn't store any album gain!
else if (gain_type == 2)
nomad->lame.albumGain = val;
*/
}
/*
* 4 encoding flags
* 4 ATH type
* 8 minimal bitrate (if ABR -> specified bitrate)
*/
mad_bit_read(&ptr, 16);
} else
mad_bit_read(&ptr, 88);
nomad->lame.encoderDelay = mad_bit_read(&ptr, 12);
nomad->lame.encoderPadding = mad_bit_read(&ptr, 12);
#if defined(DEBUG_LAME)
if (adj > 0)
d_print("adjusted gains by %+d dB (old LAME)\n", adj);
if (!isnan(nomad->lame.peak))
d_print("peak: %f\n", nomad->lame.peak);
if (!isnan(nomad->lame.trackGain))
d_print("trackGain: %+.1f dB\n", nomad->lame.trackGain);
if (!isnan(nomad->lame.albumGain))
d_print("albumGain: %+.1f dB\n", nomad->lame.albumGain);
d_print("encoderDelay: %d, encoderPadding: %d\n", nomad->lame.encoderDelay, nomad->lame.encoderPadding);
#endif
mad_bit_read(&ptr, 96);
nomad->start_drop_frames = 1; /* XING/LAME header is an empty frame */
nomad->start_drop_samples = nomad->lame.encoderDelay + DECODERDELAY;
nomad->end_drop_samples = nomad->lame.encoderPadding - DECODERDELAY;
nomad->has_lame = 1;
return 1;
}
/*
* format:
*
* 4 "Xing"
* 4 flags
* 4 frames (optional)
* 4 bytes (optional)
* 100 TOC (optional)
* 4 scale (optional)
*/
static int xing_parse(struct nomad *nomad)
{
struct mad_bitptr ptr = nomad->stream.anc_ptr;
struct mad_bitptr start = ptr;
int oldbitlen = nomad->stream.anc_bitlen;
int bitlen = nomad->stream.anc_bitlen;
int bitsleft;
unsigned xing_id;
nomad->has_xing = 0;
nomad->has_lame = 0;
if (bitlen < 64)
return -1;
xing_id = mad_bit_read(&ptr, 32);
if (xing_id != XING_MAGIC && xing_id != INFO_MAGIC) {
/*
* Due to an unfortunate historical accident, a Xing VBR tag
* may be misplaced in a stream with CRC protection. We check
* for this by assuming the tag began two octets prior and the
* high bits of the following flags field are always zero.
*/
if (xing_id != (((XING_MAGIC+0UL) << 16) & 0xffffffffL) &&
xing_id != (((INFO_MAGIC+0UL) << 16) & 0xffffffffL))
return -1;
xing_id >>= 16;
ptr = start;
mad_bit_skip(&ptr, 16);
bitlen += 16;
}
nomad->xing.is_info = ((xing_id & 0x0000ffffL) == (INFO_MAGIC & 0x0000ffffL));
nomad->xing.flags = mad_bit_read(&ptr, 32);
bitlen -= 64;
if (nomad->xing.flags & XING_FRAMES) {
if (bitlen < 32)
return -1;
nomad->xing.nr_frames = mad_bit_read(&ptr, 32);
bitlen -= 32;
}
if (nomad->xing.flags & XING_BYTES) {
if (bitlen < 32)
return -1;
nomad->xing.bytes = mad_bit_read(&ptr, 32);
bitlen -= 32;
}
if (nomad->xing.flags & XING_TOC) {
int i;
if (bitlen < 800)
return -1;
for (i = 0; i < 100; i++)
nomad->xing.toc[i] = mad_bit_read(&ptr, 8);
bitlen -= 800;
}
if (nomad->xing.flags & XING_SCALE) {
if (bitlen < 32)
return -1;
nomad->xing.scale = mad_bit_read(&ptr, 32);
bitlen -= 32;
}
/* Make sure we consume no less than 120 bytes (960 bits) in hopes that
* the LAME tag is found there, and not right after the Xing header */
bitsleft = 960 - (oldbitlen - bitlen);
if (bitsleft < 0) return -1;
else if (bitsleft > 0) {
mad_bit_read(&ptr, bitsleft);
bitlen -= bitsleft;
}
nomad->has_xing = 1;
#if defined(DEBUG_XING)
if (nomad->xing.flags & XING_FRAMES)
d_print("frames: %d (xing)\n", nomad->xing.nr_frames);
#endif
parse_lame(nomad, ptr, bitlen);
return 0;
}
/*
* returns:
* 0: eof
* -1: error
* >0: ok
*/
static int fill_buffer(struct nomad *nomad)
{
if (nomad->stream.buffer == NULL || nomad->stream.error == MAD_ERROR_BUFLEN) {
ssize_t read_size, remaining, len;
unsigned char *read_start;
if (nomad->stream.next_frame != NULL) {
remaining = nomad->stream.bufend - nomad->stream.next_frame;
memmove(nomad->input_buffer, nomad->stream.next_frame, remaining);
read_start = nomad->input_buffer + remaining;
read_size = INPUT_BUFFER_SIZE - remaining;
} else {
read_size = INPUT_BUFFER_SIZE;
read_start = nomad->input_buffer;
remaining = 0;
}
read_size = nomad->cbs.read(nomad->datasource, read_start, read_size);
if (read_size == -1) {
if (errno != EAGAIN)
d_print("read error on bitstream (%d:%s)\n", errno, strerror(errno));
return -1;
}
if (read_size == 0) {
if (!nomad->readEOF) {
memset(nomad->input_buffer + remaining, 0, MAD_BUFFER_GUARD);
remaining += MAD_BUFFER_GUARD;
d_print("hit end of stream, appended MAD_BUFFER_GUARD zeros\n");
nomad->readEOF = 1;
}
else return 0;
}
len = read_size + remaining;
nomad->input_offset += read_size;
mad_stream_buffer(&nomad->stream, nomad->input_buffer, len);
nomad->stream.error = 0;
}
return 1;
}
static void handle_lost_sync(struct nomad *nomad)
{
unsigned long frame;
int size;
frame = nomad->cur_frame;
if (frame == 0) {
/* cur_frame is not set when scanning file */
frame = nomad->info.nr_frames;
}
size = id3_tag_size((const char *)nomad->stream.this_frame,
nomad->stream.bufend - nomad->stream.this_frame);
if (size > 0) {
d_print("frame %ld, skipping ID3 tag (%d bytes)\n", frame, size);
mad_stream_skip(&nomad->stream, size);
} else {
d_print("frame %ld\n", frame);
}
}
/* Builds a seek index as the file is decoded
* NOTE: increases nomad->timer (current position)
*/
static void build_seek_index(struct nomad *nomad)
{
mad_timer_t timer_now = nomad->timer;
off_t offset;
int idx;
mad_timer_add(&nomad->timer, nomad->frame.header.duration);
if (nomad->has_xing)
return;
if (nomad->timer.seconds < (nomad->seek_idx.size + 1) * SEEK_IDX_INTERVAL)
return;
/* offset = ftell() */
offset = nomad->input_offset;
/* subtract by buffer length to get offset to start of buffer */
offset -= (nomad->stream.bufend - nomad->input_buffer);
/* then add offset to the current frame */
offset += (nomad->stream.this_frame - nomad->input_buffer);
idx = nomad->seek_idx.size;
nomad->seek_idx.table = xrenew(struct seek_idx_entry, nomad->seek_idx.table, idx + 1);
nomad->seek_idx.table[idx].offset = offset;
nomad->seek_idx.table[idx].timer = timer_now;
nomad->seek_idx.size++;
}
static void calc_frames_fast(struct nomad *nomad)
{
if (nomad->has_xing && (nomad->xing.flags & XING_FRAMES) && nomad->xing.nr_frames) {
nomad->info.nr_frames = nomad->xing.nr_frames;
mad_timer_multiply(&nomad->timer, nomad->info.nr_frames);
} else {
nomad->info.nr_frames = nomad->info.filesize /
(nomad->stream.next_frame - nomad->stream.this_frame);
mad_timer_multiply(&nomad->timer, nomad->info.nr_frames);
}
}
static void calc_bitrate_fast(struct nomad *nomad)
{
nomad->info.vbr = nomad->has_xing ? !nomad->xing.is_info : 0;
if (nomad->has_lame && nomad->lame.vbr_method == 1)
nomad->info.vbr = 0;
if (nomad->has_xing && (nomad->xing.flags & XING_BYTES) && nomad->xing.bytes)
nomad->info.avg_bitrate = (nomad->xing.bytes * 8.0) / nomad->info.duration;
else
nomad->info.avg_bitrate = nomad->frame.header.bitrate;
}
/*
* fields
* nomad->info.avg_bitrate and
* nomad->info.vbr
* are only estimated
*/
static int scan(struct nomad *nomad)
{
struct mad_header *header = &nomad->frame.header;
while (1) {
int rc;
rc = fill_buffer(nomad);
if (rc == -1)
return -1;
if (rc == 0)
break;
if (mad_frame_decode(&nomad->frame, &nomad->stream) == -1) {
if (nomad->stream.error == MAD_ERROR_BUFLEN)
continue;
if (!MAD_RECOVERABLE(nomad->stream.error)) {
d_print("unrecoverable frame level error.\n");
return -1;
}
if (nomad->stream.error == MAD_ERROR_LOSTSYNC)
handle_lost_sync(nomad);
continue;
}
build_seek_index(nomad);
// first valid frame
nomad->info.sample_rate = header->samplerate;
nomad->info.channels = MAD_NCHANNELS(header);
nomad->info.layer = header->layer;
nomad->info.dual_channel = header->mode == MAD_MODE_DUAL_CHANNEL;
nomad->info.joint_stereo = header->mode == MAD_MODE_JOINT_STEREO;
xing_parse(nomad);
calc_frames_fast(nomad);
break;
}
if (nomad->info.nr_frames == 0) {
d_print("error: not an mp3 file!\n");
return -NOMAD_ERROR_FILE_FORMAT;
}
nomad->info.duration = timer_to_seconds(nomad->timer);
calc_bitrate_fast(nomad);
nomad->cur_frame = 0;
nomad->cbs.lseek(nomad->datasource, 0, SEEK_SET);
nomad->input_offset = 0;
return 0;
}
static int decode(struct nomad *nomad)
{
int rc;
start:
rc = fill_buffer(nomad);
if (rc == -1)
return -1;
if (rc == 0)
return 1;
if (mad_frame_decode(&nomad->frame, &nomad->stream)) {
if (nomad->stream.error == MAD_ERROR_BUFLEN)
goto start;
if (!MAD_RECOVERABLE(nomad->stream.error)) {
d_print("unrecoverable frame level error.\n");
return -1;
}
if (nomad->stream.error == MAD_ERROR_LOSTSYNC)
handle_lost_sync(nomad);
goto start;
}
nomad->cur_frame++;
nomad->current.bitrate_sum += nomad->frame.header.bitrate;
nomad->current.nr_frames++;
if (nomad->info.filesize != -1) {
build_seek_index(nomad);
} else {
mad_timer_add(&nomad->timer, nomad->frame.header.duration);
}
mad_synth_frame(&nomad->synth, &nomad->frame);
return 0;
}
static void init_mad(struct nomad *nomad)
{
mad_stream_init(&nomad->stream);
nomad->stream.options |= MAD_OPTION_IGNORECRC;
mad_frame_init(&nomad->frame);
mad_synth_init(&nomad->synth);
mad_timer_reset(&nomad->timer);
nomad->cur_frame = 0;
nomad->i = -1;
nomad->input_offset = 0;
nomad->seen_first_frame = 0;
nomad->readEOF = 0;
}
static void free_mad(struct nomad *nomad)
{
mad_stream_finish(&nomad->stream);
mad_frame_finish(&nomad->frame);
mad_synth_finish(nomad->synth);
}
static int do_open(struct nomad *nomad)
{
int rc;
init_mad(nomad);
nomad->info.filesize = nomad->cbs.lseek(nomad->datasource, 0, SEEK_END);
if (nomad->info.filesize != -1)
nomad->cbs.lseek(nomad->datasource, 0, SEEK_SET);
if (nomad->info.filesize == -1) {
rc = decode(nomad);
if (rc < 0)
goto error;
if (rc == 1)
goto eof;
nomad->info.sample_rate = nomad->frame.header.samplerate;
nomad->info.channels = MAD_NCHANNELS(&nomad->frame.header);
nomad->info.layer = nomad->frame.header.layer;
nomad->info.dual_channel = nomad->frame.header.mode == MAD_MODE_DUAL_CHANNEL;
nomad->info.joint_stereo = nomad->frame.header.mode == MAD_MODE_JOINT_STEREO;
/* unknown */
nomad->info.duration = -1.0;
nomad->info.nr_frames = -1;
nomad->info.vbr = -1;
nomad->info.avg_bitrate = -1;
} else {
rc = scan(nomad);
if (rc < 0)
goto error;
if (rc == 1)
goto eof;
free_mad(nomad);
init_mad(nomad);
}
d_print("\n frames: %d, br: %d b/s, sr: %d Hz, ch: %d, layer: %d, joint stereo: %d\n"
" dual channel: %d, vbr: %d, duration: %g s, xing: %d\n",
nomad->info.nr_frames, nomad->info.avg_bitrate,
nomad->info.sample_rate, nomad->info.channels,
nomad->info.layer, nomad->info.joint_stereo,
nomad->info.dual_channel, nomad->info.vbr,
nomad->info.duration,
nomad->has_xing);
#if defined(DEBUG_XING)
if (nomad->has_xing)
d_print("xing: flags: 0x%x, frames: %d, bytes: %d, scale: %d\n",
nomad->xing.flags,
nomad->xing.nr_frames,
nomad->xing.bytes,
nomad->xing.scale);
#endif
return 0;
error:
nomad_close(nomad);
return rc;
eof:
nomad_close(nomad);
return -NOMAD_ERROR_FILE_FORMAT;
}
int nomad_open_callbacks(struct nomad **nomadp, void *datasource, struct nomad_callbacks *cbs)
{
struct nomad *nomad;
const struct nomad nomad_init = {
.datasource = datasource,
.cbs = {
.read = cbs->read,
.lseek = cbs->lseek,
.close = cbs->close
}
};
nomad = xnew(struct nomad, 1);
*nomad = nomad_init;
nomad->lame.peak = nomad->lame.trackGain = nomad->lame.albumGain = strtof("NAN", NULL);
*nomadp = nomad;
/* on error do_open calls nomad_close */
return do_open(nomad);
}
void nomad_close(struct nomad *nomad)
{
free_mad(nomad);
nomad->cbs.close(nomad->datasource);
free(nomad->seek_idx.table);
free(nomad);
}
int nomad_read(struct nomad *nomad, char *buffer, int count)
{
int i, j, size, psize, to;
if (nomad->i == -1) {
int rc;
next_frame:
rc = decode(nomad);
if (rc < 0)
return rc;
if (rc == 1)
return 0;
nomad->i = 0;
}
if (nomad->has_lame) {
/* skip samples at start for gapless playback */
if (nomad->start_drop_frames) {
nomad->start_drop_frames--;
/* XING header is an empty frame we want to skip */
if (!nomad->seen_first_frame) {
nomad->cur_frame--;
nomad->seen_first_frame = 1;
}
#if defined(DEBUG_LAME)
d_print("skipped a frame at start\n");
#endif
goto next_frame;
}
if (nomad->start_drop_samples) {
if (nomad->start_drop_samples < nomad->synth.pcm.length) {
nomad->i += nomad->start_drop_samples;
nomad->start_drop_samples = 0;
/* Take advantage of the fact that this block is only executed once per file, and
calculate the # of samples/frames to skip at the end. Note that synth.pcm.length
is needed for the calculation. */
nomad->end_drop_frames = nomad->end_drop_samples / nomad->synth.pcm.length;
nomad->end_drop_samples = nomad->end_drop_samples % nomad->synth.pcm.length;
#if defined(DEBUG_LAME)
d_print("skipped %d samples at start\n", nomad->i);
d_print("will skip %d samples and %d frame(s) at end\n",
nomad->end_drop_samples, nomad->end_drop_frames);
#endif
}
else {
nomad->start_drop_samples -= nomad->synth.pcm.length;
#if defined(DEBUG_LAME)
d_print("skipped %d samples at start and moving to next frame\n", nomad->synth.pcm.length);
#endif
goto next_frame;
}
}
/* skip samples/frames at end for gapless playback */
if (nomad->cur_frame == (nomad->xing.nr_frames + 1 - nomad->end_drop_frames)) {
#if defined(DEBUG_LAME)
d_print("skipped %d frame(s) at end\n", nomad->end_drop_frames);
#endif
return 0;
}
}
psize = nomad->info.channels * 16 / 8;
size = (nomad->synth.pcm.length - nomad->i) * psize;
if (size > count) {
to = nomad->i + count / psize;
} else {
to = nomad->synth.pcm.length;
}
j = 0;
for (i = nomad->i; i < to; i++) {
short sample;
/* skip samples/frames at end for gapless playback */
if (nomad->has_lame
&& nomad->end_drop_samples
&& (nomad->cur_frame == (nomad->xing.nr_frames - nomad->end_drop_frames))
&& i == (nomad->synth.pcm.length - nomad->end_drop_samples)) {
nomad->i = -1;
#if defined(DEBUG_LAME)
d_print("skipped %d samples at end of frame %d\n", nomad->end_drop_samples, (int)nomad->cur_frame);
#endif
return j;
}
sample = scale(nomad->synth.pcm.samples[0][i]);
buffer[j++] = (sample >> 0) & 0xff;
buffer[j++] = (sample >> 8) & 0xff;
if (nomad->info.channels == 2) {
sample = scale(nomad->synth.pcm.samples[1][i]);
buffer[j++] = (sample >> 0) & 0xff;
buffer[j++] = (sample >> 8) & 0xff;
}
}
if (to != nomad->synth.pcm.length) {
nomad->i = i;
} else {
nomad->i = -1;
}
return j;
}
static int nomad_time_seek_accurate(struct nomad *nomad, double pos)
{
int rc;
/* seek to beginning of file and search frame-by-frame */
if (nomad->cbs.lseek(nomad->datasource, 0, SEEK_SET) == -1)
return -1;
/* XING header should NOT be counted - if we're here, we know it's present */
nomad->cur_frame = -1;
while (timer_to_seconds(nomad->timer) < pos) {
rc = fill_buffer(nomad);
if (rc == -1)
return -1;
if (rc == 0)
return 1;
if (mad_header_decode(&nomad->frame.header, &nomad->stream)) {
if (nomad->stream.error == MAD_ERROR_BUFLEN)
continue;
if (!MAD_RECOVERABLE(nomad->stream.error)) {
d_print("unrecoverable frame level error.\n");
return -1;
}
if (nomad->stream.error == MAD_ERROR_LOSTSYNC)
handle_lost_sync(nomad);
continue;
}
nomad->cur_frame++;
mad_timer_add(&nomad->timer, nomad->frame.header.duration);
}
#if defined(DEBUG_LAME)
d_print("seeked to %g = %g\n", pos, timer_to_seconds(nomad->timer));
#endif
return 0;
}
int nomad_time_seek(struct nomad *nomad, double pos)
{
off_t offset = 0;
if (pos < 0.0 || pos > nomad->info.duration) {
errno = EINVAL;
return -1;
}
if (nomad->info.filesize == -1) {
errno = ESPIPE;
return -1;
}
free_mad(nomad);
init_mad(nomad);
/* if file has a LAME header, perform frame-accurate seek for gapless playback */
if (nomad->has_lame) {
return nomad_time_seek_accurate(nomad, pos);
} else if (nomad->has_xing) {
/* calculate seek offset */
/* seek to truncate(pos / duration * 100) / 100 * duration */
double k, tmp_pos;
int ki;
k = pos / nomad->info.duration * 100.0;
ki = k;
tmp_pos = ((double)ki) / 100.0 * nomad->info.duration;
nomad->timer.seconds = (signed int)tmp_pos;
nomad->timer.fraction = (tmp_pos - (double)nomad->timer.seconds) * MAD_TIMER_RESOLUTION;
#if defined(DEBUG_XING)
d_print("seeking to %g = %g %d%%\n",
pos,
timer_to_seconds(nomad->timer),
ki);
#endif
offset = ((unsigned long long)nomad->xing.toc[ki] * nomad->xing.bytes) / 256;
} else if (nomad->seek_idx.size > 0) {
int idx = (int)(pos / SEEK_IDX_INTERVAL) - 1;
if (idx > nomad->seek_idx.size - 1)
idx = nomad->seek_idx.size - 1;
if (idx >= 0) {
offset = nomad->seek_idx.table[idx].offset;
nomad->timer = nomad->seek_idx.table[idx].timer;
}
}
if (nomad->cbs.lseek(nomad->datasource, offset, SEEK_SET) == -1)
return -1;
nomad->input_offset = offset;
while (timer_to_seconds(nomad->timer) < pos) {
int rc;
rc = fill_buffer(nomad);
if (rc == -1)
return -1;
if (rc == 0)
return 0;
if (mad_header_decode(&nomad->frame.header, &nomad->stream) == 0) {
build_seek_index(nomad);
} else {
if (!MAD_RECOVERABLE(nomad->stream.error) && nomad->stream.error != MAD_ERROR_BUFLEN) {
d_print("unrecoverable frame level error.\n");
return -1;
}
if (nomad->stream.error == MAD_ERROR_LOSTSYNC)
handle_lost_sync(nomad);
}
}
#if defined(DEBUG_XING)
if (nomad->has_xing)
d_print("seeked to %g = %g\n", pos, timer_to_seconds(nomad->timer));
#endif
return 0;
}
const struct nomad_xing *nomad_xing(struct nomad *nomad)
{
return nomad->has_xing ? &nomad->xing : NULL;
}
const struct nomad_lame *nomad_lame(struct nomad *nomad)
{
return nomad->has_lame ? &nomad->lame : NULL;
}
const struct nomad_info *nomad_info(struct nomad *nomad)
{
return &nomad->info;
}
long nomad_current_bitrate(struct nomad *nomad)
{
long bitrate = -1;
if (nomad->current.nr_frames > 0) {
bitrate = nomad->current.bitrate_sum / nomad->current.nr_frames;
nomad->current.bitrate_sum = 0;
nomad->current.nr_frames = 0;
}
return bitrate;
}