873 lines
23 KiB
C
873 lines
23 KiB
C
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
* Gapless decoding added by Chun-Yu Shei <cshei AT cs.indiana.edu>
|
|
*/
|
|
|
|
/*
|
|
* 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 <mad.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#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;
|
|
}
|