Marcoen Hirschberg | 2175d1e | 2007-06-16 18:19:51 +0000 | [diff] [blame] | 1 | /*************************************************************************** |
| 2 | * __________ __ ___. |
| 3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| 4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| 5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| 6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| 7 | * \/ \/ \/ \/ \/ |
| 8 | * $Id$ |
| 9 | * |
Thom Johansen | 1a24595 | 2007-08-24 11:11:06 +0000 | [diff] [blame] | 10 | * Copyright (C) 2005 Thom Johansen |
Andree Buschmann | 63f0b2b | 2010-03-14 22:34:09 +0000 | [diff] [blame^] | 11 | * Copyright (C) 2010 Andree Buschmann |
Marcoen Hirschberg | 2175d1e | 2007-06-16 18:19:51 +0000 | [diff] [blame] | 12 | * |
Daniel Stenberg | 2acc0ac | 2008-06-28 18:10:04 +0000 | [diff] [blame] | 13 | * This program is free software; you can redistribute it and/or |
| 14 | * modify it under the terms of the GNU General Public License |
| 15 | * as published by the Free Software Foundation; either version 2 |
| 16 | * of the License, or (at your option) any later version. |
Marcoen Hirschberg | 2175d1e | 2007-06-16 18:19:51 +0000 | [diff] [blame] | 17 | * |
| 18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| 19 | * KIND, either express or implied. |
| 20 | * |
| 21 | ****************************************************************************/ |
Marcoen Hirschberg | 2175d1e | 2007-06-16 18:19:51 +0000 | [diff] [blame] | 22 | |
Thom Johansen | 5915736 | 2007-12-04 20:48:40 +0000 | [diff] [blame] | 23 | #include <string.h> |
| 24 | #include <inttypes.h> |
Marcoen Hirschberg | 2175d1e | 2007-06-16 18:19:51 +0000 | [diff] [blame] | 25 | #include "system.h" |
Björn Stenberg | 51b45d5 | 2008-10-15 06:38:51 +0000 | [diff] [blame] | 26 | #include "metadata.h" |
Marcoen Hirschberg | 2175d1e | 2007-06-16 18:19:51 +0000 | [diff] [blame] | 27 | #include "metadata_common.h" |
Bertrik Sikken | 1273f95 | 2008-05-03 07:03:59 +0000 | [diff] [blame] | 28 | #include "metadata_parsers.h" |
Marcoen Hirschberg | 2175d1e | 2007-06-16 18:19:51 +0000 | [diff] [blame] | 29 | #include "logf.h" |
| 30 | #include "replaygain.h" |
Andree Buschmann | 63f0b2b | 2010-03-14 22:34:09 +0000 | [diff] [blame^] | 31 | #include "fixedpoint.h" |
Marcoen Hirschberg | 2175d1e | 2007-06-16 18:19:51 +0000 | [diff] [blame] | 32 | |
Andree Buschmann | 63f0b2b | 2010-03-14 22:34:09 +0000 | [diff] [blame^] | 33 | /* Needed for replay gain and clipping prevention of SV8 files. */ |
| 34 | #define SV8_TO_SV7_CONVERT_GAIN (6482) /* 64.82 * 100, MPC_OLD_GAIN_REF */ |
| 35 | #define SV8_TO_SV7_CONVERT_PEAK (23119) /* 256 * 20 * log10(32768) */ |
Andree Buschmann | b3d9578 | 2010-03-07 19:34:44 +0000 | [diff] [blame] | 36 | |
| 37 | static int set_replaygain_sv7(struct mp3entry* id3, |
| 38 | bool album, |
| 39 | long value, |
| 40 | long used) |
Magnus Holmgren | aee243e | 2007-12-01 11:55:19 +0000 | [diff] [blame] | 41 | { |
| 42 | long gain = (int16_t) ((value >> 16) & 0xffff); |
| 43 | long peak = (uint16_t) (value & 0xffff); |
| 44 | |
| 45 | /* We use a peak value of 0 to indicate a given gain type isn't used. */ |
Andree Buschmann | b3d9578 | 2010-03-07 19:34:44 +0000 | [diff] [blame] | 46 | if (peak != 0) { |
Andree Buschmann | 63f0b2b | 2010-03-14 22:34:09 +0000 | [diff] [blame^] | 47 | /* Save the ReplayGain data to id3-structure for further processing. */ |
Magnus Holmgren | aee243e | 2007-12-01 11:55:19 +0000 | [diff] [blame] | 48 | used += parse_replaygain_int(album, gain * 512 / 100, peak << 9, |
| 49 | id3, id3->toc + used, sizeof(id3->toc) - used); |
| 50 | } |
| 51 | |
| 52 | return used; |
| 53 | } |
| 54 | |
Andree Buschmann | b3d9578 | 2010-03-07 19:34:44 +0000 | [diff] [blame] | 55 | static int set_replaygain_sv8(struct mp3entry* id3, |
| 56 | bool album, |
| 57 | long gain, |
| 58 | long peak, |
| 59 | long used) |
| 60 | { |
Andree Buschmann | 40c2665 | 2010-03-07 20:55:37 +0000 | [diff] [blame] | 61 | gain = (long)(SV8_TO_SV7_CONVERT_GAIN - ((gain*100)/256)); |
Andree Buschmann | b3d9578 | 2010-03-07 19:34:44 +0000 | [diff] [blame] | 62 | |
Andree Buschmann | 63f0b2b | 2010-03-14 22:34:09 +0000 | [diff] [blame^] | 63 | /* Transform SV8's logarithmic peak representation to the desired linear |
| 64 | * representation: linear = pow(10, peak/256/20). |
| 65 | * |
| 66 | * FP_BITS = 24 bits = desired fp representation for dsp routines |
| 67 | * FRAC_BITS = 12 bits = resolution used for fp_bits |
| 68 | * fp_factor(peak*(1<<FRAC_BITS)/256, FRAC_BITS) << (FP_BITS-FRAC_BITS) |
| 69 | **/ |
| 70 | peak = (fp_factor((peak-SV8_TO_SV7_CONVERT_PEAK)*16, 12) << 12); |
| 71 | |
Andree Buschmann | b3d9578 | 2010-03-07 19:34:44 +0000 | [diff] [blame] | 72 | /* We use a peak value of 0 to indicate a given gain type isn't used. */ |
| 73 | if (peak != 0) { |
Andree Buschmann | 63f0b2b | 2010-03-14 22:34:09 +0000 | [diff] [blame^] | 74 | /* Save the ReplayGain data to id3-structure for further processing. */ |
| 75 | used += parse_replaygain_int(album, gain * 512 / 100, peak, |
Andree Buschmann | b3d9578 | 2010-03-07 19:34:44 +0000 | [diff] [blame] | 76 | id3, id3->toc + used, sizeof(id3->toc) - used); |
| 77 | } |
| 78 | |
| 79 | return used; |
| 80 | } |
| 81 | |
| 82 | static int sv8_get_size(uint8_t *buffer, int index, uint64_t *p_size) |
| 83 | { |
| 84 | unsigned char tmp; |
| 85 | uint64_t size = 0; |
| 86 | |
| 87 | do { |
| 88 | tmp = buffer[index++]; |
| 89 | size = (size << 7) | (tmp & 0x7F); |
| 90 | } while((tmp & 0x80)); |
| 91 | |
| 92 | *p_size = size; |
| 93 | return index; |
| 94 | } |
| 95 | |
Marcoen Hirschberg | 2175d1e | 2007-06-16 18:19:51 +0000 | [diff] [blame] | 96 | bool get_musepack_metadata(int fd, struct mp3entry *id3) |
| 97 | { |
Andree Buschmann | b3d9578 | 2010-03-07 19:34:44 +0000 | [diff] [blame] | 98 | static const int32_t sfreqs[4] = { 44100, 48000, 37800, 32000 }; |
Marcoen Hirschberg | 2175d1e | 2007-06-16 18:19:51 +0000 | [diff] [blame] | 99 | uint32_t header[8]; |
| 100 | uint64_t samples = 0; |
| 101 | int i; |
| 102 | |
| 103 | if (!skip_id3v2(fd, id3)) |
| 104 | return false; |
| 105 | if (read(fd, header, 4*8) != 4*8) return false; |
| 106 | /* Musepack files are little endian, might need swapping */ |
| 107 | for (i = 1; i < 8; i++) |
| 108 | header[i] = letoh32(header[i]); |
| 109 | if (!memcmp(header, "MP+", 3)) { /* Compare to sig "MP+" */ |
| 110 | unsigned int streamversion; |
Marcoen Hirschberg | 2175d1e | 2007-06-16 18:19:51 +0000 | [diff] [blame] | 111 | header[0] = letoh32(header[0]); |
| 112 | streamversion = (header[0] >> 24) & 15; |
Andree Buschmann | db4d7a3 | 2010-01-31 11:43:42 +0000 | [diff] [blame] | 113 | if (streamversion == 7) { |
Marcoen Hirschberg | 2175d1e | 2007-06-16 18:19:51 +0000 | [diff] [blame] | 114 | unsigned int gapless = (header[5] >> 31) & 0x0001; |
| 115 | unsigned int last_frame_samples = (header[5] >> 20) & 0x07ff; |
Thom Johansen | 655fa16 | 2007-11-30 01:10:28 +0000 | [diff] [blame] | 116 | unsigned int bufused = 0; |
Marcoen Hirschberg | 2175d1e | 2007-06-16 18:19:51 +0000 | [diff] [blame] | 117 | |
Andree Buschmann | b3d9578 | 2010-03-07 19:34:44 +0000 | [diff] [blame] | 118 | id3->frequency = sfreqs[(header[2] >> 16) & 0x0003]; |
Marcoen Hirschberg | 2175d1e | 2007-06-16 18:19:51 +0000 | [diff] [blame] | 119 | samples = (uint64_t)header[1]*1152; /* 1152 is mpc frame size */ |
| 120 | if (gapless) |
| 121 | samples -= 1152 - last_frame_samples; |
| 122 | else |
| 123 | samples -= 481; /* Musepack subband synth filter delay */ |
| 124 | |
Andree Buschmann | b3d9578 | 2010-03-07 19:34:44 +0000 | [diff] [blame] | 125 | bufused = set_replaygain_sv7(id3, false, header[3], bufused); |
| 126 | bufused = set_replaygain_sv7(id3, true , header[4], bufused); |
Andree Buschmann | db4d7a3 | 2010-01-31 11:43:42 +0000 | [diff] [blame] | 127 | } else { |
| 128 | return false; /* only SV7 is allowed within a "MP+" signature */ |
Marcoen Hirschberg | 2175d1e | 2007-06-16 18:19:51 +0000 | [diff] [blame] | 129 | } |
Andree Buschmann | b3d9578 | 2010-03-07 19:34:44 +0000 | [diff] [blame] | 130 | } else if (!memcmp(header, "MPCK", 4)) { /* Compare to sig "MPCK" */ |
| 131 | uint8_t sv8_header[32]; |
| 132 | /* 4 bytes 'MPCK' */ |
| 133 | lseek(fd, 4, SEEK_SET); |
| 134 | if (read(fd, sv8_header, 2) != 2) return false; /* read frame ID */ |
| 135 | if (!memcmp(sv8_header, "SH", 2)) { /* Stream Header ID */ |
| 136 | int32_t k = 0; |
| 137 | uint32_t streamversion; |
Andree Buschmann | 5602383 | 2010-03-08 22:39:15 +0000 | [diff] [blame] | 138 | uint64_t size = 0; /* tag size */ |
| 139 | uint64_t dummy = 0; /* used to dummy read data from header */ |
Andree Buschmann | b3d9578 | 2010-03-07 19:34:44 +0000 | [diff] [blame] | 140 | |
Andree Buschmann | 5602383 | 2010-03-08 22:39:15 +0000 | [diff] [blame] | 141 | /* 4 bytes 'MPCK' + 2 'SH' */ |
| 142 | lseek(fd, 6, SEEK_SET); |
Andree Buschmann | b3d9578 | 2010-03-07 19:34:44 +0000 | [diff] [blame] | 143 | if (read(fd, sv8_header, 32) != 32) return false; |
Andree Buschmann | 5602383 | 2010-03-08 22:39:15 +0000 | [diff] [blame] | 144 | |
| 145 | /* Read the size of 'SH'-tag */ |
| 146 | k = sv8_get_size(sv8_header, k, &size); |
| 147 | |
| 148 | /* Skip crc32 */ |
| 149 | k += 4; |
Andree Buschmann | b3d9578 | 2010-03-07 19:34:44 +0000 | [diff] [blame] | 150 | |
| 151 | /* Read stream version */ |
| 152 | streamversion = sv8_header[k++]; |
| 153 | if (streamversion != 8) return false; /* Only SV8 is allowed. */ |
| 154 | |
| 155 | /* Number of samples */ |
| 156 | k = sv8_get_size(sv8_header, k, &samples); |
| 157 | |
| 158 | /* Number of leading zero-samples */ |
Andree Buschmann | 5602383 | 2010-03-08 22:39:15 +0000 | [diff] [blame] | 159 | k = sv8_get_size(sv8_header, k, &dummy); |
Andree Buschmann | b3d9578 | 2010-03-07 19:34:44 +0000 | [diff] [blame] | 160 | |
| 161 | /* Sampling frequency */ |
| 162 | id3->frequency = sfreqs[(sv8_header[k++] >> 5) & 0x0003]; |
| 163 | |
| 164 | /* Number of channels */ |
| 165 | id3->channels = (sv8_header[k++] >> 4) + 1; |
| 166 | |
Andree Buschmann | 5602383 | 2010-03-08 22:39:15 +0000 | [diff] [blame] | 167 | /* Skip to next tag: k = size -2 */ |
| 168 | k = size - 2; |
| 169 | |
Andree Buschmann | b3d9578 | 2010-03-07 19:34:44 +0000 | [diff] [blame] | 170 | if (!memcmp(sv8_header+k, "RG", 2)) { /* Replay Gain ID */ |
| 171 | long peak, gain; |
| 172 | int bufused = 0; |
| 173 | |
| 174 | k += 2; /* 2 bytes 'RG' */ |
| 175 | |
| 176 | /* sv8_get_size must be called to skip the right amount of |
| 177 | * bits within the header data. */ |
| 178 | k = sv8_get_size(sv8_header, k, &size); |
| 179 | |
| 180 | /* Read and set replay gain */ |
| 181 | if (sv8_header[k++] == 1) { |
| 182 | /* Title's peak and gain */ |
| 183 | gain = (int16_t) ((sv8_header[k]<<8) + sv8_header[k+1]); k += 2; |
| 184 | peak = (uint16_t)((sv8_header[k]<<8) + sv8_header[k+1]); k += 2; |
| 185 | bufused += set_replaygain_sv8(id3, false, gain, peak, bufused); |
| 186 | |
| 187 | /* Album's peak and gain */ |
| 188 | gain = (int16_t) ((sv8_header[k]<<8) + sv8_header[k+1]); k += 2; |
| 189 | peak = (uint16_t)((sv8_header[k]<<8) + sv8_header[k+1]); k += 2; |
| 190 | bufused += set_replaygain_sv8(id3, true , gain, peak, bufused); |
| 191 | } |
| 192 | } |
| 193 | } else { |
| 194 | /* No sv8 stream header found */ |
| 195 | return false; |
| 196 | } |
Marcoen Hirschberg | 2175d1e | 2007-06-16 18:19:51 +0000 | [diff] [blame] | 197 | } else { |
Andree Buschmann | e1e0bc2 | 2008-05-22 10:08:24 +0000 | [diff] [blame] | 198 | return false; /* SV4-6 is not supported anymore */ |
Marcoen Hirschberg | 2175d1e | 2007-06-16 18:19:51 +0000 | [diff] [blame] | 199 | } |
| 200 | |
| 201 | id3->vbr = true; |
| 202 | /* Estimate bitrate, we should probably subtract the various header sizes |
| 203 | here for super-accurate results */ |
| 204 | id3->length = ((int64_t) samples * 1000) / id3->frequency; |
| 205 | |
| 206 | if (id3->length <= 0) |
| 207 | { |
| 208 | logf("mpc length invalid!"); |
| 209 | return false; |
| 210 | } |
| 211 | |
| 212 | id3->filesize = filesize(fd); |
| 213 | id3->bitrate = id3->filesize * 8 / id3->length; |
| 214 | return true; |
| 215 | } |