nathan@0
|
1 |
/*
|
nathan@0
|
2 |
* MP3/MPlayer plugin to VDR (C++)
|
nathan@0
|
3 |
*
|
nathan@0
|
4 |
* (C) 2001-2007 Stefan Huelswitt <s.huelswitt@gmx.de>
|
nathan@0
|
5 |
*
|
nathan@0
|
6 |
* This code is free software; you can redistribute it and/or
|
nathan@0
|
7 |
* modify it under the terms of the GNU General Public License
|
nathan@0
|
8 |
* as published by the Free Software Foundation; either version 2
|
nathan@0
|
9 |
* of the License, or (at your option) any later version.
|
nathan@0
|
10 |
*
|
nathan@0
|
11 |
* This code is distributed in the hope that it will be useful,
|
nathan@0
|
12 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
nathan@0
|
13 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
nathan@0
|
14 |
* GNU General Public License for more details.
|
nathan@0
|
15 |
*
|
nathan@0
|
16 |
* You should have received a copy of the GNU General Public License
|
nathan@0
|
17 |
* along with this program; if not, write to the Free Software
|
nathan@0
|
18 |
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
nathan@0
|
19 |
* Or, point your browser to http://www.gnu.org/copyleft/gpl.html
|
nathan@0
|
20 |
*/
|
nathan@0
|
21 |
|
nathan@0
|
22 |
#include <stdlib.h>
|
nathan@0
|
23 |
#include <stdio.h>
|
nathan@0
|
24 |
#include <sys/ioctl.h>
|
nathan@0
|
25 |
#include <math.h>
|
nathan@0
|
26 |
#ifdef WITH_OSS
|
nathan@0
|
27 |
#include <sys/soundcard.h>
|
nathan@0
|
28 |
#endif
|
nathan@0
|
29 |
|
nathan@0
|
30 |
#include <mad.h>
|
nathan@0
|
31 |
#include <id3tag.h>
|
nathan@0
|
32 |
|
nathan@0
|
33 |
#include <vdr/player.h>
|
nathan@0
|
34 |
#include <vdr/ringbuffer.h>
|
nathan@0
|
35 |
#include <vdr/thread.h>
|
nathan@0
|
36 |
#include <vdr/tools.h>
|
nathan@0
|
37 |
|
nathan@0
|
38 |
#include "common.h"
|
nathan@0
|
39 |
#include "setup-mp3.h"
|
nathan@0
|
40 |
#include "player-mp3.h"
|
nathan@0
|
41 |
#include "data-mp3.h"
|
nathan@0
|
42 |
#include "decoder.h"
|
nathan@0
|
43 |
#include "decoder-core.h"
|
nathan@0
|
44 |
|
nathan@0
|
45 |
#ifndef NO_DEBUG
|
nathan@0
|
46 |
//#define DEBUG_MODE // debug playmode changes
|
nathan@0
|
47 |
#define DEBUG_BGR // debug backround scan thread
|
nathan@0
|
48 |
#define DEBUG_DELAY 300 // debug write/decode delays
|
nathan@0
|
49 |
//#define ACC_DUMP // dump limiter lookup table to /tmp/limiter
|
nathan@0
|
50 |
#endif
|
nathan@0
|
51 |
|
nathan@0
|
52 |
#if !defined(NO_DEBUG) && defined(DEBUG_MODE)
|
nathan@0
|
53 |
#define dm(x) { (x); }
|
nathan@0
|
54 |
#else
|
nathan@0
|
55 |
#define dm(x) ;
|
nathan@0
|
56 |
#endif
|
nathan@0
|
57 |
|
nathan@0
|
58 |
#if !defined(NO_DEBUG) && defined(DEBUG_BGR)
|
nathan@0
|
59 |
#define db(x) { (x); }
|
nathan@0
|
60 |
#else
|
nathan@0
|
61 |
#define db(x) ;
|
nathan@0
|
62 |
#endif
|
nathan@0
|
63 |
|
nathan@0
|
64 |
// ----------------------------------------------------------------
|
nathan@0
|
65 |
|
nathan@0
|
66 |
#define MP3BUFSIZE (1024*1024) // output ringbuffer size
|
nathan@0
|
67 |
#define OUT_BITS 16 // output 16 bit samples to DVB driver
|
nathan@0
|
68 |
#define OUT_FACT (OUT_BITS/8*2) // output factor is 16 bit & 2 channels -> 4 bytes
|
nathan@0
|
69 |
// cResample
|
nathan@0
|
70 |
#define MAX_NSAMPLES (1152*7) // max. buffer for resampled frame
|
nathan@0
|
71 |
// cNormalize
|
nathan@0
|
72 |
#define MIN_GAIN 0.03 // min. gain required to launch the normalizer
|
nathan@0
|
73 |
#define MAX_GAIN 3.0 // max. allowed gain
|
nathan@0
|
74 |
#define USE_FAST_LIMITER
|
nathan@0
|
75 |
#define LIM_ACC 12 // bit, accuracy for lookup table
|
nathan@0
|
76 |
#define F_LIM_MAX (mad_fixed_t)((1<<(MAD_F_FRACBITS+2))-1) // max. value covered by lookup table
|
nathan@0
|
77 |
#define LIM_SHIFT (MAD_F_FRACBITS-LIM_ACC) // shift value for table lookup
|
nathan@0
|
78 |
#define F_LIM_JMP (mad_fixed_t)(1<<LIM_SHIFT) // lookup table jump between values
|
nathan@0
|
79 |
// cLevel
|
nathan@0
|
80 |
#define POW_WIN 100 // window width for smoothing power values
|
nathan@0
|
81 |
#define EPSILON 0.00000000001 // anything less than EPSILON is considered zero
|
nathan@0
|
82 |
|
nathan@0
|
83 |
// --- cResample ------------------------------------------------------------
|
nathan@0
|
84 |
|
nathan@0
|
85 |
// The resample code has been adapted from the madplay project
|
nathan@0
|
86 |
// (resample.c) found in the libmad distribution
|
nathan@0
|
87 |
|
nathan@0
|
88 |
class cResample {
|
nathan@0
|
89 |
private:
|
nathan@0
|
90 |
mad_fixed_t ratio;
|
nathan@0
|
91 |
mad_fixed_t step;
|
nathan@0
|
92 |
mad_fixed_t last;
|
nathan@0
|
93 |
mad_fixed_t resampled[MAX_NSAMPLES];
|
nathan@0
|
94 |
public:
|
nathan@0
|
95 |
bool SetInputRate(unsigned int oldrate, unsigned int newrate);
|
nathan@0
|
96 |
unsigned int ResampleBlock(unsigned int nsamples, const mad_fixed_t *old);
|
nathan@0
|
97 |
const mad_fixed_t *Resampled(void) { return resampled; }
|
nathan@0
|
98 |
};
|
nathan@0
|
99 |
|
nathan@0
|
100 |
bool cResample::SetInputRate(unsigned int oldrate, unsigned int newrate)
|
nathan@0
|
101 |
{
|
nathan@0
|
102 |
if(oldrate<8000 || oldrate>newrate*6) { // out of range
|
nathan@0
|
103 |
esyslog("WARNING: samplerate %d out of range 8000-%d\n",oldrate,newrate*6);
|
nathan@0
|
104 |
return 0;
|
nathan@0
|
105 |
}
|
nathan@0
|
106 |
ratio=mad_f_tofixed((double)oldrate/(double)newrate);
|
nathan@0
|
107 |
step=0; last=0;
|
nathan@0
|
108 |
#ifdef DEBUG
|
nathan@0
|
109 |
static mad_fixed_t oldratio=0;
|
nathan@0
|
110 |
if(oldratio!=ratio) {
|
nathan@0
|
111 |
printf("mad: new resample ratio %f (from %d kHz to %d kHz)\n",mad_f_todouble(ratio),oldrate,newrate);
|
nathan@0
|
112 |
oldratio=ratio;
|
nathan@0
|
113 |
}
|
nathan@0
|
114 |
#endif
|
nathan@0
|
115 |
return ratio!=MAD_F_ONE;
|
nathan@0
|
116 |
}
|
nathan@0
|
117 |
|
nathan@0
|
118 |
unsigned int cResample::ResampleBlock(unsigned int nsamples, const mad_fixed_t *old)
|
nathan@0
|
119 |
{
|
nathan@0
|
120 |
// This resampling algorithm is based on a linear interpolation, which is
|
nathan@0
|
121 |
// not at all the best sounding but is relatively fast and efficient.
|
nathan@0
|
122 |
//
|
nathan@0
|
123 |
// A better algorithm would be one that implements a bandlimited
|
nathan@0
|
124 |
// interpolation.
|
nathan@0
|
125 |
|
nathan@0
|
126 |
mad_fixed_t *nsam=resampled;
|
nathan@0
|
127 |
const mad_fixed_t *end=old+nsamples;
|
nathan@0
|
128 |
const mad_fixed_t *begin=nsam;
|
nathan@0
|
129 |
|
nathan@0
|
130 |
if(step < 0) {
|
nathan@0
|
131 |
step = mad_f_fracpart(-step);
|
nathan@0
|
132 |
|
nathan@0
|
133 |
while (step < MAD_F_ONE) {
|
nathan@0
|
134 |
*nsam++ = step ? last+mad_f_mul(*old-last,step) : last;
|
nathan@0
|
135 |
step += ratio;
|
nathan@0
|
136 |
if(((step + 0x00000080L) & 0x0fffff00L) == 0)
|
nathan@0
|
137 |
step = (step + 0x00000080L) & ~0x0fffffffL;
|
nathan@0
|
138 |
}
|
nathan@0
|
139 |
step -= MAD_F_ONE;
|
nathan@0
|
140 |
}
|
nathan@0
|
141 |
|
nathan@0
|
142 |
while (end - old > 1 + mad_f_intpart(step)) {
|
nathan@0
|
143 |
old += mad_f_intpart(step);
|
nathan@0
|
144 |
step = mad_f_fracpart(step);
|
nathan@0
|
145 |
*nsam++ = step ? *old + mad_f_mul(old[1] - old[0], step) : *old;
|
nathan@0
|
146 |
step += ratio;
|
nathan@0
|
147 |
if (((step + 0x00000080L) & 0x0fffff00L) == 0)
|
nathan@0
|
148 |
step = (step + 0x00000080L) & ~0x0fffffffL;
|
nathan@0
|
149 |
}
|
nathan@0
|
150 |
|
nathan@0
|
151 |
if (end - old == 1 + mad_f_intpart(step)) {
|
nathan@0
|
152 |
last = end[-1];
|
nathan@0
|
153 |
step = -step;
|
nathan@0
|
154 |
}
|
nathan@0
|
155 |
else step -= mad_f_fromint(end - old);
|
nathan@0
|
156 |
|
nathan@0
|
157 |
return nsam-begin;
|
nathan@0
|
158 |
}
|
nathan@0
|
159 |
|
nathan@0
|
160 |
// --- cLevel ----------------------------------------------------------------
|
nathan@0
|
161 |
|
nathan@0
|
162 |
// The normalize algorithm and parts of the code has been adapted from the
|
nathan@0
|
163 |
// Normalize 0.7 project. (C) 1999-2002, Chris Vaill <cvaill@cs.columbia.edu>
|
nathan@0
|
164 |
|
nathan@0
|
165 |
// A little background on how normalize computes the volume
|
nathan@0
|
166 |
// of a wav file, in case you want to know just how your
|
nathan@0
|
167 |
// files are being munged:
|
nathan@0
|
168 |
//
|
nathan@0
|
169 |
// The volumes calculated are RMS amplitudes, which corre
|
nathan@0
|
170 |
// spond (roughly) to perceived volume. Taking the RMS ampli
|
nathan@0
|
171 |
// tude of an entire file would not give us quite the measure
|
nathan@0
|
172 |
// we want, though, because a quiet song punctuated by short
|
nathan@0
|
173 |
// loud parts would average out to a quiet song, and the
|
nathan@0
|
174 |
// adjustment we would compute would make the loud parts
|
nathan@0
|
175 |
// excessively loud.
|
nathan@0
|
176 |
//
|
nathan@0
|
177 |
// What we want is to consider the maximum volume of the
|
nathan@0
|
178 |
// file, and normalize according to that. We break up the
|
nathan@0
|
179 |
// signal into 100 chunks per second, and get the signal
|
nathan@0
|
180 |
// power of each chunk, in order to get an estimation of
|
nathan@0
|
181 |
// "instantaneous power" over time. This "instantaneous
|
nathan@0
|
182 |
// power" signal varies too much to get a good measure of the
|
nathan@0
|
183 |
// original signal's maximum sustained power, so we run a
|
nathan@0
|
184 |
// smoothing algorithm over the power signal (specifically, a
|
nathan@0
|
185 |
// mean filter with a window width of 100 elements). The max
|
nathan@0
|
186 |
// imum point of the smoothed power signal turns out to be a
|
nathan@0
|
187 |
// good measure of the maximum sustained power of the file.
|
nathan@0
|
188 |
// We can then take the square root of the power to get maxi
|
nathan@0
|
189 |
// mum sustained RMS amplitude.
|
nathan@0
|
190 |
|
nathan@0
|
191 |
class cLevel {
|
nathan@0
|
192 |
private:
|
nathan@0
|
193 |
double maxpow;
|
nathan@0
|
194 |
mad_fixed_t peak;
|
nathan@0
|
195 |
struct Power {
|
nathan@0
|
196 |
// smooth
|
nathan@0
|
197 |
int npow, wpow;
|
nathan@0
|
198 |
double powsum, pows[POW_WIN];
|
nathan@0
|
199 |
// sum
|
nathan@0
|
200 |
unsigned int nsum;
|
nathan@0
|
201 |
double sum;
|
nathan@0
|
202 |
} power[2];
|
nathan@0
|
203 |
//
|
nathan@0
|
204 |
inline void AddPower(struct Power *p, double pow);
|
nathan@0
|
205 |
public:
|
nathan@0
|
206 |
void Init(void);
|
nathan@0
|
207 |
void GetPower(struct mad_pcm *pcm);
|
nathan@0
|
208 |
double GetLevel(void);
|
nathan@0
|
209 |
double GetPeak(void);
|
nathan@0
|
210 |
};
|
nathan@0
|
211 |
|
nathan@0
|
212 |
void cLevel::Init(void)
|
nathan@0
|
213 |
{
|
nathan@0
|
214 |
for(int l=0 ; l<2 ; l++) {
|
nathan@0
|
215 |
struct Power *p=&power[l];
|
nathan@0
|
216 |
p->sum=p->powsum=0.0; p->wpow=p->npow=p->nsum=0;
|
nathan@0
|
217 |
for(int i=POW_WIN-1 ; i>=0 ; i--) p->pows[i]=0.0;
|
nathan@0
|
218 |
}
|
nathan@0
|
219 |
maxpow=0.0; peak=0;
|
nathan@0
|
220 |
}
|
nathan@0
|
221 |
|
nathan@0
|
222 |
void cLevel::GetPower(struct mad_pcm *pcm)
|
nathan@0
|
223 |
{
|
nathan@0
|
224 |
for(int i=0 ; i<pcm->channels ; i++) {
|
nathan@0
|
225 |
struct Power *p=&power[i];
|
nathan@0
|
226 |
mad_fixed_t *data=pcm->samples[i];
|
nathan@0
|
227 |
for(int n=pcm->length ; n>0 ; n--) {
|
nathan@0
|
228 |
if(*data < -peak) peak = -*data;
|
nathan@0
|
229 |
if(*data > peak) peak = *data;
|
nathan@0
|
230 |
double s=mad_f_todouble(*data++);
|
nathan@0
|
231 |
p->sum+=(s*s);
|
nathan@0
|
232 |
if(++(p->nsum)>=pcm->samplerate/100) {
|
nathan@0
|
233 |
AddPower(p,p->sum/(double)p->nsum);
|
nathan@0
|
234 |
p->sum=0.0; p->nsum=0;
|
nathan@0
|
235 |
}
|
nathan@0
|
236 |
}
|
nathan@0
|
237 |
}
|
nathan@0
|
238 |
}
|
nathan@0
|
239 |
|
nathan@0
|
240 |
void cLevel::AddPower(struct Power *p, double pow)
|
nathan@0
|
241 |
{
|
nathan@0
|
242 |
p->powsum+=pow;
|
nathan@0
|
243 |
if(p->npow>=POW_WIN) {
|
nathan@0
|
244 |
if(p->powsum>maxpow) maxpow=p->powsum;
|
nathan@0
|
245 |
p->powsum-=p->pows[p->wpow];
|
nathan@0
|
246 |
}
|
nathan@0
|
247 |
else p->npow++;
|
nathan@0
|
248 |
p->pows[p->wpow]=pow;
|
nathan@0
|
249 |
p->wpow=(p->wpow+1) % POW_WIN;
|
nathan@0
|
250 |
}
|
nathan@0
|
251 |
|
nathan@0
|
252 |
double cLevel::GetLevel(void)
|
nathan@0
|
253 |
{
|
nathan@0
|
254 |
if(maxpow<EPSILON) {
|
nathan@0
|
255 |
// Either this whole file has zero power, or was too short to ever
|
nathan@0
|
256 |
// fill the smoothing buffer. In the latter case, we need to just
|
nathan@0
|
257 |
// get maxpow from whatever data we did collect.
|
nathan@0
|
258 |
|
nathan@0
|
259 |
if(power[0].powsum>maxpow) maxpow=power[0].powsum;
|
nathan@0
|
260 |
if(power[1].powsum>maxpow) maxpow=power[1].powsum;
|
nathan@0
|
261 |
}
|
nathan@0
|
262 |
double level=sqrt(maxpow/(double)POW_WIN); // adjust for the smoothing window size and root
|
nathan@0
|
263 |
d(printf("norm: new volumen level=%f peak=%f\n",level,mad_f_todouble(peak)))
|
nathan@0
|
264 |
return level;
|
nathan@0
|
265 |
}
|
nathan@0
|
266 |
|
nathan@0
|
267 |
double cLevel::GetPeak(void)
|
nathan@0
|
268 |
{
|
nathan@0
|
269 |
return mad_f_todouble(peak);
|
nathan@0
|
270 |
}
|
nathan@0
|
271 |
|
nathan@0
|
272 |
// --- cNormalize ------------------------------------------------------------
|
nathan@0
|
273 |
|
nathan@0
|
274 |
class cNormalize {
|
nathan@0
|
275 |
private:
|
nathan@0
|
276 |
mad_fixed_t gain;
|
nathan@0
|
277 |
double d_limlvl, one_limlvl;
|
nathan@0
|
278 |
mad_fixed_t limlvl;
|
nathan@0
|
279 |
bool dogain, dolimit;
|
nathan@0
|
280 |
#ifdef DEBUG
|
nathan@0
|
281 |
// stats
|
nathan@0
|
282 |
unsigned long limited, clipped, total;
|
nathan@0
|
283 |
mad_fixed_t peak;
|
nathan@0
|
284 |
#endif
|
nathan@0
|
285 |
// limiter
|
nathan@0
|
286 |
#ifdef USE_FAST_LIMITER
|
nathan@0
|
287 |
mad_fixed_t *table, tablestart;
|
nathan@0
|
288 |
int tablesize;
|
nathan@0
|
289 |
inline mad_fixed_t FastLimiter(mad_fixed_t x);
|
nathan@0
|
290 |
#endif
|
nathan@0
|
291 |
inline mad_fixed_t Limiter(mad_fixed_t x);
|
nathan@0
|
292 |
public:
|
nathan@0
|
293 |
cNormalize(void);
|
nathan@0
|
294 |
~cNormalize();
|
nathan@0
|
295 |
void Init(double Level, double Peak);
|
nathan@0
|
296 |
void Stats(void);
|
nathan@0
|
297 |
void AddGain(struct mad_pcm *pcm);
|
nathan@0
|
298 |
};
|
nathan@0
|
299 |
|
nathan@0
|
300 |
cNormalize::cNormalize(void)
|
nathan@0
|
301 |
{
|
nathan@0
|
302 |
d_limlvl=(double)MP3Setup.LimiterLevel/100.0;
|
nathan@0
|
303 |
one_limlvl=1-d_limlvl;
|
nathan@0
|
304 |
limlvl=mad_f_tofixed(d_limlvl);
|
nathan@0
|
305 |
d(printf("norm: lim_lev=%f lim_acc=%d\n",d_limlvl,LIM_ACC))
|
nathan@0
|
306 |
|
nathan@0
|
307 |
#ifdef USE_FAST_LIMITER
|
nathan@0
|
308 |
mad_fixed_t start=limlvl & ~(F_LIM_JMP-1);
|
nathan@0
|
309 |
tablestart=start;
|
nathan@0
|
310 |
tablesize=(unsigned int)(F_LIM_MAX-start)/F_LIM_JMP + 2;
|
nathan@0
|
311 |
table=new mad_fixed_t[tablesize];
|
nathan@0
|
312 |
if(table) {
|
nathan@0
|
313 |
d(printf("norm: table size=%d start=%08x jump=%08x\n",tablesize,start,F_LIM_JMP))
|
nathan@0
|
314 |
for(int i=0 ; i<tablesize ; i++) {
|
nathan@0
|
315 |
table[i]=Limiter(start);
|
nathan@0
|
316 |
start+=F_LIM_JMP;
|
nathan@0
|
317 |
}
|
nathan@0
|
318 |
tablesize--; // avoid a -1 in FastLimiter()
|
nathan@0
|
319 |
|
nathan@0
|
320 |
// do a quick accuracy check, just to be sure that FastLimiter() is working
|
nathan@0
|
321 |
// as expected :-)
|
nathan@0
|
322 |
#ifdef ACC_DUMP
|
nathan@0
|
323 |
FILE *out=fopen("/tmp/limiter","w");
|
nathan@0
|
324 |
#endif
|
nathan@0
|
325 |
mad_fixed_t maxdiff=0;
|
nathan@0
|
326 |
for(mad_fixed_t x=F_LIM_MAX ; x>=limlvl ; x-=mad_f_tofixed(1e-4)) {
|
nathan@0
|
327 |
mad_fixed_t diff=mad_f_abs(Limiter(x)-FastLimiter(x));
|
nathan@0
|
328 |
if(diff>maxdiff) maxdiff=diff;
|
nathan@0
|
329 |
#ifdef ACC_DUMP
|
nathan@0
|
330 |
fprintf(out,"%0.10f\t%0.10f\t%0.10f\t%0.10f\t%0.10f\n",
|
nathan@0
|
331 |
mad_f_todouble(x),mad_f_todouble(Limiter(x)),mad_f_todouble(FastLimiter(x)),mad_f_todouble(diff),mad_f_todouble(maxdiff));
|
nathan@0
|
332 |
if(ferror(out)) break;
|
nathan@0
|
333 |
#endif
|
nathan@0
|
334 |
}
|
nathan@0
|
335 |
#ifdef ACC_DUMP
|
nathan@0
|
336 |
fclose(out);
|
nathan@0
|
337 |
#endif
|
nathan@0
|
338 |
d(printf("norm: accuracy %.12f\n",mad_f_todouble(maxdiff)))
|
nathan@0
|
339 |
if(mad_f_todouble(maxdiff)>1e-6) {
|
nathan@0
|
340 |
esyslog("ERROR: accuracy check failed, normalizer disabled");
|
nathan@0
|
341 |
delete table; table=0;
|
nathan@0
|
342 |
}
|
nathan@0
|
343 |
}
|
nathan@0
|
344 |
else esyslog("ERROR: no memory for lookup table, normalizer disabled");
|
nathan@0
|
345 |
#endif // USE_FAST_LIMITER
|
nathan@0
|
346 |
}
|
nathan@0
|
347 |
|
nathan@0
|
348 |
cNormalize::~cNormalize()
|
nathan@0
|
349 |
{
|
nathan@0
|
350 |
#ifdef USE_FAST_LIMITER
|
nathan@0
|
351 |
delete[] table;
|
nathan@0
|
352 |
#endif
|
nathan@0
|
353 |
}
|
nathan@0
|
354 |
|
nathan@0
|
355 |
void cNormalize::Init(double Level, double Peak)
|
nathan@0
|
356 |
{
|
nathan@0
|
357 |
double Target=(double)MP3Setup.TargetLevel/100.0;
|
nathan@0
|
358 |
double dgain=Target/Level;
|
nathan@0
|
359 |
if(dgain>MAX_GAIN) dgain=MAX_GAIN;
|
nathan@0
|
360 |
gain=mad_f_tofixed(dgain);
|
nathan@0
|
361 |
// Check if we actually need to apply a gain
|
nathan@0
|
362 |
dogain=(Target>0.0 && fabs(1-dgain)>MIN_GAIN);
|
nathan@0
|
363 |
#ifdef USE_FAST_LIMITER
|
nathan@0
|
364 |
if(!table) dogain=false;
|
nathan@0
|
365 |
#endif
|
nathan@0
|
366 |
// Check if we actually need to do limiting:
|
nathan@0
|
367 |
// we have to if limiter is enabled, if gain>1 and if the peaks will clip.
|
nathan@0
|
368 |
dolimit=(d_limlvl<1.0 && dgain>1.0 && Peak*dgain>1.0);
|
nathan@0
|
369 |
#ifdef DEBUG
|
nathan@0
|
370 |
printf("norm: gain=%f dogain=%d dolimit=%d (target=%f level=%f peak=%f)\n",dgain,dogain,dolimit,Target,Level,Peak);
|
nathan@0
|
371 |
limited=clipped=total=0; peak=0;
|
nathan@0
|
372 |
#endif
|
nathan@0
|
373 |
}
|
nathan@0
|
374 |
|
nathan@0
|
375 |
void cNormalize::Stats(void)
|
nathan@0
|
376 |
{
|
nathan@0
|
377 |
#ifdef DEBUG
|
nathan@0
|
378 |
if(total)
|
nathan@0
|
379 |
printf("norm: stats tot=%ld lim=%ld/%.3f%% clip=%ld/%.3f%% peak=%.3f\n",
|
nathan@0
|
380 |
total,limited,(double)limited/total*100.0,clipped,(double)clipped/total*100.0,mad_f_todouble(peak));
|
nathan@0
|
381 |
#endif
|
nathan@0
|
382 |
}
|
nathan@0
|
383 |
|
nathan@0
|
384 |
mad_fixed_t cNormalize::Limiter(mad_fixed_t x)
|
nathan@0
|
385 |
{
|
nathan@0
|
386 |
// Limiter function:
|
nathan@0
|
387 |
//
|
nathan@0
|
388 |
// / x (for x <= lev)
|
nathan@0
|
389 |
// x' = |
|
nathan@0
|
390 |
// \ tanh((x - lev) / (1-lev)) * (1-lev) + lev (for x > lev)
|
nathan@0
|
391 |
//
|
nathan@0
|
392 |
// call only with x>=0. For negative samples, preserve sign outside this function
|
nathan@0
|
393 |
//
|
nathan@0
|
394 |
// With limiter level = 0, this is equivalent to a tanh() function;
|
nathan@0
|
395 |
// with limiter level = 1, this is equivalent to clipping.
|
nathan@0
|
396 |
|
nathan@0
|
397 |
if(x>limlvl) {
|
nathan@0
|
398 |
#ifdef DEBUG
|
nathan@0
|
399 |
if(x>MAD_F_ONE) clipped++;
|
nathan@0
|
400 |
limited++;
|
nathan@0
|
401 |
#endif
|
nathan@0
|
402 |
x=mad_f_tofixed(tanh((mad_f_todouble(x)-d_limlvl) / one_limlvl) * one_limlvl + d_limlvl);
|
nathan@0
|
403 |
}
|
nathan@0
|
404 |
return x;
|
nathan@0
|
405 |
}
|
nathan@0
|
406 |
|
nathan@0
|
407 |
#ifdef USE_FAST_LIMITER
|
nathan@0
|
408 |
mad_fixed_t cNormalize::FastLimiter(mad_fixed_t x)
|
nathan@0
|
409 |
{
|
nathan@0
|
410 |
// The fast algorithm is based on a linear interpolation between the
|
nathan@0
|
411 |
// the values in the lookup table. Relays heavly on libmads fixed point format.
|
nathan@0
|
412 |
|
nathan@0
|
413 |
if(x>limlvl) {
|
nathan@0
|
414 |
int i=(unsigned int)(x-tablestart)/F_LIM_JMP;
|
nathan@0
|
415 |
#ifdef DEBUG
|
nathan@0
|
416 |
if(x>MAD_F_ONE) clipped++;
|
nathan@0
|
417 |
limited++;
|
nathan@0
|
418 |
if(i>=tablesize) printf("norm: overflow x=%f x-ts=%f i=%d tsize=%d\n",
|
nathan@0
|
419 |
mad_f_todouble(x),mad_f_todouble(x-tablestart),i,tablesize);
|
nathan@0
|
420 |
#endif
|
nathan@0
|
421 |
mad_fixed_t r=x & (F_LIM_JMP-1);
|
nathan@0
|
422 |
x=MAD_F_ONE;
|
nathan@0
|
423 |
if(i<tablesize) {
|
nathan@0
|
424 |
mad_fixed_t *ptr=&table[i];
|
nathan@0
|
425 |
x=*ptr;
|
nathan@0
|
426 |
mad_fixed_t d=*(ptr+1)-x;
|
nathan@0
|
427 |
//x+=mad_f_mul(d,r)<<LIM_ACC; // this is not accurate as mad_f_mul() does >>MAD_F_FRACBITS
|
nathan@0
|
428 |
// which is senseless in the case of following <<LIM_ACC.
|
nathan@0
|
429 |
x+=((long long)d*(long long)r)>>LIM_SHIFT; // better, don't know if works on all machines
|
nathan@0
|
430 |
}
|
nathan@0
|
431 |
}
|
nathan@0
|
432 |
return x;
|
nathan@0
|
433 |
}
|
nathan@0
|
434 |
#endif
|
nathan@0
|
435 |
|
nathan@0
|
436 |
#ifdef USE_FAST_LIMITER
|
nathan@0
|
437 |
#define LIMITER_FUNC FastLimiter
|
nathan@0
|
438 |
#else
|
nathan@0
|
439 |
#define LIMITER_FUNC Limiter
|
nathan@0
|
440 |
#endif
|
nathan@0
|
441 |
|
nathan@0
|
442 |
void cNormalize::AddGain(struct mad_pcm *pcm)
|
nathan@0
|
443 |
{
|
nathan@0
|
444 |
if(dogain) {
|
nathan@0
|
445 |
for(int i=0 ; i<pcm->channels ; i++) {
|
nathan@0
|
446 |
mad_fixed_t *data=pcm->samples[i];
|
nathan@0
|
447 |
#ifdef DEBUG
|
nathan@0
|
448 |
total+=pcm->length;
|
nathan@0
|
449 |
#endif
|
nathan@0
|
450 |
if(dolimit) {
|
nathan@0
|
451 |
for(int n=pcm->length ; n>0 ; n--) {
|
nathan@0
|
452 |
mad_fixed_t s=mad_f_mul(*data,gain);
|
nathan@0
|
453 |
if(s<0) {
|
nathan@0
|
454 |
s=-s;
|
nathan@0
|
455 |
#ifdef DEBUG
|
nathan@0
|
456 |
if(s>peak) peak=s;
|
nathan@0
|
457 |
#endif
|
nathan@0
|
458 |
s=LIMITER_FUNC(s);
|
nathan@0
|
459 |
s=-s;
|
nathan@0
|
460 |
}
|
nathan@0
|
461 |
else {
|
nathan@0
|
462 |
#ifdef DEBUG
|
nathan@0
|
463 |
if(s>peak) peak=s;
|
nathan@0
|
464 |
#endif
|
nathan@0
|
465 |
s=LIMITER_FUNC(s);
|
nathan@0
|
466 |
}
|
nathan@0
|
467 |
*data++=s;
|
nathan@0
|
468 |
}
|
nathan@0
|
469 |
}
|
nathan@0
|
470 |
else {
|
nathan@0
|
471 |
for(int n=pcm->length ; n>0 ; n--) {
|
nathan@0
|
472 |
mad_fixed_t s=mad_f_mul(*data,gain);
|
nathan@0
|
473 |
#ifdef DEBUG
|
nathan@0
|
474 |
if(s>peak) peak=s;
|
nathan@0
|
475 |
else if(-s>peak) peak=-s;
|
nathan@0
|
476 |
#endif
|
nathan@0
|
477 |
if(s>MAD_F_ONE) s=MAD_F_ONE; // do clipping
|
nathan@0
|
478 |
if(s<-MAD_F_ONE) s=-MAD_F_ONE;
|
nathan@0
|
479 |
*data++=s;
|
nathan@0
|
480 |
}
|
nathan@0
|
481 |
}
|
nathan@0
|
482 |
}
|
nathan@0
|
483 |
}
|
nathan@0
|
484 |
}
|
nathan@0
|
485 |
|
nathan@0
|
486 |
// --- cScale ----------------------------------------------------------------
|
nathan@0
|
487 |
|
nathan@0
|
488 |
// The dither code has been adapted from the madplay project
|
nathan@0
|
489 |
// (audio.c) found in the libmad distribution
|
nathan@0
|
490 |
|
nathan@0
|
491 |
enum eAudioMode { amRoundBE, amDitherBE, amRoundLE, amDitherLE };
|
nathan@0
|
492 |
|
nathan@0
|
493 |
class cScale {
|
nathan@0
|
494 |
private:
|
nathan@0
|
495 |
enum { MIN=-MAD_F_ONE, MAX=MAD_F_ONE - 1 };
|
nathan@0
|
496 |
#ifdef DEBUG
|
nathan@0
|
497 |
// audio stats
|
nathan@0
|
498 |
unsigned long clipped_samples;
|
nathan@0
|
499 |
mad_fixed_t peak_clipping;
|
nathan@0
|
500 |
mad_fixed_t peak_sample;
|
nathan@0
|
501 |
#endif
|
nathan@0
|
502 |
// dither
|
nathan@0
|
503 |
struct dither {
|
nathan@0
|
504 |
mad_fixed_t error[3];
|
nathan@0
|
505 |
mad_fixed_t random;
|
nathan@0
|
506 |
} leftD, rightD;
|
nathan@0
|
507 |
//
|
nathan@0
|
508 |
inline mad_fixed_t Clip(mad_fixed_t sample, bool stats=true);
|
nathan@0
|
509 |
inline unsigned long Prng(unsigned long state);
|
nathan@0
|
510 |
signed long LinearRound(mad_fixed_t sample);
|
nathan@0
|
511 |
signed long LinearDither(mad_fixed_t sample, struct dither *dither);
|
nathan@0
|
512 |
public:
|
nathan@0
|
513 |
void Init(void);
|
nathan@0
|
514 |
void Stats(void);
|
nathan@0
|
515 |
unsigned int ScaleBlock(unsigned char *data, unsigned int size, unsigned int &nsamples, const mad_fixed_t * &left, const mad_fixed_t * &right, eAudioMode mode);
|
nathan@0
|
516 |
};
|
nathan@0
|
517 |
|
nathan@0
|
518 |
void cScale::Init(void)
|
nathan@0
|
519 |
{
|
nathan@0
|
520 |
#ifdef DEBUG
|
nathan@0
|
521 |
clipped_samples=0; peak_clipping=peak_sample=0;
|
nathan@0
|
522 |
#endif
|
nathan@0
|
523 |
memset(&leftD,0,sizeof(leftD));
|
nathan@0
|
524 |
memset(&rightD,0,sizeof(rightD));
|
nathan@0
|
525 |
}
|
nathan@0
|
526 |
|
nathan@0
|
527 |
void cScale::Stats(void)
|
nathan@0
|
528 |
{
|
nathan@0
|
529 |
#ifdef DEBUG
|
nathan@0
|
530 |
printf("mp3: scale stats clipped=%ld peak_clip=%f peak=%f\n",
|
nathan@0
|
531 |
clipped_samples,mad_f_todouble(peak_clipping),mad_f_todouble(peak_sample));
|
nathan@0
|
532 |
#endif
|
nathan@0
|
533 |
}
|
nathan@0
|
534 |
|
nathan@0
|
535 |
// gather signal statistics while clipping
|
nathan@0
|
536 |
mad_fixed_t cScale::Clip(mad_fixed_t sample, bool stats)
|
nathan@0
|
537 |
{
|
nathan@0
|
538 |
#ifndef DEBUG
|
nathan@0
|
539 |
if (sample > MAX) sample = MAX;
|
nathan@0
|
540 |
if (sample < MIN) sample = MIN;
|
nathan@0
|
541 |
#else
|
nathan@0
|
542 |
if(!stats) {
|
nathan@0
|
543 |
if (sample > MAX) sample = MAX;
|
nathan@0
|
544 |
if (sample < MIN) sample = MIN;
|
nathan@0
|
545 |
}
|
nathan@0
|
546 |
else {
|
nathan@0
|
547 |
if (sample >= peak_sample) {
|
nathan@0
|
548 |
if (sample > MAX) {
|
nathan@0
|
549 |
++clipped_samples;
|
nathan@0
|
550 |
if (sample - MAX > peak_clipping)
|
nathan@0
|
551 |
peak_clipping = sample - MAX;
|
nathan@0
|
552 |
sample = MAX;
|
nathan@0
|
553 |
}
|
nathan@0
|
554 |
peak_sample = sample;
|
nathan@0
|
555 |
}
|
nathan@0
|
556 |
else if (sample < -peak_sample) {
|
nathan@0
|
557 |
if (sample < MIN) {
|
nathan@0
|
558 |
++clipped_samples;
|
nathan@0
|
559 |
if (MIN - sample > peak_clipping)
|
nathan@0
|
560 |
peak_clipping = MIN - sample;
|
nathan@0
|
561 |
sample = MIN;
|
nathan@0
|
562 |
}
|
nathan@0
|
563 |
peak_sample = -sample;
|
nathan@0
|
564 |
}
|
nathan@0
|
565 |
}
|
nathan@0
|
566 |
#endif
|
nathan@0
|
567 |
return sample;
|
nathan@0
|
568 |
}
|
nathan@0
|
569 |
|
nathan@0
|
570 |
// generic linear sample quantize routine
|
nathan@0
|
571 |
signed long cScale::LinearRound(mad_fixed_t sample)
|
nathan@0
|
572 |
{
|
nathan@0
|
573 |
// round
|
nathan@0
|
574 |
sample += (1L << (MAD_F_FRACBITS - OUT_BITS));
|
nathan@0
|
575 |
// clip
|
nathan@0
|
576 |
sample=Clip(sample);
|
nathan@0
|
577 |
// quantize and scale
|
nathan@0
|
578 |
return sample >> (MAD_F_FRACBITS + 1 - OUT_BITS);
|
nathan@0
|
579 |
}
|
nathan@0
|
580 |
|
nathan@0
|
581 |
// 32-bit pseudo-random number generator
|
nathan@0
|
582 |
unsigned long cScale::Prng(unsigned long state)
|
nathan@0
|
583 |
{
|
nathan@0
|
584 |
return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL;
|
nathan@0
|
585 |
}
|
nathan@0
|
586 |
|
nathan@0
|
587 |
// generic linear sample quantize and dither routine
|
nathan@0
|
588 |
signed long cScale::LinearDither(mad_fixed_t sample, struct dither *dither)
|
nathan@0
|
589 |
{
|
nathan@0
|
590 |
// noise shape
|
nathan@0
|
591 |
sample += dither->error[0] - dither->error[1] + dither->error[2];
|
nathan@0
|
592 |
dither->error[2] = dither->error[1];
|
nathan@0
|
593 |
dither->error[1] = dither->error[0] / 2;
|
nathan@0
|
594 |
// bias
|
nathan@0
|
595 |
mad_fixed_t output = sample + (1L << (MAD_F_FRACBITS + 1 - OUT_BITS - 1));
|
nathan@0
|
596 |
const int scalebits = MAD_F_FRACBITS + 1 - OUT_BITS;
|
nathan@0
|
597 |
const mad_fixed_t mask = (1L << scalebits) - 1;
|
nathan@0
|
598 |
// dither
|
nathan@0
|
599 |
const mad_fixed_t random = Prng(dither->random);
|
nathan@0
|
600 |
output += (random & mask) - (dither->random & mask);
|
nathan@0
|
601 |
dither->random = random;
|
nathan@0
|
602 |
// clip
|
nathan@0
|
603 |
output=Clip(output);
|
nathan@0
|
604 |
sample=Clip(sample,false);
|
nathan@0
|
605 |
// quantize
|
nathan@0
|
606 |
output &= ~mask;
|
nathan@0
|
607 |
// error feedback
|
nathan@0
|
608 |
dither->error[0] = sample - output;
|
nathan@0
|
609 |
// scale
|
nathan@0
|
610 |
return output >> scalebits;
|
nathan@0
|
611 |
}
|
nathan@0
|
612 |
|
nathan@0
|
613 |
#define PUT_BE(data,sample) { *data++=(sample)>>8; *data++=(sample)>>0; }
|
nathan@0
|
614 |
#define PUT_LE(data,sample) { *data++=(sample)>>0; *data++=(sample)>>8; }
|
nathan@0
|
615 |
|
nathan@0
|
616 |
// write a block of signed 16-bit PCM samples
|
nathan@0
|
617 |
unsigned int cScale::ScaleBlock(unsigned char *data, unsigned int size, unsigned int &nsamples, const mad_fixed_t * &left, const mad_fixed_t * &right, eAudioMode mode)
|
nathan@0
|
618 |
{
|
nathan@0
|
619 |
unsigned int len=size/OUT_FACT;
|
nathan@0
|
620 |
if(len>nsamples) { len=nsamples; size=len*OUT_FACT; }
|
nathan@0
|
621 |
nsamples-=len;
|
nathan@0
|
622 |
switch(mode) {
|
nathan@0
|
623 |
case amRoundBE:
|
nathan@0
|
624 |
while(len--) {
|
nathan@0
|
625 |
signed int sample=LinearRound(*left++);
|
nathan@0
|
626 |
PUT_BE(data,sample);
|
nathan@0
|
627 |
if(right) sample=LinearRound(*right++);
|
nathan@0
|
628 |
PUT_BE(data,sample);
|
nathan@0
|
629 |
}
|
nathan@0
|
630 |
break;
|
nathan@0
|
631 |
case amDitherBE:
|
nathan@0
|
632 |
while(len--) {
|
nathan@0
|
633 |
signed int sample=LinearDither(*left++,&leftD);
|
nathan@0
|
634 |
PUT_BE(data,sample);
|
nathan@0
|
635 |
if(right) sample=LinearDither(*right++,&rightD);
|
nathan@0
|
636 |
PUT_BE(data,sample);
|
nathan@0
|
637 |
}
|
nathan@0
|
638 |
break;
|
nathan@0
|
639 |
case amRoundLE:
|
nathan@0
|
640 |
while(len--) {
|
nathan@0
|
641 |
signed int sample=LinearRound(*left++);
|
nathan@0
|
642 |
PUT_LE(data,sample);
|
nathan@0
|
643 |
if(right) sample=LinearRound(*right++);
|
nathan@0
|
644 |
PUT_LE(data,sample);
|
nathan@0
|
645 |
}
|
nathan@0
|
646 |
break;
|
nathan@0
|
647 |
case amDitherLE:
|
nathan@0
|
648 |
while(len--) {
|
nathan@0
|
649 |
signed int sample=LinearDither(*left++,&leftD);
|
nathan@0
|
650 |
PUT_LE(data,sample);
|
nathan@0
|
651 |
if(right) sample=LinearDither(*right++,&rightD);
|
nathan@0
|
652 |
PUT_LE(data,sample);
|
nathan@0
|
653 |
}
|
nathan@0
|
654 |
break;
|
nathan@0
|
655 |
}
|
nathan@0
|
656 |
return size;
|
nathan@0
|
657 |
}
|
nathan@0
|
658 |
|
nathan@0
|
659 |
// --- cShuffle ----------------------------------------------------------------
|
nathan@0
|
660 |
|
nathan@0
|
661 |
class cShuffle {
|
nathan@0
|
662 |
private:
|
nathan@0
|
663 |
int *shuffle, max;
|
nathan@0
|
664 |
unsigned int seed;
|
nathan@0
|
665 |
//
|
nathan@0
|
666 |
int Index(int pos);
|
nathan@0
|
667 |
public:
|
nathan@0
|
668 |
cShuffle(void);
|
nathan@0
|
669 |
~cShuffle();
|
nathan@0
|
670 |
void Shuffle(int num, int curr);
|
nathan@0
|
671 |
void Del(int pos);
|
nathan@0
|
672 |
void Flush(void);
|
nathan@0
|
673 |
int First(void);
|
nathan@0
|
674 |
int Next(int curr);
|
nathan@0
|
675 |
int Prev(int curr);
|
nathan@0
|
676 |
int Goto(int pos, int curr);
|
nathan@0
|
677 |
};
|
nathan@0
|
678 |
|
nathan@0
|
679 |
cShuffle::cShuffle(void)
|
nathan@0
|
680 |
{
|
nathan@0
|
681 |
shuffle=0; max=0;
|
nathan@0
|
682 |
seed=time(0);
|
nathan@0
|
683 |
}
|
nathan@0
|
684 |
|
nathan@0
|
685 |
cShuffle::~cShuffle(void)
|
nathan@0
|
686 |
{
|
nathan@0
|
687 |
Flush();
|
nathan@0
|
688 |
}
|
nathan@0
|
689 |
|
nathan@0
|
690 |
void cShuffle::Flush(void)
|
nathan@0
|
691 |
{
|
nathan@0
|
692 |
delete shuffle; shuffle=0;
|
nathan@0
|
693 |
max=0;
|
nathan@0
|
694 |
}
|
nathan@0
|
695 |
|
nathan@0
|
696 |
int cShuffle::Index(int pos)
|
nathan@0
|
697 |
{
|
nathan@0
|
698 |
if(pos>=0)
|
nathan@0
|
699 |
for(int i=0; i<max; i++) if(shuffle[i]==pos) return i;
|
nathan@0
|
700 |
return -1;
|
nathan@0
|
701 |
}
|
nathan@0
|
702 |
|
nathan@0
|
703 |
void cShuffle::Shuffle(int num, int curr)
|
nathan@0
|
704 |
{
|
nathan@0
|
705 |
int oldmax=0;
|
nathan@0
|
706 |
if(num!=max) {
|
nathan@0
|
707 |
int *ns=new int[num];
|
nathan@0
|
708 |
if(shuffle) {
|
nathan@0
|
709 |
if(num>max) {
|
nathan@0
|
710 |
memcpy(ns,shuffle,max*sizeof(int));
|
nathan@0
|
711 |
oldmax=max;
|
nathan@0
|
712 |
}
|
nathan@0
|
713 |
delete shuffle;
|
nathan@0
|
714 |
}
|
nathan@0
|
715 |
shuffle=ns; max=num;
|
nathan@0
|
716 |
}
|
nathan@0
|
717 |
if(!oldmax) curr=-1;
|
nathan@0
|
718 |
for(int i=oldmax ; i<max ; i++) shuffle[i]=i;
|
nathan@0
|
719 |
|
nathan@0
|
720 |
int in=Index(curr)+1; if(in<0) in=0;
|
nathan@0
|
721 |
if((max-in)>=2) {
|
nathan@0
|
722 |
for(int i=in ; i<max ; i++) {
|
nathan@0
|
723 |
int ran=(rand_r(&seed) % ((max-in)*4-4))/4; ran+=((ran+in) >= i);
|
nathan@0
|
724 |
int t=shuffle[i];
|
nathan@0
|
725 |
shuffle[i]=shuffle[ran+in];
|
nathan@0
|
726 |
shuffle[ran+in]=t;
|
nathan@0
|
727 |
}
|
nathan@0
|
728 |
}
|
nathan@0
|
729 |
#ifdef DEBUG
|
nathan@0
|
730 |
printf("shuffle: order (%d , %d -> %d) ",num,curr,in);
|
nathan@0
|
731 |
for(int i=0 ; i<max ; i++) printf("%d ",shuffle[i]);
|
nathan@0
|
732 |
printf("\n");
|
nathan@0
|
733 |
#endif
|
nathan@0
|
734 |
}
|
nathan@0
|
735 |
|
nathan@0
|
736 |
void cShuffle::Del(int pos)
|
nathan@0
|
737 |
{
|
nathan@0
|
738 |
int i=Index(pos);
|
nathan@0
|
739 |
if(i>=0) {
|
nathan@0
|
740 |
if(i+1<max) memmove(&shuffle[i],&shuffle[i+1],(max-i-1)*sizeof(int));
|
nathan@0
|
741 |
max--;
|
nathan@0
|
742 |
}
|
nathan@0
|
743 |
}
|
nathan@0
|
744 |
|
nathan@0
|
745 |
int cShuffle::First(void)
|
nathan@0
|
746 |
{
|
nathan@0
|
747 |
return shuffle[0];
|
nathan@0
|
748 |
}
|
nathan@0
|
749 |
|
nathan@0
|
750 |
int cShuffle::Next(int curr)
|
nathan@0
|
751 |
{
|
nathan@0
|
752 |
int i=Index(curr);
|
nathan@0
|
753 |
return (i>=0 && i+1<max) ? shuffle[i+1] : -1;
|
nathan@0
|
754 |
}
|
nathan@0
|
755 |
|
nathan@0
|
756 |
int cShuffle::Prev(int curr)
|
nathan@0
|
757 |
{
|
nathan@0
|
758 |
int i=Index(curr);
|
nathan@0
|
759 |
return (i>0) ? shuffle[i-1] : -1;
|
nathan@0
|
760 |
}
|
nathan@0
|
761 |
|
nathan@0
|
762 |
int cShuffle::Goto(int pos, int curr)
|
nathan@0
|
763 |
{
|
nathan@0
|
764 |
int i=Index(curr);
|
nathan@0
|
765 |
int g=Index(pos);
|
nathan@0
|
766 |
if(g>=0) {
|
nathan@0
|
767 |
if(g<i) {
|
nathan@0
|
768 |
for(int l=g; l<i; l++) shuffle[l]=shuffle[l+1];
|
nathan@0
|
769 |
shuffle[i]=pos;
|
nathan@0
|
770 |
}
|
nathan@0
|
771 |
else if(g>i) {
|
nathan@0
|
772 |
for(int l=g; l>i+1; l--) shuffle[l]=shuffle[l-1];
|
nathan@0
|
773 |
shuffle[i+1]=pos;
|
nathan@0
|
774 |
}
|
nathan@0
|
775 |
#ifdef DEBUG
|
nathan@0
|
776 |
printf("shuffle: goto order (%d -> %d , %d -> %d) ",pos,g,curr,i);
|
nathan@0
|
777 |
for(int i=0 ; i<max ; i++) printf("%d ",shuffle[i]);
|
nathan@0
|
778 |
printf("\n");
|
nathan@0
|
779 |
#endif
|
nathan@0
|
780 |
return pos;
|
nathan@0
|
781 |
}
|
nathan@0
|
782 |
return -1;
|
nathan@0
|
783 |
}
|
nathan@0
|
784 |
|
nathan@0
|
785 |
// --- cPlayManager ------------------------------------------------------------
|
nathan@0
|
786 |
|
nathan@0
|
787 |
#define SCANNED_ID3 1
|
nathan@0
|
788 |
#define SCANNED_LVL 2
|
nathan@0
|
789 |
|
nathan@0
|
790 |
cPlayManager *mgr=0;
|
nathan@0
|
791 |
|
nathan@0
|
792 |
cPlayManager::cPlayManager(void)
|
nathan@0
|
793 |
{
|
nathan@0
|
794 |
curr=0; currIndex=-1;
|
nathan@0
|
795 |
scan=0; stopscan=throttle=pass2=release=false;
|
nathan@0
|
796 |
play=0; playNew=eol=false;
|
nathan@0
|
797 |
shuffle=new cShuffle;
|
nathan@0
|
798 |
loopMode=(MP3Setup.InitLoopMode>0);
|
nathan@0
|
799 |
shuffleMode=(MP3Setup.InitShuffleMode>0);
|
nathan@0
|
800 |
}
|
nathan@0
|
801 |
|
nathan@0
|
802 |
cPlayManager::~cPlayManager()
|
nathan@0
|
803 |
{
|
nathan@0
|
804 |
Flush();
|
nathan@0
|
805 |
Release();
|
nathan@0
|
806 |
listMutex.Lock();
|
nathan@0
|
807 |
stopscan=true; bgCond.Broadcast();
|
nathan@0
|
808 |
listMutex.Unlock();
|
nathan@0
|
809 |
Cancel(2);
|
nathan@0
|
810 |
delete shuffle;
|
nathan@0
|
811 |
}
|
nathan@0
|
812 |
|
nathan@0
|
813 |
void cPlayManager::ThrottleWait(void)
|
nathan@0
|
814 |
{
|
nathan@0
|
815 |
while(!stopscan && !release && throttle) {
|
nathan@0
|
816 |
db(printf("mgr: background scan throttled\n"))
|
nathan@0
|
817 |
bgCond.Wait(listMutex);
|
nathan@0
|
818 |
db(printf("mgr: background scan throttle wakeup\n"))
|
nathan@0
|
819 |
}
|
nathan@0
|
820 |
}
|
nathan@0
|
821 |
|
nathan@0
|
822 |
void cPlayManager::Action(void)
|
nathan@0
|
823 |
{
|
nathan@0
|
824 |
db(printf("mgr: background scan thread started (pid=%d)\n", getpid()))
|
nathan@0
|
825 |
nice(5);
|
nathan@0
|
826 |
listMutex.Lock();
|
nathan@0
|
827 |
while(!stopscan) {
|
nathan@0
|
828 |
for(scan=list.First(); !stopscan && !release && scan; scan=list.Next(scan)) {
|
nathan@0
|
829 |
ThrottleWait();
|
nathan@0
|
830 |
listMutex.Unlock();
|
nathan@0
|
831 |
if(!(scan->user & SCANNED_ID3)) {
|
nathan@0
|
832 |
db(printf("mgr: scanning (id3) %s\n",scan->Name()))
|
nathan@0
|
833 |
cSongInfo *si=scan->Info(true);
|
nathan@0
|
834 |
if(si && si->Level>0.0) scan->user|=SCANNED_LVL;
|
nathan@0
|
835 |
scan->user|=SCANNED_ID3;
|
nathan@0
|
836 |
}
|
nathan@0
|
837 |
listMutex.Lock();
|
nathan@0
|
838 |
}
|
nathan@0
|
839 |
if(MP3Setup.BgrScan>1) {
|
nathan@0
|
840 |
pass2=true;
|
nathan@0
|
841 |
for(scan=list.First(); !stopscan && !release && scan; scan=list.Next(scan)) {
|
nathan@0
|
842 |
if(scan==curr) continue;
|
nathan@0
|
843 |
ThrottleWait();
|
nathan@0
|
844 |
listMutex.Unlock();
|
nathan@0
|
845 |
if(!(scan->user & SCANNED_LVL)) {
|
nathan@0
|
846 |
cDecoder *dec=scan->Decoder();
|
nathan@0
|
847 |
if(dec) {
|
nathan@0
|
848 |
cSongInfo *si=scan->Info(false);
|
nathan@0
|
849 |
if(!dec->IsStream() && (!si || si->Level<=0.0) && dec->Start()) {
|
nathan@0
|
850 |
db(printf("mgr: scanning (lvl) %s\n",scan->Name()))
|
nathan@0
|
851 |
cLevel level;
|
nathan@0
|
852 |
level.Init();
|
nathan@0
|
853 |
bool go=true;
|
nathan@0
|
854 |
while(go && !release) {
|
nathan@0
|
855 |
if(throttle) {
|
nathan@0
|
856 |
listMutex.Lock(); ThrottleWait(); listMutex.Unlock();
|
nathan@0
|
857 |
continue;
|
nathan@0
|
858 |
}
|
nathan@0
|
859 |
struct Decode *ds=dec->Decode();
|
nathan@0
|
860 |
switch(ds->status) {
|
nathan@0
|
861 |
case dsPlay:
|
nathan@0
|
862 |
level.GetPower(ds->pcm);
|
nathan@0
|
863 |
break;
|
nathan@0
|
864 |
case dsSkip:
|
nathan@0
|
865 |
case dsSoftError:
|
nathan@0
|
866 |
break;
|
nathan@0
|
867 |
case dsEof:
|
nathan@0
|
868 |
{
|
nathan@0
|
869 |
double l=level.GetLevel();
|
nathan@0
|
870 |
if(l>0.0) {
|
nathan@0
|
871 |
cSongInfo *si=dec->SongInfo(false);
|
nathan@0
|
872 |
cFileInfo *fi=dec->FileInfo();
|
nathan@0
|
873 |
if(si && fi) {
|
nathan@0
|
874 |
si->Level=l;
|
nathan@0
|
875 |
si->Peak=level.GetPeak();
|
nathan@0
|
876 |
InfoCache.Cache(si,fi);
|
nathan@0
|
877 |
}
|
nathan@0
|
878 |
}
|
nathan@0
|
879 |
}
|
nathan@0
|
880 |
//fall through
|
nathan@0
|
881 |
case dsOK:
|
nathan@0
|
882 |
case dsError:
|
nathan@0
|
883 |
scan->user|=SCANNED_LVL;
|
nathan@0
|
884 |
go=false;
|
nathan@0
|
885 |
break;
|
nathan@0
|
886 |
}
|
nathan@0
|
887 |
}
|
nathan@0
|
888 |
}
|
nathan@0
|
889 |
else scan->user|=SCANNED_LVL;
|
nathan@0
|
890 |
dec->Stop();
|
nathan@0
|
891 |
}
|
nathan@0
|
892 |
}
|
nathan@0
|
893 |
listMutex.Lock();
|
nathan@0
|
894 |
}
|
nathan@0
|
895 |
pass2=false;
|
nathan@0
|
896 |
}
|
nathan@0
|
897 |
do {
|
nathan@0
|
898 |
scan=0; release=false; fgCond.Broadcast();
|
nathan@0
|
899 |
db(printf("mgr: background scan idle\n"))
|
nathan@0
|
900 |
bgCond.Wait(listMutex);
|
nathan@0
|
901 |
db(printf("mgr: background scan idle wakeup\n"))
|
nathan@0
|
902 |
} while(!stopscan && (release || throttle));
|
nathan@0
|
903 |
}
|
nathan@0
|
904 |
listMutex.Unlock();
|
nathan@0
|
905 |
db(printf("mgr: background scan thread ended (pid=%d)\n", getpid()))
|
nathan@0
|
906 |
}
|
nathan@0
|
907 |
|
nathan@0
|
908 |
void cPlayManager::Throttle(bool thr)
|
nathan@0
|
909 |
{
|
nathan@0
|
910 |
if(MP3Setup.BgrScan) {
|
nathan@0
|
911 |
if(!thr && throttle) {
|
nathan@0
|
912 |
db(printf("mgr: bgr-scan -> run (%d)\n",time_ms()))
|
nathan@0
|
913 |
listMutex.Lock();
|
nathan@0
|
914 |
throttle=false; bgCond.Broadcast();
|
nathan@0
|
915 |
listMutex.Unlock();
|
nathan@0
|
916 |
}
|
nathan@0
|
917 |
if(thr && !throttle) {
|
nathan@0
|
918 |
db(printf("mgr: bgr-scan -> throttle (%d)\n",time_ms()))
|
nathan@0
|
919 |
throttle=true;
|
nathan@0
|
920 |
}
|
nathan@0
|
921 |
}
|
nathan@0
|
922 |
}
|
nathan@0
|
923 |
|
nathan@0
|
924 |
void cPlayManager::ToggleShuffle(void)
|
nathan@0
|
925 |
{
|
nathan@0
|
926 |
shuffleMode=!shuffleMode;
|
nathan@0
|
927 |
d(printf("mgr: shuffle mode toggled : %d\n",shuffleMode))
|
nathan@0
|
928 |
if(shuffleMode && !eol) {
|
nathan@0
|
929 |
curr=0; currIndex=-1;
|
nathan@0
|
930 |
shuffle->Shuffle(maxIndex+1,-1);
|
nathan@0
|
931 |
Next();
|
nathan@0
|
932 |
}
|
nathan@0
|
933 |
}
|
nathan@0
|
934 |
|
nathan@0
|
935 |
void cPlayManager::ToggleLoop(void)
|
nathan@0
|
936 |
{
|
nathan@0
|
937 |
loopMode=!loopMode;
|
nathan@0
|
938 |
d(printf("mgr: loop mode toggled : %d\n",loopMode))
|
nathan@0
|
939 |
}
|
nathan@0
|
940 |
|
nathan@0
|
941 |
bool cPlayManager::Info(int num, cMP3PlayInfo *pi)
|
nathan@0
|
942 |
{
|
nathan@0
|
943 |
cSong *s;
|
nathan@0
|
944 |
int idx=num-1;
|
nathan@0
|
945 |
if(idx<0) { idx=currIndex; s=curr; }
|
nathan@0
|
946 |
else { s=list.Get(idx); }
|
nathan@0
|
947 |
memset(pi,0,sizeof(*pi));
|
nathan@0
|
948 |
pi->Num=idx+1;
|
nathan@0
|
949 |
pi->MaxNum=maxIndex+1;
|
nathan@0
|
950 |
pi->Loop=loopMode;
|
nathan@0
|
951 |
pi->Shuffle=shuffleMode;
|
nathan@0
|
952 |
bool res=false;
|
nathan@0
|
953 |
if(s) {
|
nathan@0
|
954 |
strn0cpy(pi->Title,s->Name(),sizeof(pi->Title));
|
nathan@0
|
955 |
strn0cpy(pi->Filename,s->FullPath(),sizeof(pi->Filename));
|
nathan@0
|
956 |
cSongInfo *si=s->Info(false);
|
nathan@0
|
957 |
if(si && si->HasInfo()) {
|
nathan@6
|
958 |
static const char *modestr[] = { "Mono","Dual","Joint-Stereo","Stereo" };
|
nathan@0
|
959 |
|
nathan@0
|
960 |
if(si->Title) strn0cpy(pi->Title,si->Title,sizeof(pi->Title));
|
nathan@0
|
961 |
if(si->Artist) strn0cpy(pi->Artist,si->Artist,sizeof(pi->Artist));
|
nathan@0
|
962 |
if(si->Album) strn0cpy(pi->Album,si->Album,sizeof(pi->Album));
|
nathan@0
|
963 |
strn0cpy(pi->SMode,modestr[si->ChMode],sizeof(pi->SMode));
|
nathan@0
|
964 |
pi->Year=si->Year;
|
nathan@0
|
965 |
pi->SampleFreq=si->SampleFreq;
|
nathan@0
|
966 |
pi->Bitrate=si->Bitrate;
|
nathan@0
|
967 |
pi->MaxBitrate=si->MaxBitrate;
|
nathan@0
|
968 |
res=true;
|
nathan@0
|
969 |
}
|
nathan@0
|
970 |
}
|
nathan@0
|
971 |
pi->Hash=MakeHashBuff((char *)pi,(char *)&pi->Loop-(char *)pi);
|
nathan@0
|
972 |
return res;
|
nathan@0
|
973 |
}
|
nathan@0
|
974 |
|
nathan@0
|
975 |
void cPlayManager::Add(cPlayList *pl)
|
nathan@0
|
976 |
{
|
nathan@0
|
977 |
cMutexLock lock(&listMutex);
|
nathan@0
|
978 |
bool real=false;
|
nathan@0
|
979 |
for(cSong *song=pl->First(); song; song=pl->cList<cSong>::Next(song)) {
|
nathan@0
|
980 |
cSong *ns=new cSong(song);
|
nathan@0
|
981 |
list.Add(ns);
|
nathan@0
|
982 |
real=true;
|
nathan@0
|
983 |
}
|
nathan@0
|
984 |
if(real) {
|
nathan@0
|
985 |
if(MP3Setup.BgrScan) { stopscan=false; if(!Active()) Start(); }
|
nathan@0
|
986 |
else stopscan=true;
|
nathan@0
|
987 |
bgCond.Broadcast();
|
nathan@0
|
988 |
maxIndex=list.Count()-1;
|
nathan@0
|
989 |
if(shuffleMode) shuffle->Shuffle(maxIndex+1,currIndex);
|
nathan@0
|
990 |
if(!curr) Next();
|
nathan@0
|
991 |
}
|
nathan@0
|
992 |
}
|
nathan@0
|
993 |
|
nathan@0
|
994 |
void cPlayManager::Flush(void)
|
nathan@0
|
995 |
{
|
nathan@0
|
996 |
cMutexLock lock(&listMutex);
|
nathan@0
|
997 |
Halt();
|
nathan@0
|
998 |
list.Clear();
|
nathan@0
|
999 |
shuffle->Flush();
|
nathan@0
|
1000 |
}
|
nathan@0
|
1001 |
|
nathan@0
|
1002 |
void cPlayManager::Halt(void)
|
nathan@0
|
1003 |
{
|
nathan@0
|
1004 |
cMutexLock lock(&listMutex);
|
nathan@0
|
1005 |
curr=0; currIndex=-1;
|
nathan@0
|
1006 |
playNew=true;
|
nathan@0
|
1007 |
stopscan=true; bgCond.Broadcast();
|
nathan@0
|
1008 |
NoScan(0);
|
nathan@0
|
1009 |
NoPlay(0);
|
nathan@0
|
1010 |
}
|
nathan@0
|
1011 |
|
nathan@0
|
1012 |
void cPlayManager::NoScan(cSong *nono)
|
nathan@0
|
1013 |
{
|
nathan@0
|
1014 |
// call with listMutex locked!!
|
nathan@0
|
1015 |
while((nono && pass2 && scan==nono) || (!nono && scan)) {
|
nathan@0
|
1016 |
release=true; bgCond.Broadcast();
|
nathan@0
|
1017 |
d(printf("mgr: waiting for bgr release ... (pass2=%d nono=%p scan=%p)\n",pass2,nono,scan))
|
nathan@0
|
1018 |
fgCond.Wait(listMutex);
|
nathan@0
|
1019 |
}
|
nathan@0
|
1020 |
}
|
nathan@0
|
1021 |
|
nathan@0
|
1022 |
void cPlayManager::NoPlay(cSong *nono)
|
nathan@0
|
1023 |
{
|
nathan@0
|
1024 |
// call with listMutex locked!!
|
nathan@0
|
1025 |
while((nono && play==nono) || (!nono && play)) {
|
nathan@0
|
1026 |
playNew=true;
|
nathan@0
|
1027 |
fgCond.Wait(listMutex);
|
nathan@0
|
1028 |
}
|
nathan@0
|
1029 |
}
|
nathan@0
|
1030 |
|
nathan@0
|
1031 |
bool cPlayManager::Next(void)
|
nathan@0
|
1032 |
{
|
nathan@0
|
1033 |
cMutexLock lock(&listMutex);
|
nathan@0
|
1034 |
int ni;
|
nathan@0
|
1035 |
cSong *n;
|
nathan@0
|
1036 |
if(shuffleMode) {
|
nathan@0
|
1037 |
if(curr) {
|
nathan@0
|
1038 |
ni=shuffle->Next(currIndex);
|
nathan@0
|
1039 |
if(ni<0) {
|
nathan@0
|
1040 |
if(loopMode || eol) {
|
nathan@0
|
1041 |
shuffle->Shuffle(maxIndex+1,-1);
|
nathan@0
|
1042 |
ni=shuffle->First();
|
nathan@0
|
1043 |
}
|
nathan@0
|
1044 |
else eol=true;
|
nathan@0
|
1045 |
}
|
nathan@0
|
1046 |
}
|
nathan@0
|
1047 |
else
|
nathan@0
|
1048 |
ni=shuffle->First();
|
nathan@0
|
1049 |
n=(ni>=0) ? list.Get(ni) : 0;
|
nathan@0
|
1050 |
}
|
nathan@0
|
1051 |
else {
|
nathan@0
|
1052 |
if(curr) {
|
nathan@0
|
1053 |
n=list.cList<cSong>::Next(curr);
|
nathan@0
|
1054 |
if(!n) {
|
nathan@0
|
1055 |
if(loopMode || eol) n=list.First();
|
nathan@0
|
1056 |
else eol=true;
|
nathan@0
|
1057 |
}
|
nathan@0
|
1058 |
}
|
nathan@0
|
1059 |
else
|
nathan@0
|
1060 |
n=list.First();
|
nathan@0
|
1061 |
ni=n ? n->Index() : -1;
|
nathan@0
|
1062 |
}
|
nathan@0
|
1063 |
if(n) {
|
nathan@0
|
1064 |
curr=n; currIndex=ni;
|
nathan@0
|
1065 |
playNew=true; eol=false;
|
nathan@0
|
1066 |
d(printf("mgr: next -> %d\n",currIndex))
|
nathan@0
|
1067 |
return true;
|
nathan@0
|
1068 |
}
|
nathan@0
|
1069 |
return false;
|
nathan@0
|
1070 |
}
|
nathan@0
|
1071 |
|
nathan@0
|
1072 |
bool cPlayManager::Prev(void)
|
nathan@0
|
1073 |
{
|
nathan@0
|
1074 |
cMutexLock lock(&listMutex);
|
nathan@0
|
1075 |
int ni;
|
nathan@0
|
1076 |
cSong *n;
|
nathan@0
|
1077 |
if(shuffleMode) {
|
nathan@0
|
1078 |
ni=shuffle->Prev(currIndex);
|
nathan@0
|
1079 |
n=(ni>=0) ? list.Get(ni) : 0;
|
nathan@0
|
1080 |
}
|
nathan@0
|
1081 |
else {
|
nathan@0
|
1082 |
n=list.cList<cSong>::Prev(curr);
|
nathan@0
|
1083 |
ni=n ? n->Index() : -1;
|
nathan@0
|
1084 |
}
|
nathan@0
|
1085 |
if(n) {
|
nathan@0
|
1086 |
curr=n; currIndex=ni;
|
nathan@0
|
1087 |
playNew=true; eol=false;
|
nathan@0
|
1088 |
d(printf("mgr: prev -> %d\n",currIndex))
|
nathan@0
|
1089 |
return true;
|
nathan@0
|
1090 |
}
|
nathan@0
|
1091 |
return false;
|
nathan@0
|
1092 |
}
|
nathan@0
|
1093 |
|
nathan@0
|
1094 |
void cPlayManager::Goto(int num)
|
nathan@0
|
1095 |
{
|
nathan@0
|
1096 |
cMutexLock lock(&listMutex);
|
nathan@0
|
1097 |
if(num>0 && num<=maxIndex+1) {
|
nathan@0
|
1098 |
int idx=num-1;
|
nathan@0
|
1099 |
if(shuffleMode) {
|
nathan@0
|
1100 |
if(eol) {
|
nathan@0
|
1101 |
shuffle->Shuffle(maxIndex+1,-1);
|
nathan@0
|
1102 |
currIndex=shuffle->Goto(idx,-1);
|
nathan@0
|
1103 |
}
|
nathan@0
|
1104 |
else
|
nathan@0
|
1105 |
currIndex=shuffle->Goto(idx,currIndex);
|
nathan@0
|
1106 |
}
|
nathan@0
|
1107 |
else
|
nathan@0
|
1108 |
currIndex=idx;
|
nathan@0
|
1109 |
curr=(currIndex>=0) ? list.Get(currIndex) : 0;
|
nathan@0
|
1110 |
playNew=true; eol=false;
|
nathan@0
|
1111 |
d(printf("mgr: goto -> %d\n",currIndex))
|
nathan@0
|
1112 |
}
|
nathan@0
|
1113 |
}
|
nathan@0
|
1114 |
|
nathan@0
|
1115 |
cSong *cPlayManager::Current(void)
|
nathan@0
|
1116 |
{
|
nathan@0
|
1117 |
cMutexLock lock(&listMutex);
|
nathan@0
|
1118 |
if(!play) {
|
nathan@0
|
1119 |
NoScan(curr);
|
nathan@0
|
1120 |
play=curr;
|
nathan@0
|
1121 |
playNew=false;
|
nathan@0
|
1122 |
if(play) d(printf("mgr: playing %s\n",play->Name()))
|
nathan@0
|
1123 |
else d(printf("mgr: nothing to play\n"))
|
nathan@0
|
1124 |
fgCond.Broadcast();
|
nathan@0
|
1125 |
}
|
nathan@0
|
1126 |
return play;
|
nathan@0
|
1127 |
}
|
nathan@0
|
1128 |
|
nathan@0
|
1129 |
bool cPlayManager::NextCurrent(void)
|
nathan@0
|
1130 |
{
|
nathan@0
|
1131 |
cMutexLock lock(&listMutex);
|
nathan@0
|
1132 |
return (!eol && (playNew || Next()));
|
nathan@0
|
1133 |
}
|
nathan@0
|
1134 |
|
nathan@0
|
1135 |
bool cPlayManager::NewCurrent(void)
|
nathan@0
|
1136 |
{
|
nathan@0
|
1137 |
return playNew;
|
nathan@0
|
1138 |
}
|
nathan@0
|
1139 |
|
nathan@0
|
1140 |
void cPlayManager::Release(void)
|
nathan@0
|
1141 |
{
|
nathan@0
|
1142 |
cMutexLock lock(&listMutex);
|
nathan@0
|
1143 |
play=0;
|
nathan@0
|
1144 |
fgCond.Broadcast();
|
nathan@0
|
1145 |
}
|
nathan@0
|
1146 |
|
nathan@0
|
1147 |
// --- cOutput -----------------------------------------------------------------
|
nathan@0
|
1148 |
|
nathan@0
|
1149 |
struct FrameHeader {
|
nathan@0
|
1150 |
unsigned int samplerate;
|
nathan@0
|
1151 |
};
|
nathan@0
|
1152 |
#define FHS sizeof(struct FrameHeader)
|
nathan@0
|
1153 |
|
nathan@0
|
1154 |
class cOutput {
|
nathan@0
|
1155 |
protected:
|
nathan@0
|
1156 |
cMP3Player *player;
|
nathan@0
|
1157 |
cScale scale;
|
nathan@0
|
1158 |
public:
|
nathan@0
|
1159 |
cOutput(cMP3Player *Player);
|
nathan@0
|
1160 |
virtual ~cOutput() {}
|
nathan@0
|
1161 |
virtual void Init(void);
|
nathan@0
|
1162 |
virtual unsigned int SampleRate(unsigned int PcmSampleRate)=0;
|
nathan@0
|
1163 |
virtual cFrame *MakeFrame(unsigned int & Samples, const mad_fixed_t **Data, int index, int sr)=0;
|
nathan@0
|
1164 |
virtual int Output(const unsigned char *Data, int Len, bool SOF)=0;
|
nathan@0
|
1165 |
virtual bool Poll(void)=0;
|
nathan@0
|
1166 |
virtual void Play(void)=0;
|
nathan@0
|
1167 |
virtual void Pause(void)=0;
|
nathan@0
|
1168 |
#ifdef DEBUG
|
nathan@0
|
1169 |
virtual void Stats(void);
|
nathan@0
|
1170 |
#endif
|
nathan@0
|
1171 |
};
|
nathan@0
|
1172 |
|
nathan@0
|
1173 |
cOutput::cOutput(cMP3Player *Player)
|
nathan@0
|
1174 |
{
|
nathan@0
|
1175 |
player=Player;
|
nathan@0
|
1176 |
}
|
nathan@0
|
1177 |
|
nathan@0
|
1178 |
void cOutput::Init(void)
|
nathan@0
|
1179 |
{
|
nathan@0
|
1180 |
scale.Init();
|
nathan@0
|
1181 |
}
|
nathan@0
|
1182 |
|
nathan@0
|
1183 |
#ifdef DEBUG
|
nathan@0
|
1184 |
void cOutput::Stats(void)
|
nathan@0
|
1185 |
{
|
nathan@0
|
1186 |
scale.Stats();
|
nathan@0
|
1187 |
}
|
nathan@0
|
1188 |
#endif
|
nathan@0
|
1189 |
|
nathan@0
|
1190 |
// --- cOutputDvb --------------------------------------------------------------
|
nathan@0
|
1191 |
|
nathan@0
|
1192 |
/*
|
nathan@0
|
1193 |
struct LPCMHeader { int id:8; // id
|
nathan@0
|
1194 |
int frame_count:8; // number of frames
|
nathan@0
|
1195 |
int access_ptr:16; // first acces unit pointer, i.e. start of audio frame
|
nathan@0
|
1196 |
bool emphasis:1; // audio emphasis on-off
|
nathan@0
|
1197 |
bool mute:1; // audio mute on-off
|
nathan@0
|
1198 |
bool reserved:1; // reserved
|
nathan@0
|
1199 |
int frame_number:5; // audio frame number
|
nathan@0
|
1200 |
int quant_wlen:2; // quantization word length
|
nathan@0
|
1201 |
int sample_freq:2; // audio sampling frequency (48khz=0, 96khz=1, 44,1khz=2, 32khz=3)
|
nathan@0
|
1202 |
bool reserved2:1; // reserved
|
nathan@0
|
1203 |
int chan_count:3; // number of audio channels - 1 (e.g. stereo = 1)
|
nathan@0
|
1204 |
int dyn_range_ctrl:8; // dynamic range control (0x80 if off)
|
nathan@0
|
1205 |
};
|
nathan@0
|
1206 |
*/
|
nathan@0
|
1207 |
|
nathan@0
|
1208 |
#define FRAMESIZE 2048 // max. frame size allowed for DVB driver
|
nathan@0
|
1209 |
|
nathan@0
|
1210 |
class cOutputDvb : public cOutput {
|
nathan@0
|
1211 |
private:
|
nathan@0
|
1212 |
cPoller poll;
|
nathan@0
|
1213 |
unsigned int outSr;
|
nathan@0
|
1214 |
bool only48khz;
|
nathan@0
|
1215 |
public:
|
nathan@0
|
1216 |
cOutputDvb(cMP3Player *Player);
|
nathan@0
|
1217 |
virtual unsigned int SampleRate(unsigned int PcmSampleRate);
|
nathan@0
|
1218 |
virtual cFrame *MakeFrame(unsigned int & Samples, const mad_fixed_t **Data, int index, int sr);
|
nathan@0
|
1219 |
virtual int Output(const unsigned char *Data, int Len, bool SOF);
|
nathan@0
|
1220 |
virtual bool Poll(void);
|
nathan@0
|
1221 |
virtual void Play(void);
|
nathan@0
|
1222 |
virtual void Pause(void);
|
nathan@0
|
1223 |
};
|
nathan@0
|
1224 |
|
nathan@0
|
1225 |
cOutputDvb::cOutputDvb(cMP3Player *Player)
|
nathan@0
|
1226 |
:cOutput(Player)
|
nathan@0
|
1227 |
{
|
nathan@0
|
1228 |
only48khz=MP3Setup.Only48kHz;
|
nathan@0
|
1229 |
outSr=0;
|
nathan@0
|
1230 |
#if APIVERSNUM == 10318
|
nathan@0
|
1231 |
cDevice::PrimaryDevice()->SetCurrentAudioTrack(ttDolbyFirst);
|
nathan@0
|
1232 |
#elif APIVERSNUM >= 10319
|
nathan@0
|
1233 |
cDevice::PrimaryDevice()->SetCurrentAudioTrack(ttAudio);
|
nathan@0
|
1234 |
#endif
|
nathan@0
|
1235 |
d(printf("mp3-dvb: using DVB output\n"))
|
nathan@0
|
1236 |
}
|
nathan@0
|
1237 |
|
nathan@0
|
1238 |
unsigned int cOutputDvb::SampleRate(unsigned int PcmSampleRate)
|
nathan@0
|
1239 |
{
|
nathan@0
|
1240 |
unsigned int samplerate=48000;
|
nathan@0
|
1241 |
if(!only48khz) {
|
nathan@0
|
1242 |
switch(PcmSampleRate) { // If one of the supported frequencies, do it without resampling.
|
nathan@0
|
1243 |
case 96000: // Select a "even" upsampling frequency if possible, too.
|
nathan@0
|
1244 |
samplerate=96000;
|
nathan@0
|
1245 |
break;
|
nathan@0
|
1246 |
//case 48000: // this is already the default ...
|
nathan@0
|
1247 |
// samplerate=48000;
|
nathan@0
|
1248 |
// break;
|
nathan@0
|
1249 |
case 11025:
|
nathan@0
|
1250 |
case 22050:
|
nathan@0
|
1251 |
case 44100:
|
nathan@0
|
1252 |
samplerate=44100;
|
nathan@0
|
1253 |
break;
|
nathan@0
|
1254 |
case 8000:
|
nathan@0
|
1255 |
case 16000:
|
nathan@0
|
1256 |
case 32000:
|
nathan@0
|
1257 |
samplerate=32000;
|
nathan@0
|
1258 |
break;
|
nathan@0
|
1259 |
}
|
nathan@0
|
1260 |
}
|
nathan@0
|
1261 |
return samplerate;
|
nathan@0
|
1262 |
}
|
nathan@0
|
1263 |
|
nathan@0
|
1264 |
cFrame *cOutputDvb::MakeFrame(unsigned int & Samples, const mad_fixed_t **Data, int index, int sr)
|
nathan@0
|
1265 |
{
|
nathan@0
|
1266 |
static const unsigned char header[] = {
|
nathan@0
|
1267 |
0x00, // PES header
|
nathan@0
|
1268 |
0x00,
|
nathan@0
|
1269 |
0x01,
|
nathan@0
|
1270 |
0xBD, // private stream
|
nathan@0
|
1271 |
0x00,
|
nathan@0
|
1272 |
0x00,
|
nathan@0
|
1273 |
0x87, // mpeg2, aligned, copyright, original
|
nathan@0
|
1274 |
0x00, // no pts/dts
|
nathan@0
|
1275 |
0x00, // PES header data len
|
nathan@0
|
1276 |
0xA0, // aLPCM header
|
nathan@0
|
1277 |
0xFF,
|
nathan@0
|
1278 |
0x00,
|
nathan@0
|
1279 |
0x04,
|
nathan@0
|
1280 |
0x00,
|
nathan@0
|
1281 |
0x01, // 2-channel stereo (n-1)
|
nathan@0
|
1282 |
0x80 // neutral dynamic range
|
nathan@0
|
1283 |
};
|
nathan@0
|
1284 |
cFrame *f=0;
|
nathan@0
|
1285 |
unsigned char *buff=MALLOC(uchar,FRAMESIZE);
|
nathan@0
|
1286 |
if(buff) {
|
nathan@0
|
1287 |
struct FrameHeader *fh=(struct FrameHeader *)buff;
|
nathan@0
|
1288 |
fh->samplerate=sr;
|
nathan@0
|
1289 |
memcpy(buff+FHS,header,sizeof(header));
|
nathan@0
|
1290 |
int srMode;
|
nathan@0
|
1291 |
switch(sr) {
|
nathan@0
|
1292 |
default:
|
nathan@0
|
1293 |
case 48000: srMode=0<<4; break;
|
nathan@0
|
1294 |
case 96000: srMode=1<<4; break;
|
nathan@0
|
1295 |
case 44100: srMode=2<<4; break;
|
nathan@0
|
1296 |
case 32000: srMode=3<<4; break;
|
nathan@0
|
1297 |
}
|
nathan@0
|
1298 |
buff[14+FHS]|=srMode;
|
nathan@0
|
1299 |
unsigned int outlen=scale.ScaleBlock(buff+sizeof(header)+FHS,FRAMESIZE-sizeof(header)-FHS,Samples,Data[0],Data[1],MP3Setup.AudioMode?amDitherBE:amRoundBE);
|
nathan@0
|
1300 |
if(outlen) {
|
nathan@0
|
1301 |
// lPCM has 600 fps which is 80 samples at 48kHz per channel
|
nathan@0
|
1302 |
// Frame size = (sample_rate * quantization * channels)/4800
|
nathan@0
|
1303 |
buff[10+FHS]=outlen*(4800/16/2)/sr;
|
nathan@0
|
1304 |
outlen+=(sizeof(header)-6);
|
nathan@0
|
1305 |
buff[4+FHS]=outlen>>8;
|
nathan@0
|
1306 |
buff[5+FHS]=outlen;
|
nathan@0
|
1307 |
f=new cFrame(buff,-(outlen+6+FHS),ftUnknown,index);
|
nathan@0
|
1308 |
}
|
nathan@0
|
1309 |
if(!f) free(buff);
|
nathan@0
|
1310 |
}
|
nathan@0
|
1311 |
return f;
|
nathan@0
|
1312 |
}
|
nathan@0
|
1313 |
|
nathan@0
|
1314 |
#ifdef BROKEN_PCM
|
nathan@0
|
1315 |
#include "player-mp3-sample.c"
|
nathan@0
|
1316 |
#endif
|
nathan@0
|
1317 |
|
nathan@0
|
1318 |
int cOutputDvb::Output(const unsigned char *Data, int Len, bool SOF)
|
nathan@0
|
1319 |
{
|
nathan@0
|
1320 |
int n=0;
|
nathan@0
|
1321 |
if(SOF) {
|
nathan@0
|
1322 |
#ifdef BROKEN_PCM
|
nathan@0
|
1323 |
struct FrameHeader *fh=(struct FrameHeader *)Data;
|
nathan@0
|
1324 |
if(fh->samplerate!=outSr) {
|
nathan@0
|
1325 |
if(outSr) {
|
nathan@0
|
1326 |
// at this point we would need access to AUDIO_STOP/AUDIO_PLAY
|
nathan@0
|
1327 |
// ioctl, but unfortunaly VDR's API doesn't provides this.
|
nathan@0
|
1328 |
// So we have to do magic to make the driver switch samplerate.
|
nathan@0
|
1329 |
const unsigned char *p=testAudio;
|
nathan@0
|
1330 |
int pc=sizeof(testAudio);
|
nathan@0
|
1331 |
int r;
|
nathan@0
|
1332 |
do {
|
nathan@0
|
1333 |
#if APIVERSNUM < 10318
|
nathan@0
|
1334 |
r=player->PlayVideo(p,pc);
|
nathan@0
|
1335 |
#else
|
nathan@0
|
1336 |
r=player->PlayPes(p,pc);
|
nathan@0
|
1337 |
#endif
|
nathan@0
|
1338 |
if(r>0) { p+=r; pc-=r; }
|
nathan@0
|
1339 |
if(r==0) Poll();
|
nathan@0
|
1340 |
} while(r>=0 && pc>0);
|
nathan@0
|
1341 |
}
|
nathan@0
|
1342 |
outSr=fh->samplerate;
|
nathan@0
|
1343 |
d(printf("mp3-dvb: output samplerate now %d\n",outSr))
|
nathan@0
|
1344 |
}
|
nathan@0
|
1345 |
#endif
|
nathan@0
|
1346 |
n=FHS;
|
nathan@0
|
1347 |
Data+=n; Len-=n;
|
nathan@0
|
1348 |
}
|
nathan@0
|
1349 |
#if APIVERSNUM < 10318
|
nathan@0
|
1350 |
int r=player->PlayVideo(Data,Len);
|
nathan@0
|
1351 |
#else
|
nathan@0
|
1352 |
int r=player->PlayPes(Data,Len);
|
nathan@0
|
1353 |
#endif
|
nathan@0
|
1354 |
return (r>=0 ? r+n : -1);
|
nathan@0
|
1355 |
}
|
nathan@0
|
1356 |
|
nathan@0
|
1357 |
bool cOutputDvb::Poll(void)
|
nathan@0
|
1358 |
{
|
nathan@0
|
1359 |
return player->DevicePoll(poll,500);
|
nathan@0
|
1360 |
}
|
nathan@0
|
1361 |
|
nathan@0
|
1362 |
void cOutputDvb::Play(void)
|
nathan@0
|
1363 |
{
|
nathan@0
|
1364 |
#ifndef BROKEN_PCM
|
nathan@0
|
1365 |
player->DevicePlay();
|
nathan@0
|
1366 |
#endif
|
nathan@0
|
1367 |
}
|
nathan@0
|
1368 |
|
nathan@0
|
1369 |
void cOutputDvb::Pause(void)
|
nathan@0
|
1370 |
{
|
nathan@0
|
1371 |
#ifndef BROKEN_PCM
|
nathan@0
|
1372 |
player->DeviceFreeze();
|
nathan@0
|
1373 |
#endif
|
nathan@0
|
1374 |
}
|
nathan@0
|
1375 |
|
nathan@0
|
1376 |
// --- cOutputOss --------------------------------------------------------------
|
nathan@0
|
1377 |
|
nathan@0
|
1378 |
#ifdef WITH_OSS
|
nathan@0
|
1379 |
|
nathan@0
|
1380 |
const char *dspdevice="/dev/dsp";
|
nathan@0
|
1381 |
|
nathan@0
|
1382 |
class cOutputOss : public cOutput {
|
nathan@0
|
1383 |
private:
|
nathan@0
|
1384 |
int fd;
|
nathan@0
|
1385 |
cPoller poll;
|
nathan@0
|
1386 |
unsigned int outSr;
|
nathan@0
|
1387 |
unsigned char buff[8192];
|
nathan@0
|
1388 |
//
|
nathan@0
|
1389 |
bool Reset(unsigned int sr);
|
nathan@0
|
1390 |
public:
|
nathan@0
|
1391 |
cOutputOss(cMP3Player *Player);
|
nathan@0
|
1392 |
virtual ~cOutputOss();
|
nathan@0
|
1393 |
virtual void Init(void);
|
nathan@0
|
1394 |
virtual unsigned int SampleRate(unsigned int PcmSampleRate);
|
nathan@0
|
1395 |
virtual cFrame *MakeFrame(unsigned int & Samples, const mad_fixed_t **Data, int index, int sr);
|
nathan@0
|
1396 |
virtual int Output(const unsigned char *Data, int Len, bool SOF);
|
nathan@0
|
1397 |
virtual bool Poll(void);
|
nathan@0
|
1398 |
virtual void Play(void);
|
nathan@0
|
1399 |
virtual void Pause(void);
|
nathan@0
|
1400 |
};
|
nathan@0
|
1401 |
|
nathan@0
|
1402 |
cOutputOss::cOutputOss(cMP3Player *Player)
|
nathan@0
|
1403 |
:cOutput(Player)
|
nathan@0
|
1404 |
{
|
nathan@0
|
1405 |
fd=-1; outSr=0;
|
nathan@0
|
1406 |
d(printf("mp3-oss: using OSS output\n"))
|
nathan@0
|
1407 |
}
|
nathan@0
|
1408 |
|
nathan@0
|
1409 |
cOutputOss::~cOutputOss()
|
nathan@0
|
1410 |
{
|
nathan@0
|
1411 |
close(fd);
|
nathan@0
|
1412 |
}
|
nathan@0
|
1413 |
|
nathan@0
|
1414 |
void cOutputOss::Init(void)
|
nathan@0
|
1415 |
{
|
nathan@0
|
1416 |
if(fd<0) {
|
nathan@0
|
1417 |
fd=open(dspdevice,O_WRONLY|O_NONBLOCK);
|
nathan@0
|
1418 |
if(fd>=0) poll.Add(fd,true);
|
nathan@0
|
1419 |
else esyslog("ERROR: Cannot open dsp device '%s': %s!",dspdevice,strerror(errno));
|
nathan@0
|
1420 |
}
|
nathan@0
|
1421 |
cOutput::Init();
|
nathan@0
|
1422 |
}
|
nathan@0
|
1423 |
|
nathan@0
|
1424 |
bool cOutputOss::Reset(unsigned int sr)
|
nathan@0
|
1425 |
{
|
nathan@0
|
1426 |
if(fd>=0) {
|
nathan@0
|
1427 |
CHECK(ioctl(fd,SNDCTL_DSP_SYNC,0));
|
nathan@0
|
1428 |
int format=AFMT_S16_LE;
|
nathan@0
|
1429 |
CHECK(ioctl(fd,SNDCTL_DSP_SETFMT,&format));
|
nathan@0
|
1430 |
if(format==AFMT_S16_LE) {
|
nathan@0
|
1431 |
int channels=2;
|
nathan@0
|
1432 |
CHECK(ioctl(fd,SNDCTL_DSP_CHANNELS,&channels));
|
nathan@0
|
1433 |
if(channels==2) {
|
nathan@0
|
1434 |
int real=sr;
|
nathan@0
|
1435 |
CHECK(ioctl(fd,SNDCTL_DSP_SPEED,&real));
|
nathan@0
|
1436 |
d(printf("oss: DSP samplerate now %d\n",real))
|
nathan@0
|
1437 |
if(abs(real-sr)<sr/50) {
|
nathan@0
|
1438 |
outSr=sr;
|
nathan@0
|
1439 |
d(printf("mp3-oss: DSP reset done\n"))
|
nathan@0
|
1440 |
return true;
|
nathan@0
|
1441 |
}
|
nathan@0
|
1442 |
else {
|
nathan@0
|
1443 |
d(printf("mp3-oss: driver can't handle samplerate %d, got %d\n",sr,real))
|
nathan@0
|
1444 |
esyslog("ERROR: OSS driver can't handle samplerate %d, got %d\n",sr,real);
|
nathan@0
|
1445 |
}
|
nathan@0
|
1446 |
}
|
nathan@0
|
1447 |
else {
|
nathan@0
|
1448 |
d(printf("mp3-oss: 2-channel stereo not supported\n"))
|
nathan@0
|
1449 |
esyslog("ERROR: OSS driver doesn't support 2-channel stereo.");
|
nathan@0
|
1450 |
}
|
nathan@0
|
1451 |
}
|
nathan@0
|
1452 |
else {
|
nathan@0
|
1453 |
d(printf("mp3-oss: little-endian samples not supported\n"))
|
nathan@0
|
1454 |
esyslog("ERROR: OSS driver doesn't support 16-bit little-endian samples.");
|
nathan@0
|
1455 |
}
|
nathan@0
|
1456 |
close(fd); fd=-1;
|
nathan@0
|
1457 |
}
|
nathan@0
|
1458 |
return false;
|
nathan@0
|
1459 |
}
|
nathan@0
|
1460 |
|
nathan@0
|
1461 |
unsigned int cOutputOss::SampleRate(unsigned int PcmSampleRate)
|
nathan@0
|
1462 |
{
|
nathan@0
|
1463 |
return PcmSampleRate;
|
nathan@0
|
1464 |
}
|
nathan@0
|
1465 |
|
nathan@0
|
1466 |
cFrame *cOutputOss::MakeFrame(unsigned int & Samples, const mad_fixed_t **Data, int index, int sr)
|
nathan@0
|
1467 |
{
|
nathan@0
|
1468 |
struct FrameHeader *fh=(struct FrameHeader *)buff;
|
nathan@0
|
1469 |
fh->samplerate=sr;
|
nathan@0
|
1470 |
cFrame *f=0;
|
nathan@0
|
1471 |
unsigned int outlen=scale.ScaleBlock(buff+FHS,sizeof(buff)-FHS,Samples,Data[0],Data[1],MP3Setup.AudioMode?amDitherLE:amRoundLE);
|
nathan@0
|
1472 |
if(outlen)
|
nathan@0
|
1473 |
f=new cFrame(buff,outlen+FHS,ftUnknown,index);
|
nathan@0
|
1474 |
return f;
|
nathan@0
|
1475 |
}
|
nathan@0
|
1476 |
|
nathan@0
|
1477 |
int cOutputOss::Output(const unsigned char *Data, int Len, bool SOF)
|
nathan@0
|
1478 |
{
|
nathan@0
|
1479 |
if(fd>=0) {
|
nathan@0
|
1480 |
int n=0;
|
nathan@0
|
1481 |
if(SOF) {
|
nathan@0
|
1482 |
struct FrameHeader *fh=(struct FrameHeader *)Data;
|
nathan@0
|
1483 |
if(fh->samplerate!=outSr) Reset(fh->samplerate);
|
nathan@0
|
1484 |
n=FHS;
|
nathan@0
|
1485 |
Data+=n; Len-=n;
|
nathan@0
|
1486 |
}
|
nathan@0
|
1487 |
int r=write(fd,Data,Len);
|
nathan@0
|
1488 |
if(r<0 && !FATALERRNO) r=0;
|
nathan@0
|
1489 |
if(r>=0) return n+r;
|
nathan@0
|
1490 |
}
|
nathan@0
|
1491 |
return -1;
|
nathan@0
|
1492 |
}
|
nathan@0
|
1493 |
|
nathan@0
|
1494 |
bool cOutputOss::Poll(void)
|
nathan@0
|
1495 |
{
|
nathan@0
|
1496 |
return fd>=0 ? poll.Poll(500) : false;
|
nathan@0
|
1497 |
}
|
nathan@0
|
1498 |
|
nathan@0
|
1499 |
void cOutputOss::Play(void)
|
nathan@0
|
1500 |
{
|
nathan@0
|
1501 |
}
|
nathan@0
|
1502 |
|
nathan@0
|
1503 |
void cOutputOss::Pause(void)
|
nathan@0
|
1504 |
{
|
nathan@0
|
1505 |
CHECK(ioctl(fd,SNDCTL_DSP_POST,0));
|
nathan@0
|
1506 |
}
|
nathan@0
|
1507 |
|
nathan@0
|
1508 |
#endif
|
nathan@0
|
1509 |
|
nathan@0
|
1510 |
// --- cMP3Player --------------------------------------------------------------
|
nathan@0
|
1511 |
|
nathan@0
|
1512 |
cMP3Player::cMP3Player()
|
nathan@0
|
1513 |
:cPlayer(MP3Setup.BackgrMode==1 ? pmAudioOnly : pmAudioOnlyBlack)
|
nathan@0
|
1514 |
{
|
nathan@0
|
1515 |
active=true; started=false; isStream=false;
|
nathan@0
|
1516 |
ringBuffer=new cRingBufferFrame(MP3BUFSIZE);
|
nathan@0
|
1517 |
rframe=0; pframe=0; decoder=0; output=0;
|
nathan@0
|
1518 |
playMode=pmStartup; state=msStop;
|
nathan@0
|
1519 |
playindex=total=0;
|
nathan@0
|
1520 |
}
|
nathan@0
|
1521 |
|
nathan@0
|
1522 |
cMP3Player::~cMP3Player()
|
nathan@0
|
1523 |
{
|
nathan@0
|
1524 |
Detach();
|
nathan@0
|
1525 |
delete ringBuffer;
|
nathan@0
|
1526 |
}
|
nathan@0
|
1527 |
|
nathan@0
|
1528 |
void cMP3Player::Activate(bool On)
|
nathan@0
|
1529 |
{
|
nathan@0
|
1530 |
if(On) {
|
nathan@0
|
1531 |
d(printf("mp3: player active true requested...\n"))
|
nathan@0
|
1532 |
if(!started) {
|
nathan@0
|
1533 |
playMode=pmStartup; Start(); started=true;
|
nathan@0
|
1534 |
playModeMutex.Lock();
|
nathan@0
|
1535 |
WaitPlayMode(pmStartup,true); // wait for the decoder to become ready
|
nathan@0
|
1536 |
playModeMutex.Unlock();
|
nathan@0
|
1537 |
Lock();
|
nathan@0
|
1538 |
Play();
|
nathan@0
|
1539 |
Unlock();
|
nathan@0
|
1540 |
}
|
nathan@0
|
1541 |
d(printf("mp3: player active true done\n"))
|
nathan@0
|
1542 |
}
|
nathan@0
|
1543 |
else if(started && active) {
|
nathan@0
|
1544 |
d(printf("mp3: player active false requested...\n"))
|
nathan@0
|
1545 |
Lock(); StopPlay(); Unlock();
|
nathan@0
|
1546 |
active=false;
|
nathan@0
|
1547 |
SetPlayMode(pmStartup);
|
nathan@0
|
1548 |
Cancel(2);
|
nathan@0
|
1549 |
d(printf("mp3: player active false done\n"))
|
nathan@0
|
1550 |
}
|
nathan@0
|
1551 |
}
|
nathan@0
|
1552 |
|
nathan@0
|
1553 |
void cMP3Player::SetPlayMode(ePlayMode mode)
|
nathan@0
|
1554 |
{
|
nathan@0
|
1555 |
playModeMutex.Lock();
|
nathan@0
|
1556 |
if(mode!=playMode) {
|
nathan@0
|
1557 |
playMode=mode;
|
nathan@0
|
1558 |
dm(printf("mp3: setting mode=%d (pid=%d)\n",mode,getpid()))
|
nathan@0
|
1559 |
playModeCond.Broadcast();
|
nathan@0
|
1560 |
}
|
nathan@0
|
1561 |
playModeMutex.Unlock();
|
nathan@0
|
1562 |
}
|
nathan@0
|
1563 |
|
nathan@0
|
1564 |
void cMP3Player::WaitPlayMode(ePlayMode mode, bool inv)
|
nathan@0
|
1565 |
{
|
nathan@0
|
1566 |
// must be called with playModeMutex LOCKED !!!
|
nathan@0
|
1567 |
|
nathan@0
|
1568 |
while(active && ((!inv && mode!=playMode) || (inv && mode==playMode))) {
|
nathan@0
|
1569 |
dm(printf("mp3: entering wait for mode%s%d with mode=%d (pid=%d)\n",inv?"!=":"==",mode,playMode,getpid()))
|
nathan@0
|
1570 |
playModeCond.Wait(playModeMutex);
|
nathan@0
|
1571 |
dm(printf("mp3: returning from wait with mode=%d (pid=%d)\n",playMode,getpid()))
|
nathan@0
|
1572 |
}
|
nathan@0
|
1573 |
}
|
nathan@0
|
1574 |
|
nathan@0
|
1575 |
void cMP3Player::Action(void)
|
nathan@0
|
1576 |
{
|
nathan@0
|
1577 |
cSong *playing=0;
|
nathan@0
|
1578 |
struct mad_pcm *pcm=0;
|
nathan@0
|
1579 |
cResample resample[2];
|
nathan@0
|
1580 |
unsigned int nsamples[2];
|
nathan@0
|
1581 |
const mad_fixed_t *data[2];
|
nathan@0
|
1582 |
cLevel level;
|
nathan@0
|
1583 |
cNormalize norm;
|
nathan@0
|
1584 |
bool haslevel=false;
|
nathan@0
|
1585 |
const unsigned char *p=0;
|
nathan@0
|
1586 |
int pc=0, readindex=0;
|
nathan@0
|
1587 |
bool imageValid=true;
|
nathan@0
|
1588 |
int imageCheck=0;
|
nathan@0
|
1589 |
#ifdef DEBUG
|
nathan@0
|
1590 |
int beat=0;
|
nathan@0
|
1591 |
#endif
|
nathan@0
|
1592 |
#ifdef DEBUG_DELAY
|
nathan@0
|
1593 |
int lastwrite=0;
|
nathan@0
|
1594 |
#endif
|
nathan@0
|
1595 |
|
nathan@0
|
1596 |
dsyslog("mp3: player thread started (pid=%d)", getpid());
|
nathan@0
|
1597 |
state=msStop;
|
nathan@0
|
1598 |
SetPlayMode(pmStopped);
|
nathan@0
|
1599 |
|
nathan@0
|
1600 |
delete output; output=0;
|
nathan@0
|
1601 |
#ifdef WITH_OSS
|
nathan@0
|
1602 |
if(MP3Setup.AudioOutMode==AUDIOOUTMODE_OSS) output=new cOutputOss(this);
|
nathan@0
|
1603 |
#endif
|
nathan@0
|
1604 |
if(MP3Setup.AudioOutMode==AUDIOOUTMODE_DVB) output=new cOutputDvb(this);
|
nathan@0
|
1605 |
if(!output) {
|
nathan@0
|
1606 |
d(printf("mp3: audiooutmode mismatch or no output driver\n"))
|
nathan@0
|
1607 |
esyslog("ERROR: no audio output driver. balling out");
|
nathan@0
|
1608 |
goto abort;
|
nathan@0
|
1609 |
}
|
nathan@0
|
1610 |
|
nathan@0
|
1611 |
while(active) {
|
nathan@0
|
1612 |
#ifdef DEBUG
|
nathan@0
|
1613 |
{
|
nathan@0
|
1614 |
int now=time(0);
|
nathan@0
|
1615 |
if(now>=beat) {
|
nathan@0
|
1616 |
int avail=ringBuffer->Available();
|
nathan@0
|
1617 |
printf("mp3: heartbeat buffer=%d now=%d\n",avail,now&4095);
|
nathan@0
|
1618 |
//output->Stats(); if(haslevel) norm.Stats();
|
nathan@0
|
1619 |
beat=now+(avail>(MP3BUFSIZE*10/100) ? (avail<(MP3BUFSIZE*50/100) ? 2 : 20) : 1);
|
nathan@0
|
1620 |
}
|
nathan@0
|
1621 |
}
|
nathan@0
|
1622 |
#endif
|
nathan@0
|
1623 |
|
nathan@0
|
1624 |
Lock();
|
nathan@0
|
1625 |
|
nathan@0
|
1626 |
next:
|
nathan@0
|
1627 |
if(!pframe && playing && !imageValid && imageCheck<time_ms()) {
|
nathan@0
|
1628 |
unsigned char *mem;
|
nathan@0
|
1629 |
int len;
|
nathan@0
|
1630 |
imageCheck=time_ms()+250;
|
nathan@0
|
1631 |
imageValid=playing->Image(mem,len);
|
nathan@0
|
1632 |
if(mem) {
|
nathan@0
|
1633 |
if(playindex) SLEEP(80); // stillpicture ioctl freezes without this
|
nathan@0
|
1634 |
DeviceStillPicture(mem,len);
|
nathan@0
|
1635 |
free(mem);
|
nathan@0
|
1636 |
}
|
nathan@0
|
1637 |
}
|
nathan@0
|
1638 |
|
nathan@0
|
1639 |
bool SOF=false;
|
nathan@0
|
1640 |
if(!pframe && playMode==pmPlay) {
|
nathan@0
|
1641 |
pframe=ringBuffer->Get();
|
nathan@0
|
1642 |
if(pframe) {
|
nathan@0
|
1643 |
playindex=pframe->Index();
|
nathan@0
|
1644 |
p=pframe->Data();
|
nathan@0
|
1645 |
pc=pframe->Count();
|
nathan@0
|
1646 |
SOF=true;
|
nathan@0
|
1647 |
}
|
nathan@0
|
1648 |
}
|
nathan@0
|
1649 |
|
nathan@0
|
1650 |
if(pframe) {
|
nathan@0
|
1651 |
#ifdef DEBUG_DELAY
|
nathan@0
|
1652 |
{
|
nathan@0
|
1653 |
int now=time_ms();
|
nathan@0
|
1654 |
if(lastwrite && lastwrite<now-(DEBUG_DELAY+50))
|
nathan@0
|
1655 |
printf("mp3: write delayed %d ms\n",now-lastwrite);
|
nathan@0
|
1656 |
lastwrite=now;
|
nathan@0
|
1657 |
}
|
nathan@0
|
1658 |
#endif
|
nathan@0
|
1659 |
int w=output->Output(p,pc,SOF);
|
nathan@0
|
1660 |
if(w>0) {
|
nathan@0
|
1661 |
p+=w; pc-=w;
|
nathan@0
|
1662 |
if(pc<=0) {
|
nathan@0
|
1663 |
ringBuffer->Drop(pframe);
|
nathan@0
|
1664 |
pframe=0;
|
nathan@0
|
1665 |
goto next;
|
nathan@0
|
1666 |
}
|
nathan@0
|
1667 |
}
|
nathan@0
|
1668 |
else if(w<0 && FATALERRNO) {
|
nathan@0
|
1669 |
LOG_ERROR;
|
nathan@0
|
1670 |
d(printf("mp3: output failed: %s\n",strerror(errno)))
|
nathan@0
|
1671 |
Unlock();
|
nathan@0
|
1672 |
goto abort;
|
nathan@0
|
1673 |
}
|
nathan@0
|
1674 |
}
|
nathan@0
|
1675 |
|
nathan@0
|
1676 |
if(mgr->NewCurrent() && playMode==pmPlay && state!=msStart) {
|
nathan@0
|
1677 |
Empty();
|
nathan@0
|
1678 |
state=msRestart;
|
nathan@0
|
1679 |
d(printf("mp3: stale song change, restart.\n"))
|
nathan@0
|
1680 |
}
|
nathan@0
|
1681 |
|
nathan@0
|
1682 |
if(!rframe && playMode==pmPlay) {
|
nathan@0
|
1683 |
switch(state) {
|
nathan@0
|
1684 |
case msStart:
|
nathan@0
|
1685 |
d(printf("mp3: starting play\n"))
|
nathan@0
|
1686 |
mgr->Throttle(true);
|
nathan@0
|
1687 |
playindex=readindex=total=0;
|
nathan@0
|
1688 |
playing=mgr->Current();
|
nathan@0
|
1689 |
if(playing) {
|
nathan@0
|
1690 |
if((decoder=playing->Decoder()) && decoder->Start()) {
|
nathan@0
|
1691 |
isStream=decoder->IsStream(); levelgood=!isStream; haslevel=false;
|
nathan@0
|
1692 |
cSongInfo *si=playing->Info(true);
|
nathan@0
|
1693 |
if(si) {
|
nathan@0
|
1694 |
if(si->Level>0.0) {
|
nathan@0
|
1695 |
d(printf("mp3: found song level=%f peak=%f\n",si->Level,si->Peak))
|
nathan@0
|
1696 |
haslevel=true;
|
nathan@0
|
1697 |
norm.Init(si->Level,si->Peak);
|
nathan@0
|
1698 |
}
|
nathan@0
|
1699 |
if(si->HasInfo())
|
nathan@0
|
1700 |
total=SecondsToFrames(si->Total);
|
nathan@0
|
1701 |
}
|
nathan@0
|
1702 |
d(printf("mp3: isStream=%d levelgood=%d haslevel=%d\n",isStream,levelgood,haslevel))
|
nathan@0
|
1703 |
output->Init();
|
nathan@0
|
1704 |
level.Init();
|
nathan@0
|
1705 |
if(MP3Setup.BackgrMode==2) imageValid=false;
|
nathan@0
|
1706 |
state=msDecode;
|
nathan@0
|
1707 |
break;
|
nathan@0
|
1708 |
}
|
nathan@0
|
1709 |
else
|
nathan@0
|
1710 |
esyslog("ERROR: playlist entry %s is not a valid file",playing->Name());
|
nathan@0
|
1711 |
}
|
nathan@0
|
1712 |
else
|
nathan@0
|
1713 |
d(printf("mp3: no current on start play\n"))
|
nathan@0
|
1714 |
state=msEof;
|
nathan@0
|
1715 |
break;
|
nathan@0
|
1716 |
case msDecode:
|
nathan@0
|
1717 |
{
|
nathan@0
|
1718 |
#ifdef DEBUG_DELAY
|
nathan@0
|
1719 |
int now=time_ms();
|
nathan@0
|
1720 |
#endif
|
nathan@0
|
1721 |
struct Decode *ds=decoder->Decode();
|
nathan@0
|
1722 |
#ifdef DEBUG_DELAY
|
nathan@0
|
1723 |
now=time_ms()-now;
|
nathan@0
|
1724 |
if(now>DEBUG_DELAY) printf("mp3: decode delayed %d ms\n",now);
|
nathan@0
|
1725 |
#endif
|
nathan@0
|
1726 |
switch(ds->status) {
|
nathan@0
|
1727 |
case dsPlay:
|
nathan@0
|
1728 |
pcm=ds->pcm;
|
nathan@0
|
1729 |
readindex=ds->index;
|
nathan@0
|
1730 |
state=msNormalize;
|
nathan@0
|
1731 |
break;
|
nathan@0
|
1732 |
case dsSkip:
|
nathan@0
|
1733 |
case dsSoftError:
|
nathan@0
|
1734 |
// skipping, state unchanged, next decode
|
nathan@0
|
1735 |
break;
|
nathan@0
|
1736 |
case dsEof:
|
nathan@0
|
1737 |
if(!haslevel && levelgood) { // save level & peak to infocache on eof
|
nathan@0
|
1738 |
double l=level.GetLevel();
|
nathan@0
|
1739 |
if(l>0.0) {
|
nathan@0
|
1740 |
cSongInfo *si=decoder->SongInfo(false);
|
nathan@0
|
1741 |
cFileInfo *fi=decoder->FileInfo();
|
nathan@0
|
1742 |
if(si && fi) {
|
nathan@0
|
1743 |
si->Level=l;
|
nathan@0
|
1744 |
si->Peak=level.GetPeak();
|
nathan@0
|
1745 |
InfoCache.Cache(si,fi);
|
nathan@0
|
1746 |
}
|
nathan@0
|
1747 |
}
|
nathan@0
|
1748 |
}
|
nathan@0
|
1749 |
state=msEof;
|
nathan@0
|
1750 |
break;
|
nathan@0
|
1751 |
case dsOK:
|
nathan@0
|
1752 |
case dsError:
|
nathan@0
|
1753 |
state=msError;
|
nathan@0
|
1754 |
break;
|
nathan@0
|
1755 |
}
|
nathan@0
|
1756 |
break;
|
nathan@0
|
1757 |
}
|
nathan@0
|
1758 |
case msNormalize:
|
nathan@0
|
1759 |
if(!haslevel) { if(levelgood) level.GetPower(pcm); }
|
nathan@0
|
1760 |
else norm.AddGain(pcm);
|
nathan@0
|
1761 |
state=msResample;
|
nathan@0
|
1762 |
break;
|
nathan@0
|
1763 |
case msResample:
|
nathan@0
|
1764 |
#ifdef DEBUG
|
nathan@0
|
1765 |
{
|
nathan@0
|
1766 |
static unsigned int oldrate=0;
|
nathan@0
|
1767 |
if(oldrate!=pcm->samplerate) {
|
nathan@0
|
1768 |
printf("mp3: new input sample rate %d\n",pcm->samplerate);
|
nathan@0
|
1769 |
oldrate=pcm->samplerate;
|
nathan@0
|
1770 |
}
|
nathan@0
|
1771 |
}
|
nathan@0
|
1772 |
#endif
|
nathan@0
|
1773 |
nsamples[0]=nsamples[1]=pcm->length;
|
nathan@0
|
1774 |
data[0]=pcm->samples[0];
|
nathan@0
|
1775 |
data[1]=pcm->channels>1 ? pcm->samples[1]:0;
|
nathan@0
|
1776 |
|
nathan@0
|
1777 |
dvbSampleRate=output->SampleRate(pcm->samplerate);
|
nathan@0
|
1778 |
if(dvbSampleRate!=pcm->samplerate) {
|
nathan@0
|
1779 |
if(resample[0].SetInputRate(pcm->samplerate,dvbSampleRate)) {
|
nathan@0
|
1780 |
nsamples[0]=resample[0].ResampleBlock(nsamples[0],data[0]);
|
nathan@0
|
1781 |
data[0] =resample[0].Resampled();
|
nathan@0
|
1782 |
}
|
nathan@0
|
1783 |
if(data[1] && resample[1].SetInputRate(pcm->samplerate,dvbSampleRate)) {
|
nathan@0
|
1784 |
nsamples[1]=resample[1].ResampleBlock(nsamples[1],data[1]);
|
nathan@0
|
1785 |
data[1] =resample[1].Resampled();
|
nathan@0
|
1786 |
}
|
nathan@0
|
1787 |
}
|
nathan@0
|
1788 |
state=msOutput;
|
nathan@0
|
1789 |
break;
|
nathan@0
|
1790 |
case msOutput:
|
nathan@0
|
1791 |
if(nsamples[0]>0) rframe=output->MakeFrame(nsamples[0],data,readindex,dvbSampleRate);
|
nathan@0
|
1792 |
else state=msDecode;
|
nathan@0
|
1793 |
break;
|
nathan@0
|
1794 |
case msError:
|
nathan@0
|
1795 |
case msEof:
|
nathan@0
|
1796 |
d(printf("mp3: eof or error\n"))
|
nathan@0
|
1797 |
state=msWait;
|
nathan@0
|
1798 |
// fall through
|
nathan@0
|
1799 |
case msRestart:
|
nathan@0
|
1800 |
case msStop:
|
nathan@0
|
1801 |
d(printf("mp3: stopping play\n"))
|
nathan@0
|
1802 |
if(decoder) { decoder->Stop(); decoder=0; }
|
nathan@0
|
1803 |
mgr->Release(); playing=0; imageValid=true;
|
nathan@0
|
1804 |
levelgood=false;
|
nathan@0
|
1805 |
#ifdef DEBUG
|
nathan@0
|
1806 |
output->Stats(); if(haslevel) norm.Stats();
|
nathan@0
|
1807 |
#endif
|
nathan@0
|
1808 |
if(state==msStop) SetPlayMode(pmStopped);
|
nathan@0
|
1809 |
if(state==msRestart) state=msStart;
|
nathan@0
|
1810 |
break;
|
nathan@0
|
1811 |
case msWait:
|
nathan@0
|
1812 |
if(ringBuffer->Available()==0) {
|
nathan@0
|
1813 |
if(mgr->NextCurrent()) {
|
nathan@0
|
1814 |
d(printf("mp3: playing next\n"))
|
nathan@0
|
1815 |
state=msStart;
|
nathan@0
|
1816 |
}
|
nathan@0
|
1817 |
else {
|
nathan@0
|
1818 |
d(printf("mp3: end of playlist\n"))
|
nathan@0
|
1819 |
if(MP3Setup.AbortAtEOL) {
|
nathan@0
|
1820 |
active=false;
|
nathan@0
|
1821 |
d(printf("mp3: aborting player...\n"))
|
nathan@0
|
1822 |
}
|
nathan@0
|
1823 |
else d(printf("mp3: player idle...\n"))
|
nathan@0
|
1824 |
SetPlayMode(pmStopped);
|
nathan@0
|
1825 |
}
|
nathan@0
|
1826 |
}
|
nathan@0
|
1827 |
break;
|
nathan@0
|
1828 |
}
|
nathan@0
|
1829 |
}
|
nathan@0
|
1830 |
|
nathan@0
|
1831 |
if(rframe && ringBuffer->Put(rframe)) rframe=0;
|
nathan@0
|
1832 |
|
nathan@0
|
1833 |
Unlock();
|
nathan@0
|
1834 |
|
nathan@0
|
1835 |
if((rframe || state==msWait) && pframe) {
|
nathan@0
|
1836 |
mgr->Throttle(false);
|
nathan@0
|
1837 |
output->Poll();
|
nathan@0
|
1838 |
}
|
nathan@0
|
1839 |
else if(playMode!=pmPlay) {
|
nathan@0
|
1840 |
mgr->Throttle(false);
|
nathan@0
|
1841 |
if(!imageValid)
|
nathan@0
|
1842 |
SLEEP(100);
|
nathan@0
|
1843 |
else {
|
nathan@0
|
1844 |
playModeMutex.Lock();
|
nathan@0
|
1845 |
if(playMode!=pmPlay) WaitPlayMode(playMode,true);
|
nathan@0
|
1846 |
playModeMutex.Unlock();
|
nathan@0
|
1847 |
}
|
nathan@0
|
1848 |
#ifdef DEBUG_DELAY
|
nathan@0
|
1849 |
lastwrite=0;
|
nathan@0
|
1850 |
#endif
|
nathan@0
|
1851 |
}
|
nathan@0
|
1852 |
else if(state!=msWait && ringBuffer->Available()<(MP3BUFSIZE*50/100)) {
|
nathan@0
|
1853 |
mgr->Throttle(true);
|
nathan@0
|
1854 |
}
|
nathan@0
|
1855 |
}
|
nathan@0
|
1856 |
|
nathan@0
|
1857 |
abort:
|
nathan@0
|
1858 |
Lock();
|
nathan@0
|
1859 |
delete rframe;
|
nathan@0
|
1860 |
delete output; output=0;
|
nathan@0
|
1861 |
if(decoder) { decoder->Stop(); decoder=0; }
|
nathan@0
|
1862 |
mgr->Release(); playing=0;
|
nathan@0
|
1863 |
SetPlayMode(pmStopped);
|
nathan@0
|
1864 |
Unlock();
|
nathan@0
|
1865 |
active=false;
|
nathan@0
|
1866 |
|
nathan@0
|
1867 |
dsyslog("mp3: player thread ended (pid=%d)", getpid());
|
nathan@0
|
1868 |
}
|
nathan@0
|
1869 |
|
nathan@0
|
1870 |
void cMP3Player::Empty(void)
|
nathan@0
|
1871 |
{
|
nathan@0
|
1872 |
Lock();
|
nathan@0
|
1873 |
delete rframe; rframe=0; pframe=0;
|
nathan@0
|
1874 |
ringBuffer->Clear();
|
nathan@0
|
1875 |
DeviceClear();
|
nathan@0
|
1876 |
Unlock();
|
nathan@0
|
1877 |
}
|
nathan@0
|
1878 |
|
nathan@0
|
1879 |
void cMP3Player::StopPlay(void) // StopPlay() must be called in locked state!!!
|
nathan@0
|
1880 |
{
|
nathan@0
|
1881 |
if(playMode!=pmStopped) {
|
nathan@0
|
1882 |
Empty();
|
nathan@0
|
1883 |
state=msStop;
|
nathan@0
|
1884 |
SetPlayMode(pmPlay);
|
nathan@0
|
1885 |
Unlock(); // let the decode thread process the stop signal
|
nathan@0
|
1886 |
playModeMutex.Lock();
|
nathan@0
|
1887 |
WaitPlayMode(pmStopped,false);
|
nathan@0
|
1888 |
playModeMutex.Unlock();
|
nathan@0
|
1889 |
Lock();
|
nathan@0
|
1890 |
}
|
nathan@0
|
1891 |
}
|
nathan@0
|
1892 |
|
nathan@0
|
1893 |
void cMP3Player::Pause(void)
|
nathan@0
|
1894 |
{
|
nathan@0
|
1895 |
Lock();
|
nathan@0
|
1896 |
if(playMode==pmPaused) Play();
|
nathan@0
|
1897 |
else if(playMode==pmPlay && !isStream) {
|
nathan@0
|
1898 |
d(printf("mp3: pause\n"))
|
nathan@0
|
1899 |
if(output) output->Pause();
|
nathan@0
|
1900 |
SetPlayMode(pmPaused);
|
nathan@0
|
1901 |
}
|
nathan@0
|
1902 |
Unlock();
|
nathan@0
|
1903 |
}
|
nathan@0
|
1904 |
|
nathan@0
|
1905 |
void cMP3Player::Play(void)
|
nathan@0
|
1906 |
{
|
nathan@0
|
1907 |
Lock();
|
nathan@0
|
1908 |
if(playMode!=pmPlay) {
|
nathan@0
|
1909 |
d(printf("mp3: play\n"))
|
nathan@0
|
1910 |
if(playMode==pmStopped) state=msStart;
|
nathan@0
|
1911 |
if(output) output->Play();
|
nathan@0
|
1912 |
SetPlayMode(pmPlay);
|
nathan@0
|
1913 |
}
|
nathan@0
|
1914 |
Unlock();
|
nathan@0
|
1915 |
}
|
nathan@0
|
1916 |
|
nathan@0
|
1917 |
bool cMP3Player::PrevCheck(void)
|
nathan@0
|
1918 |
{
|
nathan@0
|
1919 |
bool res=false;
|
nathan@0
|
1920 |
Lock();
|
nathan@0
|
1921 |
if(playindex>=2000 && !isStream) {
|
nathan@0
|
1922 |
state=msRestart; res=true;
|
nathan@0
|
1923 |
Empty();
|
nathan@0
|
1924 |
d(printf("mp3: skip to start of song\n"))
|
nathan@0
|
1925 |
}
|
nathan@0
|
1926 |
Unlock();
|
nathan@0
|
1927 |
return res;
|
nathan@0
|
1928 |
}
|
nathan@0
|
1929 |
|
nathan@0
|
1930 |
void cMP3Player::SkipSeconds(int secs)
|
nathan@0
|
1931 |
{
|
nathan@0
|
1932 |
if(playMode!=pmStopped && !isStream) {
|
nathan@0
|
1933 |
Lock();
|
nathan@0
|
1934 |
d(printf("mp3: skip secs %d\n",secs))
|
nathan@0
|
1935 |
if(playMode==pmPaused) SetPlayMode(pmPlay);
|
nathan@0
|
1936 |
float bufsecs=(float)ringBuffer->Available() / (float)(dvbSampleRate*OUT_FACT);
|
nathan@0
|
1937 |
d(printf("mp3: ringbuffer available %f secs\n",bufsecs))
|
nathan@0
|
1938 |
if(secs>0 && bufsecs>=(float)secs) {
|
nathan@0
|
1939 |
// clear intermediate queue
|
nathan@0
|
1940 |
if(pframe) {
|
nathan@0
|
1941 |
ringBuffer->Drop(pframe);
|
nathan@0
|
1942 |
pframe=0;
|
nathan@0
|
1943 |
}
|
nathan@0
|
1944 |
DeviceClear();
|
nathan@0
|
1945 |
// skip inside ringbuffer
|
nathan@0
|
1946 |
int skipindex=playindex+secs*1000;
|
nathan@0
|
1947 |
d(printf("mp3: skipping play=%d skip=%d ...",playindex,skipindex))
|
nathan@0
|
1948 |
cFrame *f;
|
nathan@0
|
1949 |
do {
|
nathan@0
|
1950 |
f=ringBuffer->Get();
|
nathan@0
|
1951 |
if(f) {
|
nathan@0
|
1952 |
playindex=f->Index();
|
nathan@0
|
1953 |
ringBuffer->Drop(f);
|
nathan@0
|
1954 |
d(printf("*"))
|
nathan@0
|
1955 |
}
|
nathan@0
|
1956 |
} while(f && playindex<skipindex);
|
nathan@0
|
1957 |
d(printf("\nmp3: skipped play=%d skip=%d\n",playindex,skipindex))
|
nathan@0
|
1958 |
}
|
nathan@0
|
1959 |
else {
|
nathan@0
|
1960 |
if(decoder && decoder->Skip(secs,bufsecs)) levelgood=false;
|
nathan@0
|
1961 |
Empty();
|
nathan@0
|
1962 |
}
|
nathan@0
|
1963 |
Unlock();
|
nathan@0
|
1964 |
}
|
nathan@0
|
1965 |
}
|
nathan@0
|
1966 |
|
nathan@0
|
1967 |
bool cMP3Player::GetIndex(int &Current, int &Total, bool SnapToIFrame)
|
nathan@0
|
1968 |
{
|
nathan@0
|
1969 |
Current=SecondsToFrames(playindex/1000); Total=total;
|
nathan@0
|
1970 |
return total>=0;
|
nathan@0
|
1971 |
}
|
nathan@0
|
1972 |
|
nathan@0
|
1973 |
bool cMP3Player::GetReplayMode(bool &Play, bool &Forward, int &Speed)
|
nathan@0
|
1974 |
{
|
nathan@0
|
1975 |
Play=(playMode==pmPlay);
|
nathan@0
|
1976 |
Forward=true;
|
nathan@0
|
1977 |
Speed=-1;
|
nathan@0
|
1978 |
return true;
|
nathan@0
|
1979 |
}
|