A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2004 Linus Nielsen Feltzing
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "plugin.h"
22
23
24
25static char *audiobuf;
26static size_t audiobuflen;
27unsigned char xingbuf[1500];
28char tmpname[MAX_PATH];
29static long last_talk = 0;
30
31static void xingupdate(int percent)
32{
33 rb->lcd_putsf(0, 1, "%d%%", percent);
34 rb->lcd_update();
35 if (rb->global_settings->talk_menu)
36 {
37 long now = *(rb->current_tick) / HZ;
38 if (now - last_talk >= 5)
39 {
40 rb->talk_value_decimal(percent, UNIT_PERCENT, 0, false);
41 last_talk = now;
42 }
43 }
44}
45
46static int insert_data_in_file(const char *fname, int fpos, char *buf, int num_bytes)
47{
48 int readlen;
49 int rc;
50 int orig_fd, fd;
51
52 rb->snprintf(tmpname, MAX_PATH, "%s.tmp", fname);
53
54 orig_fd = rb->open(fname, O_RDONLY);
55 if(orig_fd < 0) {
56 return 10*orig_fd - 1;
57 }
58
59 fd = rb->creat(tmpname, 0666);
60 if(fd < 0) {
61 rb->close(orig_fd);
62 return 10*fd - 2;
63 }
64
65 /* First, copy the initial portion (the ID3 tag) */
66 if(fpos) {
67 readlen = rb->read(orig_fd, audiobuf, fpos);
68 if(readlen < 0) {
69 rb->close(fd);
70 rb->close(orig_fd);
71 return 10*readlen - 3;
72 }
73
74 rc = rb->write(fd, audiobuf, readlen);
75 if(rc < 0) {
76 rb->close(fd);
77 rb->close(orig_fd);
78 return 10*rc - 4;
79 }
80 }
81
82 /* Now insert the data into the file */
83 rc = rb->write(fd, buf, num_bytes);
84 if(rc < 0) {
85 rb->close(orig_fd);
86 rb->close(fd);
87 return 10*rc - 5;
88 }
89
90 /* Copy the file */
91 do {
92 readlen = rb->read(orig_fd, audiobuf, audiobuflen);
93 if(readlen < 0) {
94 rb->close(fd);
95 rb->close(orig_fd);
96 return 10*readlen - 7;
97 }
98
99 rc = rb->write(fd, audiobuf, readlen);
100 if(rc < 0) {
101 rb->close(fd);
102 rb->close(orig_fd);
103 return 10*rc - 8;
104 }
105 } while(readlen > 0);
106
107 rb->close(fd);
108 rb->close(orig_fd);
109
110 /* Remove the old file */
111 rc = rb->remove(fname);
112 if(rc < 0) {
113 return 10*rc - 9;
114 }
115
116 /* Replace the old file with the new */
117 rc = rb->rename(tmpname, fname);
118 if(rc < 0) {
119 return 10*rc - 9;
120 }
121
122 return 0;
123}
124
125static void fileerror(int rc)
126{
127 if (rb->global_settings->talk_menu) {
128 rb->talk_id(LANG_FILE_ERROR, true);
129 rb->talk_value_decimal(rc, UNIT_INT, 0, true);
130 rb->talk_force_enqueue_next();
131 }
132
133 rb->splashf(HZ*2, rb->str(LANG_FILE_ERROR), rc); /* (voiced above) */
134}
135
136static const unsigned char empty_id3_header[] =
137{
138 'I', 'D', '3', 0x04, 0x00, 0x00,
139 0x00, 0x00, 0x1f, 0x76 /* Size is 4096 minus 10 bytes for the header */
140};
141
142static bool vbr_fix(const char *selected_file)
143{
144 struct mp3entry entry;
145 int fd;
146 int rc;
147 int num_frames;
148 int numbytes;
149 int framelen;
150 int unused_space;
151
152 rb->lcd_clear_display();
153 rb->lcd_puts_scroll(0, 0, selected_file);
154 rb->lcd_update();
155
156 xingupdate(0);
157
158 rc = rb->get_metadata(&entry, -1, selected_file);
159 if(!rc) {
160 fileerror(rc);
161 return true;
162 }
163
164 fd = rb->open(selected_file, O_RDWR);
165 if(fd < 0) {
166 fileerror(fd);
167 return true;
168 }
169
170 xingupdate(0);
171
172 num_frames = rb->count_mp3_frames(fd, entry.first_frame_offset,
173 entry.filesize, xingupdate, audiobuf, audiobuflen);
174
175 if(num_frames) {
176 /* Note: We don't need to pass a template header because it will be
177 taken from the mpeg stream */
178 framelen = rb->create_xing_header(fd, entry.first_frame_offset,
179 entry.filesize, xingbuf, num_frames, 0,
180 0, xingupdate, true,
181 audiobuf, audiobuflen);
182
183 /* Try to fit the Xing header first in the stream. Replace the existing
184 VBR header if there is one, else see if there is room between the
185 ID3 tag and the first MP3 frame. */
186 if(entry.first_frame_offset - entry.id3v2len >=
187 (unsigned int)framelen) {
188 DEBUGF("Using existing space between ID3 and first frame\n");
189
190 /* Seek to the beginning of the unused space */
191 rc = rb->lseek(fd, entry.id3v2len, SEEK_SET);
192 if(rc < 0) {
193 rb->close(fd);
194 fileerror(rc);
195 return true;
196 }
197
198 unused_space =
199 entry.first_frame_offset - entry.id3v2len - framelen;
200
201 /* Fill the unused space with 0's (using the MP3 buffer)
202 and write it to the file */
203 if(unused_space)
204 {
205 rb->memset(audiobuf, 0, unused_space);
206 rc = rb->write(fd, audiobuf, unused_space);
207 if(rc < 0) {
208 rb->close(fd);
209 fileerror(rc);
210 return true;
211 }
212 }
213
214 /* Then write the Xing header */
215 rc = rb->write(fd, xingbuf, framelen);
216 if(rc < 0) {
217 rb->close(fd);
218 fileerror(rc);
219 return true;
220 }
221
222 rb->close(fd);
223 } else {
224 /* If not, insert some space. If there is an ID3 tag in the
225 file we only insert just enough to squeeze the Xing header
226 in. If not, we insert an additional empty ID3 tag of 4K. */
227
228 rb->close(fd);
229
230 /* Nasty trick alert! The insert_data_in_file() function
231 uses the MP3 buffer when copying the data. We assume
232 that the ID3 tag isn't longer than 1MB so the xing
233 buffer won't be overwritten. */
234
235 if(entry.first_frame_offset) {
236 DEBUGF("Inserting %d bytes\n", framelen);
237 numbytes = framelen;
238 } else {
239 DEBUGF("Inserting 4096+%d bytes\n", framelen);
240 numbytes = 4096 + framelen;
241
242 rb->memset(audiobuf + 0x100000, 0, numbytes);
243
244 /* Insert the ID3 header */
245 rb->memcpy(audiobuf + 0x100000, empty_id3_header,
246 sizeof(empty_id3_header));
247 }
248
249 /* Copy the Xing header */
250 rb->memcpy(audiobuf + 0x100000 + numbytes - framelen,
251 xingbuf, framelen);
252
253 rc = insert_data_in_file(selected_file,
254 entry.first_frame_offset,
255 audiobuf + 0x100000, numbytes);
256
257 if(rc < 0) {
258 fileerror(rc);
259 return true;
260 }
261 }
262
263 xingupdate(100);
264 }
265 else
266 {
267 rb->close(fd);
268
269 /* Not a VBR file */
270 DEBUGF("Not a VBR file\n");
271 rb->splash(HZ*2, ID2P(LANG_NOT_A_VBR_FILE));
272 }
273
274 return false;
275}
276
277enum plugin_status plugin_start(const void *parameter)
278{
279 last_talk = *(rb->current_tick) / HZ;
280
281 if (!parameter)
282 return PLUGIN_ERROR;
283
284 audiobuf = rb->plugin_get_audio_buffer(&audiobuflen);
285
286#ifdef HAVE_ADJUSTABLE_CPU_FREQ
287 rb->cpu_boost(true);
288#endif
289
290 vbr_fix(parameter);
291
292#ifdef HAVE_ADJUSTABLE_CPU_FREQ
293 rb->cpu_boost(false);
294#endif
295 return PLUGIN_OK;
296}