push
This commit is contained in:
872
ip/nomad.c
Normal file
872
ip/nomad.c
Normal file
@@ -0,0 +1,872 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user