| 1 |
/* |
| 2 |
* Copyright 2008-2011 Various Authors |
| 3 |
* Copyright 2007 Johannes Weißl |
| 4 |
* |
| 5 |
* This program is free software; you can redistribute it and/or |
| 6 |
* modify it under the terms of the GNU General Public License as |
| 7 |
* published by the Free Software Foundation; either version 2 of the |
| 8 |
* License, or (at your option) any later version. |
| 9 |
* |
| 10 |
* This program is distributed in the hope that it will be useful, but |
| 11 |
* WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 13 |
* General Public License for more details. |
| 14 |
* |
| 15 |
* You should have received a copy of the GNU General Public License |
| 16 |
* along with this program; if not, see <http://www.gnu.org/licenses/>. |
| 17 |
*/ |
| 18 |
|
| 19 |
#include "ip.h" |
| 20 |
#include "ape.h" |
| 21 |
#include "id3.h" |
| 22 |
#include "xmalloc.h" |
| 23 |
#include "read_wrapper.h" |
| 24 |
#include "debug.h" |
| 25 |
#include "buffer.h" |
| 26 |
#include "comment.h" |
| 27 |
|
| 28 |
#include <wavpack/wavpack.h> |
| 29 |
|
| 30 |
#include <errno.h> |
| 31 |
#include <stdio.h> |
| 32 |
#include <string.h> |
| 33 |
#include <sys/types.h> |
| 34 |
#include <sys/stat.h> |
| 35 |
#include <unistd.h> |
| 36 |
#include <fcntl.h> |
| 37 |
|
| 38 |
#define WV_CHANNEL_MAX 2 |
| 39 |
|
| 40 |
struct wavpack_file { |
| 41 |
int fd; |
| 42 |
off_t len; |
| 43 |
int push_back_byte; |
| 44 |
}; |
| 45 |
|
| 46 |
struct wavpack_private { |
| 47 |
WavpackContext *wpc; |
| 48 |
int32_t samples[CHUNK_SIZE * WV_CHANNEL_MAX]; |
| 49 |
struct wavpack_file wv_file; |
| 50 |
struct wavpack_file wvc_file; |
| 51 |
unsigned int has_wvc : 1; |
| 52 |
}; |
| 53 |
|
| 54 |
/* http://www.wavpack.com/lib_use.txt */ |
| 55 |
|
| 56 |
static int32_t read_bytes(void *data, void *ptr, int32_t count) |
| 57 |
{ |
| 58 |
struct wavpack_file *file = data; |
| 59 |
int32_t rc, n = 0; |
| 60 |
|
| 61 |
if (file->push_back_byte != EOF) { |
| 62 |
char *p = ptr; |
| 63 |
*p = (char) file->push_back_byte; |
| 64 |
ptr = p + 1; |
| 65 |
file->push_back_byte = EOF; |
| 66 |
count--; |
| 67 |
n++; |
| 68 |
} |
| 69 |
|
| 70 |
rc = read(file->fd, ptr, count); |
| 71 |
if (rc == -1) { |
| 72 |
d_print("error: %s\n", strerror(errno)); |
| 73 |
return 0; |
| 74 |
} |
| 75 |
if (rc == 0) { |
| 76 |
errno = 0; |
| 77 |
return 0; |
| 78 |
} |
| 79 |
return rc + n; |
| 80 |
} |
| 81 |
|
| 82 |
static uint32_t get_pos(void *data) |
| 83 |
{ |
| 84 |
struct wavpack_file *file = data; |
| 85 |
|
| 86 |
return lseek(file->fd, 0, SEEK_CUR); |
| 87 |
} |
| 88 |
|
| 89 |
static int set_pos_rel(void *data, int32_t delta, int mode) |
| 90 |
{ |
| 91 |
struct wavpack_file *file = data; |
| 92 |
|
| 93 |
if (lseek(file->fd, delta, mode) == -1) |
| 94 |
return -1; |
| 95 |
|
| 96 |
file->push_back_byte = EOF; |
| 97 |
return 0; |
| 98 |
} |
| 99 |
|
| 100 |
static int set_pos_abs(void *data, uint32_t pos) |
| 101 |
{ |
| 102 |
return set_pos_rel(data, pos, SEEK_SET); |
| 103 |
} |
| 104 |
|
| 105 |
static int push_back_byte(void *data, int c) |
| 106 |
{ |
| 107 |
struct wavpack_file *file = data; |
| 108 |
|
| 109 |
if (file->push_back_byte != EOF) { |
| 110 |
d_print("error: only one byte push back possible!\n"); |
| 111 |
return EOF; |
| 112 |
} |
| 113 |
file->push_back_byte = c; |
| 114 |
return c; |
| 115 |
} |
| 116 |
|
| 117 |
static uint32_t get_length(void *data) |
| 118 |
{ |
| 119 |
struct wavpack_file *file = data; |
| 120 |
return file->len; |
| 121 |
} |
| 122 |
|
| 123 |
static int can_seek(void *data) |
| 124 |
{ |
| 125 |
struct wavpack_file *file = data; |
| 126 |
return file->len != -1; |
| 127 |
} |
| 128 |
|
| 129 |
static int32_t write_bytes(void *data, void *ptr, int32_t count) |
| 130 |
{ |
| 131 |
/* we shall not write any bytes */ |
| 132 |
return 0; |
| 133 |
} |
| 134 |
|
| 135 |
|
| 136 |
/* |
| 137 |
* typedef struct { |
| 138 |
* int32_t (*read_bytes)(void *id, void *data, int32_t bcount); |
| 139 |
* uint32_t (*get_pos)(void *id); |
| 140 |
* int (*set_pos_abs)(void *id, uint32_t pos); |
| 141 |
* int (*set_pos_rel)(void *id, int32_t delta, int mode); |
| 142 |
* int (*push_back_byte)(void *id, int c); |
| 143 |
* uint32_t (*get_length)(void *id); |
| 144 |
* int (*can_seek)(void *id); |
| 145 |
* |
| 146 |
* // this callback is for writing edited tags only |
| 147 |
* int32_t (*write_bytes)(void *id, void *data, int32_t bcount); |
| 148 |
* } WavpackStreamReader; |
| 149 |
*/ |
| 150 |
static WavpackStreamReader callbacks = { |
| 151 |
.read_bytes = read_bytes, |
| 152 |
.get_pos = get_pos, |
| 153 |
.set_pos_abs = set_pos_abs, |
| 154 |
.set_pos_rel = set_pos_rel, |
| 155 |
.push_back_byte = push_back_byte, |
| 156 |
.get_length = get_length, |
| 157 |
.can_seek = can_seek, |
| 158 |
.write_bytes = write_bytes |
| 159 |
}; |
| 160 |
|
| 161 |
static int wavpack_open(struct input_plugin_data *ip_data) |
| 162 |
{ |
| 163 |
struct wavpack_private *priv; |
| 164 |
struct stat st; |
| 165 |
char msg[80]; |
| 166 |
int channel_mask = 0; |
| 167 |
|
| 168 |
const struct wavpack_private priv_init = { |
| 169 |
.wv_file = { |
| 170 |
.fd = ip_data->fd, |
| 171 |
.push_back_byte = EOF |
| 172 |
} |
| 173 |
}; |
| 174 |
|
| 175 |
priv = xnew(struct wavpack_private, 1); |
| 176 |
*priv = priv_init; |
| 177 |
if (!ip_data->remote && fstat(ip_data->fd, &st) == 0) { |
| 178 |
char *filename_wvc; |
| 179 |
|
| 180 |
priv->wv_file.len = st.st_size; |
| 181 |
|
| 182 |
filename_wvc = xnew(char, strlen(ip_data->filename) + 2); |
| 183 |
sprintf(filename_wvc, "%sc", ip_data->filename); |
| 184 |
if (stat(filename_wvc, &st) == 0) { |
| 185 |
priv->wvc_file.fd = open(filename_wvc, O_RDONLY); |
| 186 |
if (priv->wvc_file.fd != -1) { |
| 187 |
priv->wvc_file.len = st.st_size; |
| 188 |
priv->wvc_file.push_back_byte = EOF; |
| 189 |
priv->has_wvc = 1; |
| 190 |
d_print("use correction file: %s\n", filename_wvc); |
| 191 |
} |
| 192 |
} |
| 193 |
free(filename_wvc); |
| 194 |
} else |
| 195 |
priv->wv_file.len = -1; |
| 196 |
ip_data->private = priv; |
| 197 |
|
| 198 |
*msg = '\0'; |
| 199 |
|
| 200 |
priv->wpc = WavpackOpenFileInputEx(&callbacks, &priv->wv_file, |
| 201 |
priv->has_wvc ? &priv->wvc_file : NULL, msg, |
| 202 |
OPEN_NORMALIZE, 0); |
| 203 |
|
| 204 |
if (!priv->wpc) { |
| 205 |
d_print("WavpackOpenFileInputEx failed: %s\n", msg); |
| 206 |
free(priv); |
| 207 |
return -IP_ERROR_FILE_FORMAT; |
| 208 |
} |
| 209 |
|
| 210 |
ip_data->sf = sf_rate(WavpackGetSampleRate(priv->wpc)) |
| 211 |
| sf_channels(WavpackGetReducedChannels(priv->wpc)) |
| 212 |
| sf_bits(WavpackGetBitsPerSample(priv->wpc)) |
| 213 |
| sf_signed(1); |
| 214 |
#if CUR_STREAM_VERS > 0x404 |
| 215 |
channel_mask = WavpackGetChannelMask(priv->wpc); |
| 216 |
#endif |
| 217 |
channel_map_init_waveex(sf_get_channels(ip_data->sf), channel_mask, ip_data->channel_map); |
| 218 |
return 0; |
| 219 |
} |
| 220 |
|
| 221 |
static int wavpack_close(struct input_plugin_data *ip_data) |
| 222 |
{ |
| 223 |
struct wavpack_private *priv; |
| 224 |
|
| 225 |
priv = ip_data->private; |
| 226 |
priv->wpc = WavpackCloseFile(priv->wpc); |
| 227 |
if (priv->has_wvc) |
| 228 |
close(priv->wvc_file.fd); |
| 229 |
free(priv); |
| 230 |
ip_data->private = NULL; |
| 231 |
return 0; |
| 232 |
} |
| 233 |
|
| 234 |
/* from wv_engine.cpp (C) 2006 by Peter Lemenkov <lemenkov@newmail.ru> */ |
| 235 |
static char *format_samples(int bps, char *dst, int32_t *src, uint32_t count) |
| 236 |
{ |
| 237 |
int32_t temp; |
| 238 |
|
| 239 |
switch (bps) { |
| 240 |
case 1: |
| 241 |
while (count--) |
| 242 |
*dst++ = *src++ + 128; |
| 243 |
break; |
| 244 |
case 2: |
| 245 |
while (count--) { |
| 246 |
*dst++ = (char) (temp = *src++); |
| 247 |
*dst++ = (char) (temp >> 8); |
| 248 |
} |
| 249 |
break; |
| 250 |
case 3: |
| 251 |
while (count--) { |
| 252 |
*dst++ = (char) (temp = *src++); |
| 253 |
*dst++ = (char) (temp >> 8); |
| 254 |
*dst++ = (char) (temp >> 16); |
| 255 |
} |
| 256 |
break; |
| 257 |
case 4: |
| 258 |
while (count--) { |
| 259 |
*dst++ = (char) (temp = *src++); |
| 260 |
*dst++ = (char) (temp >> 8); |
| 261 |
*dst++ = (char) (temp >> 16); |
| 262 |
*dst++ = (char) (temp >> 24); |
| 263 |
} |
| 264 |
break; |
| 265 |
} |
| 266 |
|
| 267 |
return dst; |
| 268 |
} |
| 269 |
|
| 270 |
static int wavpack_read(struct input_plugin_data *ip_data, char *buffer, int count) |
| 271 |
{ |
| 272 |
struct wavpack_private *priv; |
| 273 |
int rc, bps, sample_count, channels; |
| 274 |
|
| 275 |
priv = ip_data->private; |
| 276 |
channels = sf_get_channels(ip_data->sf); |
| 277 |
bps = WavpackGetBytesPerSample(priv->wpc); |
| 278 |
|
| 279 |
sample_count = count / bps; |
| 280 |
|
| 281 |
rc = WavpackUnpackSamples(priv->wpc, priv->samples, sample_count / channels); |
| 282 |
format_samples(bps, buffer, priv->samples, rc * channels); |
| 283 |
return rc * channels * bps; |
| 284 |
} |
| 285 |
|
| 286 |
static int wavpack_seek(struct input_plugin_data *ip_data, double offset) |
| 287 |
{ |
| 288 |
struct wavpack_private *priv = ip_data->private; |
| 289 |
|
| 290 |
if (!WavpackSeekSample(priv->wpc, WavpackGetSampleRate(priv->wpc) * offset)) |
| 291 |
return -IP_ERROR_INTERNAL; |
| 292 |
return 0; |
| 293 |
} |
| 294 |
|
| 295 |
static int wavpack_read_comments(struct input_plugin_data *ip_data, |
| 296 |
struct keyval **comments) |
| 297 |
{ |
| 298 |
struct id3tag id3; |
| 299 |
APETAG(ape); |
| 300 |
GROWING_KEYVALS(c); |
| 301 |
int fd, rc, save, i; |
| 302 |
|
| 303 |
fd = open(ip_data->filename, O_RDONLY); |
| 304 |
if (fd == -1) |
| 305 |
return -1; |
| 306 |
d_print("filename: %s\n", ip_data->filename); |
| 307 |
|
| 308 |
id3_init(&id3); |
| 309 |
rc = id3_read_tags(&id3, fd, ID3_V1); |
| 310 |
save = errno; |
| 311 |
close(fd); |
| 312 |
errno = save; |
| 313 |
if (rc) { |
| 314 |
if (rc == -1) { |
| 315 |
d_print("error: %s\n", strerror(errno)); |
| 316 |
return -1; |
| 317 |
} |
| 318 |
d_print("corrupted tag?\n"); |
| 319 |
goto next; |
| 320 |
} |
| 321 |
|
| 322 |
for (i = 0; i < NUM_ID3_KEYS; i++) { |
| 323 |
char *val = id3_get_comment(&id3, i); |
| 324 |
if (val) |
| 325 |
comments_add(&c, id3_key_names[i], val); |
| 326 |
} |
| 327 |
|
| 328 |
next: |
| 329 |
id3_free(&id3); |
| 330 |
|
| 331 |
rc = ape_read_tags(&ape, ip_data->fd, 1); |
| 332 |
if (rc < 0) |
| 333 |
goto out; |
| 334 |
|
| 335 |
for (i = 0; i < rc; i++) { |
| 336 |
char *k, *v; |
| 337 |
k = ape_get_comment(&ape, &v); |
| 338 |
if (!k) |
| 339 |
break; |
| 340 |
comments_add(&c, k, v); |
| 341 |
free(k); |
| 342 |
} |
| 343 |
|
| 344 |
out: |
| 345 |
ape_free(&ape); |
| 346 |
|
| 347 |
keyvals_terminate(&c); |
| 348 |
*comments = c.keyvals; |
| 349 |
return 0; |
| 350 |
} |
| 351 |
|
| 352 |
static int wavpack_duration(struct input_plugin_data *ip_data) |
| 353 |
{ |
| 354 |
struct wavpack_private *priv; |
| 355 |
int duration; |
| 356 |
|
| 357 |
priv = ip_data->private; |
| 358 |
duration = WavpackGetNumSamples(priv->wpc) / |
| 359 |
WavpackGetSampleRate(priv->wpc); |
| 360 |
|
| 361 |
return duration; |
| 362 |
} |
| 363 |
|
| 364 |
static long wavpack_bitrate(struct input_plugin_data *ip_data) |
| 365 |
{ |
| 366 |
struct wavpack_private *priv = ip_data->private; |
| 367 |
double bitrate = WavpackGetAverageBitrate(priv->wpc, 1); |
| 368 |
if (!bitrate) |
| 369 |
return -IP_ERROR_FUNCTION_NOT_SUPPORTED; |
| 370 |
return (long) (bitrate + 0.5); |
| 371 |
} |
| 372 |
|
| 373 |
static long wavpack_current_bitrate(struct input_plugin_data *ip_data) |
| 374 |
{ |
| 375 |
struct wavpack_private *priv = ip_data->private; |
| 376 |
return WavpackGetInstantBitrate(priv->wpc); |
| 377 |
} |
| 378 |
|
| 379 |
static char *wavpack_codec(struct input_plugin_data *ip_data) |
| 380 |
{ |
| 381 |
return xstrdup("wavpack"); |
| 382 |
} |
| 383 |
|
| 384 |
static char *wavpack_codec_profile(struct input_plugin_data *ip_data) |
| 385 |
{ |
| 386 |
struct wavpack_private *priv = ip_data->private; |
| 387 |
int m = WavpackGetMode(priv->wpc); |
| 388 |
char buf[32]; |
| 389 |
|
| 390 |
buf[0] = '\0'; |
| 391 |
|
| 392 |
if (m & MODE_FAST) |
| 393 |
strcat(buf, "fast"); |
| 394 |
#ifdef MODE_VERY_HIGH |
| 395 |
else if (m & MODE_VERY_HIGH) |
| 396 |
strcat(buf, "very high"); |
| 397 |
#endif |
| 398 |
else if (m & MODE_HIGH) |
| 399 |
strcat(buf, "high"); |
| 400 |
else |
| 401 |
strcat(buf, "normal"); |
| 402 |
|
| 403 |
if (m & MODE_HYBRID) |
| 404 |
strcat(buf, " hybrid"); |
| 405 |
|
| 406 |
#ifdef MODE_XMODE |
| 407 |
if ((m & MODE_EXTRA) && (m & MODE_XMODE)) { |
| 408 |
char xmode[] = " x0"; |
| 409 |
xmode[2] = ((m & MODE_XMODE) >> 12) + '0'; |
| 410 |
strcat(buf, xmode); |
| 411 |
} |
| 412 |
#endif |
| 413 |
|
| 414 |
return xstrdup(buf); |
| 415 |
} |
| 416 |
|
| 417 |
const struct input_plugin_ops ip_ops = { |
| 418 |
.open = wavpack_open, |
| 419 |
.close = wavpack_close, |
| 420 |
.read = wavpack_read, |
| 421 |
.seek = wavpack_seek, |
| 422 |
.read_comments = wavpack_read_comments, |
| 423 |
.duration = wavpack_duration, |
| 424 |
.bitrate = wavpack_bitrate, |
| 425 |
.bitrate_current = wavpack_current_bitrate, |
| 426 |
.codec = wavpack_codec, |
| 427 |
.codec_profile = wavpack_codec_profile |
| 428 |
}; |
| 429 |
|
| 430 |
const int ip_priority = 50; |
| 431 |
const char * const ip_extensions[] = { "wv", NULL }; |
| 432 |
const char * const ip_mime_types[] = { "audio/x-wavpack", NULL }; |
| 433 |
const char * const ip_options[] = { NULL }; |