/* * Copyright 2008-2013 Various Authors * Copyright 2007 Johannes Weißl * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "../ip.h" #include "../ape.h" #include "../id3.h" #include "../xmalloc.h" #include "../read_wrapper.h" #include "../debug.h" #include "../buffer.h" #include "../comment.h" #include #include #include #include #include #include #include #include #define WV_CHANNEL_MAX 2 struct wavpack_file { int fd; off_t len; int push_back_byte; }; struct wavpack_private { WavpackContext *wpc; int32_t samples[CHUNK_SIZE * WV_CHANNEL_MAX]; struct wavpack_file wv_file; struct wavpack_file wvc_file; unsigned int has_wvc : 1; }; /* http://www.wavpack.com/lib_use.txt */ static int32_t read_bytes(void *data, void *ptr, int32_t count) { struct wavpack_file *file = data; int32_t rc, n = 0; if (file->push_back_byte != EOF) { char *p = ptr; *p = (char) file->push_back_byte; ptr = p + 1; file->push_back_byte = EOF; count--; n++; } rc = read(file->fd, ptr, count); if (rc == -1) { d_print("error: %s\n", strerror(errno)); return 0; } if (rc == 0) { errno = 0; return 0; } return rc + n; } static uint32_t get_pos(void *data) { struct wavpack_file *file = data; return lseek(file->fd, 0, SEEK_CUR); } static int set_pos_rel(void *data, int32_t delta, int mode) { struct wavpack_file *file = data; if (lseek(file->fd, delta, mode) == -1) return -1; file->push_back_byte = EOF; return 0; } static int set_pos_abs(void *data, uint32_t pos) { return set_pos_rel(data, pos, SEEK_SET); } static int push_back_byte(void *data, int c) { struct wavpack_file *file = data; if (file->push_back_byte != EOF) { d_print("error: only one byte push back possible!\n"); return EOF; } file->push_back_byte = c; return c; } static uint32_t get_length(void *data) { struct wavpack_file *file = data; return file->len; } static int can_seek(void *data) { struct wavpack_file *file = data; return file->len != -1; } static int32_t write_bytes(void *data, void *ptr, int32_t count) { /* we shall not write any bytes */ return 0; } /* * typedef struct { * int32_t (*read_bytes)(void *id, void *data, int32_t bcount); * uint32_t (*get_pos)(void *id); * int (*set_pos_abs)(void *id, uint32_t pos); * int (*set_pos_rel)(void *id, int32_t delta, int mode); * int (*push_back_byte)(void *id, int c); * uint32_t (*get_length)(void *id); * int (*can_seek)(void *id); * * // this callback is for writing edited tags only * int32_t (*write_bytes)(void *id, void *data, int32_t bcount); * } WavpackStreamReader; */ static WavpackStreamReader callbacks = { .read_bytes = read_bytes, .get_pos = get_pos, .set_pos_abs = set_pos_abs, .set_pos_rel = set_pos_rel, .push_back_byte = push_back_byte, .get_length = get_length, .can_seek = can_seek, .write_bytes = write_bytes }; static int wavpack_open(struct input_plugin_data *ip_data) { struct wavpack_private *priv; struct stat st; char msg[80]; int channel_mask = 0; const struct wavpack_private priv_init = { .wv_file = { .fd = ip_data->fd, .push_back_byte = EOF } }; priv = xnew(struct wavpack_private, 1); *priv = priv_init; if (!ip_data->remote && fstat(ip_data->fd, &st) == 0) { char *filename_wvc; priv->wv_file.len = st.st_size; filename_wvc = xnew(char, strlen(ip_data->filename) + 2); sprintf(filename_wvc, "%sc", ip_data->filename); if (stat(filename_wvc, &st) == 0) { priv->wvc_file.fd = open(filename_wvc, O_RDONLY); if (priv->wvc_file.fd != -1) { priv->wvc_file.len = st.st_size; priv->wvc_file.push_back_byte = EOF; priv->has_wvc = 1; d_print("use correction file: %s\n", filename_wvc); } } free(filename_wvc); } else priv->wv_file.len = -1; ip_data->private = priv; *msg = '\0'; priv->wpc = WavpackOpenFileInputEx(&callbacks, &priv->wv_file, priv->has_wvc ? &priv->wvc_file : NULL, msg, OPEN_NORMALIZE, 0); if (!priv->wpc) { d_print("WavpackOpenFileInputEx failed: %s\n", msg); free(priv); return -IP_ERROR_FILE_FORMAT; } ip_data->sf = sf_rate(WavpackGetSampleRate(priv->wpc)) | sf_channels(WavpackGetReducedChannels(priv->wpc)) | sf_bits(WavpackGetBitsPerSample(priv->wpc)) | sf_signed(1); channel_mask = WavpackGetChannelMask(priv->wpc); channel_map_init_waveex(sf_get_channels(ip_data->sf), channel_mask, ip_data->channel_map); return 0; } static int wavpack_close(struct input_plugin_data *ip_data) { struct wavpack_private *priv; priv = ip_data->private; priv->wpc = WavpackCloseFile(priv->wpc); if (priv->has_wvc) close(priv->wvc_file.fd); free(priv); ip_data->private = NULL; return 0; } /* from wv_engine.cpp (C) 2006 by Peter Lemenkov */ static char *format_samples(int bps, char *dst, int32_t *src, uint32_t count) { int32_t temp; switch (bps) { case 1: while (count--) *dst++ = *src++ + 128; break; case 2: while (count--) { *dst++ = (char) (temp = *src++); *dst++ = (char) (temp >> 8); } break; case 3: while (count--) { *dst++ = (char) (temp = *src++); *dst++ = (char) (temp >> 8); *dst++ = (char) (temp >> 16); } break; case 4: while (count--) { *dst++ = (char) (temp = *src++); *dst++ = (char) (temp >> 8); *dst++ = (char) (temp >> 16); *dst++ = (char) (temp >> 24); } break; } return dst; } static int wavpack_read(struct input_plugin_data *ip_data, char *buffer, int count) { struct wavpack_private *priv; int rc, bps, sample_count, channels; priv = ip_data->private; channels = sf_get_channels(ip_data->sf); bps = WavpackGetBytesPerSample(priv->wpc); sample_count = count / bps; rc = WavpackUnpackSamples(priv->wpc, priv->samples, sample_count / channels); format_samples(bps, buffer, priv->samples, rc * channels); return rc * channels * bps; } static int wavpack_seek(struct input_plugin_data *ip_data, double offset) { struct wavpack_private *priv = ip_data->private; if (!WavpackSeekSample(priv->wpc, WavpackGetSampleRate(priv->wpc) * offset)) return -IP_ERROR_INTERNAL; return 0; } static int wavpack_read_comments(struct input_plugin_data *ip_data, struct keyval **comments) { struct id3tag id3; APETAG(ape); GROWING_KEYVALS(c); int fd, rc, save, i; fd = open(ip_data->filename, O_RDONLY); if (fd == -1) return -1; d_print("filename: %s\n", ip_data->filename); id3_init(&id3); rc = id3_read_tags(&id3, fd, ID3_V1); save = errno; close(fd); errno = save; if (rc) { if (rc == -1) { d_print("error: %s\n", strerror(errno)); return -1; } d_print("corrupted tag?\n"); goto next; } for (i = 0; i < NUM_ID3_KEYS; i++) { char *val = id3_get_comment(&id3, i); if (val) comments_add(&c, id3_key_names[i], val); } next: id3_free(&id3); rc = ape_read_tags(&ape, ip_data->fd, 1); if (rc < 0) goto out; for (i = 0; i < rc; i++) { char *k, *v; k = ape_get_comment(&ape, &v); if (!k) break; comments_add(&c, k, v); free(k); } out: ape_free(&ape); keyvals_terminate(&c); *comments = c.keyvals; return 0; } static int wavpack_duration(struct input_plugin_data *ip_data) { struct wavpack_private *priv; int duration; priv = ip_data->private; duration = WavpackGetNumSamples(priv->wpc) / WavpackGetSampleRate(priv->wpc); return duration; } static long wavpack_bitrate(struct input_plugin_data *ip_data) { struct wavpack_private *priv = ip_data->private; double bitrate = WavpackGetAverageBitrate(priv->wpc, 1); if (!bitrate) return -IP_ERROR_FUNCTION_NOT_SUPPORTED; return (long) (bitrate + 0.5); } static long wavpack_current_bitrate(struct input_plugin_data *ip_data) { struct wavpack_private *priv = ip_data->private; return WavpackGetInstantBitrate(priv->wpc); } static char *wavpack_codec(struct input_plugin_data *ip_data) { return xstrdup("wavpack"); } static char *wavpack_codec_profile(struct input_plugin_data *ip_data) { struct wavpack_private *priv = ip_data->private; int m = WavpackGetMode(priv->wpc); char buf[32]; buf[0] = '\0'; if (m & MODE_FAST) strcat(buf, "fast"); #ifdef MODE_VERY_HIGH else if (m & MODE_VERY_HIGH) strcat(buf, "very high"); #endif else if (m & MODE_HIGH) strcat(buf, "high"); else strcat(buf, "normal"); if (m & MODE_HYBRID) strcat(buf, " hybrid"); #ifdef MODE_XMODE if ((m & MODE_EXTRA) && (m & MODE_XMODE)) { char xmode[] = " x0"; xmode[2] = ((m & MODE_XMODE) >> 12) + '0'; strcat(buf, xmode); } #endif return xstrdup(buf); } const struct input_plugin_ops ip_ops = { .open = wavpack_open, .close = wavpack_close, .read = wavpack_read, .seek = wavpack_seek, .read_comments = wavpack_read_comments, .duration = wavpack_duration, .bitrate = wavpack_bitrate, .bitrate_current = wavpack_current_bitrate, .codec = wavpack_codec, .codec_profile = wavpack_codec_profile }; const int ip_priority = 50; const char * const ip_extensions[] = { "wv", NULL }; const char * const ip_mime_types[] = { "audio/x-wavpack", NULL }; const struct input_plugin_opt ip_options[] = { { NULL } }; const unsigned ip_abi_version = IP_ABI_VERSION;