|
1 /* |
|
2 * MP3/MPlayer plugin to VDR (C++) |
|
3 * |
|
4 * (C) 2001-2005 Stefan Huelswitt <s.huelswitt@gmx.de> |
|
5 * |
|
6 * This code is free software; you can redistribute it and/or |
|
7 * modify it under the terms of the GNU General Public License |
|
8 * as published by the Free Software Foundation; either version 2 |
|
9 * of the License, or (at your option) any later version. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, |
|
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 * GNU General Public License for more details. |
|
15 * |
|
16 * You should have received a copy of the GNU General Public License |
|
17 * along with this program; if not, write to the Free Software |
|
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|
19 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html |
|
20 */ |
|
21 |
|
22 #ifdef HAVE_VORBISFILE |
|
23 |
|
24 #include <stdlib.h> |
|
25 #include <stdio.h> |
|
26 #include <errno.h> |
|
27 |
|
28 #include "common.h" |
|
29 #include "decoder-ogg.h" |
|
30 |
|
31 // --- cOggFile ---------------------------------------------------------------- |
|
32 |
|
33 cOggFile::cOggFile(const char *Filename) |
|
34 :cFileInfo(Filename) |
|
35 { |
|
36 canSeek=opened=false; |
|
37 } |
|
38 |
|
39 cOggFile::~cOggFile() |
|
40 { |
|
41 Close(); |
|
42 } |
|
43 |
|
44 bool cOggFile::Open(bool log) |
|
45 { |
|
46 if(opened) { |
|
47 if(canSeek) return (Seek()>=0); |
|
48 return true; |
|
49 } |
|
50 |
|
51 if(FileInfo(log)) { |
|
52 FILE *f=fopen(Filename,"r"); |
|
53 if(f) { |
|
54 int r=ov_open(f,&vf,0,0); |
|
55 if(!r) { |
|
56 canSeek=(ov_seekable(&vf)!=0); |
|
57 opened=true; |
|
58 } |
|
59 else { |
|
60 fclose(f); |
|
61 if(log) Error("open",r); |
|
62 } |
|
63 } |
|
64 else if(log) { esyslog("ERROR: failed to open file %s: %s",Filename,strerror(errno)); } |
|
65 } |
|
66 return opened; |
|
67 } |
|
68 |
|
69 void cOggFile::Close(void) |
|
70 { |
|
71 if(opened) { ov_clear(&vf); opened=false; } |
|
72 } |
|
73 |
|
74 void cOggFile::Error(const char *action, const int err) |
|
75 { |
|
76 char *errstr; |
|
77 switch(err) { |
|
78 case OV_FALSE: errstr="false/no data available"; break; |
|
79 case OV_EOF: errstr="EOF"; break; |
|
80 case OV_HOLE: errstr="missing or corrupted data"; break; |
|
81 case OV_EREAD: errstr="read error"; break; |
|
82 case OV_EFAULT: errstr="internal error"; break; |
|
83 case OV_EIMPL: errstr="unimplemented feature"; break; |
|
84 case OV_EINVAL: errstr="invalid argument"; break; |
|
85 case OV_ENOTVORBIS: errstr="no Ogg Vorbis stream"; break; |
|
86 case OV_EBADHEADER: errstr="corrupted Ogg Vorbis stream"; break; |
|
87 case OV_EVERSION: errstr="unsupported bitstream version"; break; |
|
88 case OV_ENOTAUDIO: errstr="ENOTAUDIO"; break; |
|
89 case OV_EBADPACKET: errstr="EBADPACKET"; break; |
|
90 case OV_EBADLINK: errstr="corrupted link"; break; |
|
91 case OV_ENOSEEK: errstr="stream not seekable"; break; |
|
92 default: errstr="unspecified error"; break; |
|
93 } |
|
94 esyslog("ERROR: vorbisfile %s failed on %s: %s",action,Filename,errstr); |
|
95 } |
|
96 |
|
97 long long cOggFile::IndexMs(void) |
|
98 { |
|
99 double p=ov_time_tell(&vf); |
|
100 if(p<0.0) p=0.0; |
|
101 return (long long)(p*1000.0); |
|
102 } |
|
103 |
|
104 long long cOggFile::Seek(long long posMs, bool relativ) |
|
105 { |
|
106 if(relativ) posMs+=IndexMs(); |
|
107 int r=ov_time_seek(&vf,(double)posMs/1000.0); |
|
108 if(r) { |
|
109 Error("seek",r); |
|
110 return -1; |
|
111 } |
|
112 posMs=IndexMs(); |
|
113 return posMs; |
|
114 } |
|
115 |
|
116 int cOggFile::Stream(short *buffer, int samples) |
|
117 { |
|
118 int n; |
|
119 do { |
|
120 int stream; |
|
121 n=ov_read(&vf,(char *)buffer,samples*2,0,2,1,&stream); |
|
122 } while(n==OV_HOLE); |
|
123 if(n<0) Error("read",n); |
|
124 return (n/2); |
|
125 } |
|
126 |
|
127 // --- cOggInfo ---------------------------------------------------------------- |
|
128 |
|
129 cOggInfo::cOggInfo(cOggFile *File) |
|
130 { |
|
131 file=File; |
|
132 } |
|
133 |
|
134 bool cOggInfo::Abort(bool result) |
|
135 { |
|
136 if(!keepOpen) file->Close(); |
|
137 return result; |
|
138 } |
|
139 |
|
140 bool cOggInfo::DoScan(bool KeepOpen) |
|
141 { |
|
142 keepOpen=KeepOpen; |
|
143 if(!file->Open()) return Abort(false); |
|
144 if(HasInfo()) return Abort(true); |
|
145 |
|
146 // check the infocache |
|
147 cCacheData *dat=InfoCache.Search(file); |
|
148 if(dat) { |
|
149 Set(dat); dat->Unlock(); |
|
150 if(!DecoderID) { |
|
151 DecoderID=DEC_OGG; |
|
152 InfoCache.Cache(this,file); |
|
153 } |
|
154 return Abort(true); |
|
155 } |
|
156 |
|
157 Clear(); |
|
158 |
|
159 vorbis_comment *vc=ov_comment(&file->vf,-1); |
|
160 if(vc) { |
|
161 for(int i=0 ; i<vc->comments ; i++) { |
|
162 const char *cc=vc->user_comments[i]; |
|
163 d(printf("ogg: comment%d='%s'\n",i,cc)) |
|
164 char *p=strchr(cc,'='); |
|
165 if(p) { |
|
166 const int len=p-cc; |
|
167 p++; |
|
168 if(!strncasecmp(cc,"TITLE",len)) { |
|
169 if(!Title) Title=strdup(p); |
|
170 } |
|
171 else if(!strncasecmp(cc,"ARTIST",len)) { |
|
172 if(!Artist) Artist=strdup(p); |
|
173 } |
|
174 else if(!strncasecmp(cc,"ALBUM",len)) { |
|
175 if(!Album) Album=strdup(p); |
|
176 } |
|
177 else if(!strncasecmp(cc,"YEAR",len)) { |
|
178 if(Year<0) { |
|
179 Year=atoi(p); |
|
180 if(Year<1800 || Year>2100) Year=-1; |
|
181 } |
|
182 } |
|
183 } |
|
184 } |
|
185 } |
|
186 if(!Title) FakeTitle(file->Filename); |
|
187 |
|
188 vorbis_info *vi=ov_info(&file->vf,-1); |
|
189 if(!vi) Abort(false); |
|
190 d(printf("ogg: info ch=%d srate=%ld brate_low=%ld brate_high=%ld brate_avg=%ld\n", |
|
191 vi->channels,vi->rate,vi->bitrate_lower,vi->bitrate_upper,vi->bitrate_nominal)) |
|
192 Channels=vi->channels; |
|
193 ChMode=Channels>1 ? 3:0; |
|
194 SampleFreq=vi->rate; |
|
195 if(vi->bitrate_upper>0 && vi->bitrate_lower>0) { |
|
196 Bitrate=vi->bitrate_lower; |
|
197 MaxBitrate=vi->bitrate_upper; |
|
198 } |
|
199 else |
|
200 Bitrate=vi->bitrate_nominal; |
|
201 |
|
202 Total=(int)ov_time_total(&file->vf,-1); |
|
203 Frames=-1; |
|
204 DecoderID=DEC_OGG; |
|
205 |
|
206 InfoDone(); |
|
207 InfoCache.Cache(this,file); |
|
208 return Abort(true); |
|
209 } |
|
210 |
|
211 // --- cOggDecoder ------------------------------------------------------------- |
|
212 |
|
213 cOggDecoder::cOggDecoder(const char *Filename) |
|
214 :cDecoder(Filename) |
|
215 ,file(Filename) |
|
216 ,info(&file) |
|
217 { |
|
218 pcm=0; |
|
219 } |
|
220 |
|
221 cOggDecoder::~cOggDecoder() |
|
222 { |
|
223 Clean(); |
|
224 } |
|
225 |
|
226 bool cOggDecoder::Valid(void) |
|
227 { |
|
228 bool res=false; |
|
229 if(TryLock()) { |
|
230 if(file.Open(false)) res=true; |
|
231 Unlock(); |
|
232 } |
|
233 return res; |
|
234 } |
|
235 |
|
236 cFileInfo *cOggDecoder::FileInfo(void) |
|
237 { |
|
238 cFileInfo *fi=0; |
|
239 if(file.HasInfo()) fi=&file; |
|
240 else if(TryLock()){ |
|
241 if(file.Open()) { fi=&file; file.Close(); } |
|
242 Unlock(); |
|
243 } |
|
244 return fi; |
|
245 } |
|
246 |
|
247 cSongInfo *cOggDecoder::SongInfo(bool get) |
|
248 { |
|
249 cSongInfo *si=0; |
|
250 if(info.HasInfo()) si=&info; |
|
251 else if(get && TryLock()) { |
|
252 if(info.DoScan(false)) si=&info; |
|
253 Unlock(); |
|
254 } |
|
255 return si; |
|
256 } |
|
257 |
|
258 cPlayInfo *cOggDecoder::PlayInfo(void) |
|
259 { |
|
260 if(playing) { |
|
261 pi.Index=index/1000; |
|
262 pi.Total=info.Total; |
|
263 return π |
|
264 } |
|
265 return 0; |
|
266 } |
|
267 |
|
268 void cOggDecoder::Init(void) |
|
269 { |
|
270 Clean(); |
|
271 pcm=new struct mad_pcm; |
|
272 index=0; |
|
273 } |
|
274 |
|
275 bool cOggDecoder::Clean(void) |
|
276 { |
|
277 playing=false; |
|
278 delete pcm; pcm=0; |
|
279 file.Close(); |
|
280 return false; |
|
281 } |
|
282 |
|
283 #define SF_SAMPLES (sizeof(pcm->samples[0])/sizeof(mad_fixed_t)) |
|
284 |
|
285 bool cOggDecoder::Start(void) |
|
286 { |
|
287 Lock(true); |
|
288 Init(); playing=true; |
|
289 if(file.Open() && info.DoScan(true)) { |
|
290 d(printf("ogg: open rate=%d channels=%d seek=%d\n", |
|
291 info.SampleFreq,info.Channels,file.CanSeek())) |
|
292 if(info.Channels<=2) { |
|
293 Unlock(); |
|
294 return true; |
|
295 } |
|
296 else esyslog("ERROR: cannot play ogg file %s: more than 2 channels",filename); |
|
297 } |
|
298 Clean(); |
|
299 Unlock(); |
|
300 return false; |
|
301 } |
|
302 |
|
303 bool cOggDecoder::Stop(void) |
|
304 { |
|
305 Lock(); |
|
306 if(playing) Clean(); |
|
307 Unlock(); |
|
308 return true; |
|
309 } |
|
310 |
|
311 struct Decode *cOggDecoder::Done(eDecodeStatus status) |
|
312 { |
|
313 ds.status=status; |
|
314 ds.index=index; |
|
315 ds.pcm=pcm; |
|
316 Unlock(); // release the lock from Decode() |
|
317 return &ds; |
|
318 } |
|
319 |
|
320 struct Decode *cOggDecoder::Decode(void) |
|
321 { |
|
322 Lock(); // this is released in Done() |
|
323 if(playing) { |
|
324 short framebuff[2*SF_SAMPLES]; |
|
325 int n=file.Stream(framebuff,SF_SAMPLES); |
|
326 if(n<0) return Done(dsError); |
|
327 if(n==0) return Done(dsEof); |
|
328 |
|
329 pcm->samplerate=info.SampleFreq; |
|
330 pcm->channels=info.Channels; |
|
331 n/=pcm->channels; |
|
332 pcm->length=n; |
|
333 index=file.IndexMs(); |
|
334 |
|
335 short *data=framebuff; |
|
336 mad_fixed_t *sam0=pcm->samples[0], *sam1=pcm->samples[1]; |
|
337 const int s=MAD_F_FRACBITS+1-(sizeof(short)*8); // shift value for mad_fixed conversion |
|
338 if(pcm->channels>1) { |
|
339 for(; n>0 ; n--) { |
|
340 *sam0++=(*data++) << s; |
|
341 *sam1++=(*data++) << s; |
|
342 } |
|
343 } |
|
344 else { |
|
345 for(; n>0 ; n--) |
|
346 *sam0++=(*data++) << s; |
|
347 } |
|
348 return Done(dsPlay); |
|
349 } |
|
350 return Done(dsError); |
|
351 } |
|
352 |
|
353 bool cOggDecoder::Skip(int Seconds, float bsecs) |
|
354 { |
|
355 Lock(); |
|
356 bool res=false; |
|
357 if(playing && file.CanSeek()) { |
|
358 float fsecs=(float)Seconds - bsecs; |
|
359 long long newpos=file.IndexMs()+(long long)(fsecs*1000.0); |
|
360 if(newpos<0) newpos=0; |
|
361 d(printf("ogg: skip: secs=%d fsecs=%f current=%lld new=%lld\n",Seconds,fsecs,file.IndexMs(),newpos)) |
|
362 |
|
363 newpos=file.Seek(newpos,false); |
|
364 if(newpos>=0) { |
|
365 index=file.IndexMs(); |
|
366 #ifdef DEBUG |
|
367 int i=index/1000; |
|
368 printf("ogg: skipping to %02d:%02d\n",i/60,i%60); |
|
369 #endif |
|
370 res=true; |
|
371 } |
|
372 } |
|
373 Unlock(); |
|
374 return res; |
|
375 } |
|
376 |
|
377 #endif //HAVE_VORBISFILE |