Line |
Branch |
Exec |
Source |
1 |
|
|
/******************************************************************************************* |
2 |
|
|
* |
3 |
|
|
* qoaplay - QOA stream playing helper functions |
4 |
|
|
* |
5 |
|
|
* qoaplay is a tiny abstraction to read and decode a QOA file "on the fly". |
6 |
|
|
* It reads and decodes one frame at a time with minimal memory requirements. |
7 |
|
|
* qoaplay also provides some functions to seek to a specific frame. |
8 |
|
|
* |
9 |
|
|
* LICENSE: MIT License |
10 |
|
|
* |
11 |
|
|
* Copyright (c) 2023 Dominic Szablewski (@phoboslab), reviewed by Ramon Santamaria (@raysan5) |
12 |
|
|
* |
13 |
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy |
14 |
|
|
* of this software and associated documentation files (the "Software"), to deal |
15 |
|
|
* in the Software without restriction, including without limitation the rights |
16 |
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
17 |
|
|
* copies of the Software, and to permit persons to whom the Software is |
18 |
|
|
* furnished to do so, subject to the following conditions: |
19 |
|
|
* |
20 |
|
|
* The above copyright notice and this permission notice shall be included in all |
21 |
|
|
* copies or substantial portions of the Software. |
22 |
|
|
* |
23 |
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
24 |
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
25 |
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
26 |
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
27 |
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
28 |
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
29 |
|
|
* SOFTWARE. |
30 |
|
|
* |
31 |
|
|
**********************************************************************************************/ |
32 |
|
|
|
33 |
|
|
//---------------------------------------------------------------------------------- |
34 |
|
|
// Types and Structures Definition |
35 |
|
|
//---------------------------------------------------------------------------------- |
36 |
|
|
// QOA streaming data descriptor |
37 |
|
|
typedef struct { |
38 |
|
|
qoa_desc info; // QOA descriptor data |
39 |
|
|
|
40 |
|
|
FILE *file; // QOA file to read, if NULL, using memory buffer -> file_data |
41 |
|
|
unsigned char *file_data; // QOA file data on memory |
42 |
|
|
unsigned int file_data_size; // QOA file data on memory size |
43 |
|
|
unsigned int file_data_offset; // QOA file data on memory offset for next read |
44 |
|
|
|
45 |
|
|
unsigned int first_frame_pos; // First frame position (after QOA header, required for offset) |
46 |
|
|
unsigned int sample_position; // Current streaming sample position |
47 |
|
|
|
48 |
|
|
unsigned char *buffer; // Buffer used to read samples from file/memory (used on decoding) |
49 |
|
|
unsigned int buffer_len; // Buffer length to read samples for streaming |
50 |
|
|
|
51 |
|
|
short *sample_data; // Sample data decoded |
52 |
|
|
unsigned int sample_data_len; // Sample data decoded length |
53 |
|
|
unsigned int sample_data_pos; // Sample data decoded position |
54 |
|
|
|
55 |
|
|
} qoaplay_desc; |
56 |
|
|
|
57 |
|
|
//---------------------------------------------------------------------------------- |
58 |
|
|
// Module Functions Declaration |
59 |
|
|
//---------------------------------------------------------------------------------- |
60 |
|
|
|
61 |
|
|
#if defined(__cplusplus) |
62 |
|
|
extern "C" { // Prevents name mangling of functions |
63 |
|
|
#endif |
64 |
|
|
|
65 |
|
|
qoaplay_desc *qoaplay_open(const char *path); |
66 |
|
|
qoaplay_desc *qoaplay_open_memory(const unsigned char *data, int data_size); |
67 |
|
|
void qoaplay_close(qoaplay_desc *qoa_ctx); |
68 |
|
|
|
69 |
|
|
void qoaplay_rewind(qoaplay_desc *qoa_ctx); |
70 |
|
|
void qoaplay_seek_frame(qoaplay_desc *qoa_ctx, int frame); |
71 |
|
|
unsigned int qoaplay_decode(qoaplay_desc *qoa_ctx, float *sample_data, int num_samples); |
72 |
|
|
unsigned int qoaplay_decode_frame(qoaplay_desc *qoa_ctx); |
73 |
|
|
double qoaplay_get_duration(qoaplay_desc *qoa_ctx); |
74 |
|
|
double qoaplay_get_time(qoaplay_desc *qoa_ctx); |
75 |
|
|
int qoaplay_get_frame(qoaplay_desc *qoa_ctx); |
76 |
|
|
|
77 |
|
|
#if defined(__cplusplus) |
78 |
|
|
} // Prevents name mangling of functions |
79 |
|
|
#endif |
80 |
|
|
|
81 |
|
|
//---------------------------------------------------------------------------------- |
82 |
|
|
// Module Functions Definition |
83 |
|
|
//---------------------------------------------------------------------------------- |
84 |
|
|
|
85 |
|
|
// Open QOA file, keep FILE pointer to keep reading from file |
86 |
|
✗ |
qoaplay_desc *qoaplay_open(const char *path) |
87 |
|
|
{ |
88 |
|
✗ |
FILE *file = fopen(path, "rb"); |
89 |
|
✗ |
if (!file) return NULL; |
90 |
|
|
|
91 |
|
|
// Read and decode the file header |
92 |
|
|
unsigned char header[QOA_MIN_FILESIZE]; |
93 |
|
✗ |
int read = fread(header, QOA_MIN_FILESIZE, 1, file); |
94 |
|
✗ |
if (!read) return NULL; |
95 |
|
|
|
96 |
|
|
qoa_desc qoa; |
97 |
|
✗ |
unsigned int first_frame_pos = qoa_decode_header(header, QOA_MIN_FILESIZE, &qoa); |
98 |
|
✗ |
if (!first_frame_pos) return NULL; |
99 |
|
|
|
100 |
|
|
// Rewind the file back to beginning of the first frame |
101 |
|
✗ |
fseek(file, first_frame_pos, SEEK_SET); |
102 |
|
|
|
103 |
|
|
// Allocate one chunk of memory for the qoaplay_desc struct |
104 |
|
|
// + the sample data for one frame |
105 |
|
|
// + a buffer to hold one frame of encoded data |
106 |
|
✗ |
unsigned int buffer_size = qoa_max_frame_size(&qoa); |
107 |
|
✗ |
unsigned int sample_data_size = qoa.channels*QOA_FRAME_LEN*sizeof(short)*2; |
108 |
|
✗ |
qoaplay_desc *qoa_ctx = QOA_MALLOC(sizeof(qoaplay_desc) + buffer_size + sample_data_size); |
109 |
|
|
memset(qoa_ctx, 0, sizeof(qoaplay_desc)); |
110 |
|
|
|
111 |
|
✗ |
qoa_ctx->file = file; |
112 |
|
|
qoa_ctx->file_data = NULL; |
113 |
|
|
qoa_ctx->file_data_size = 0; |
114 |
|
|
qoa_ctx->file_data_offset = 0; |
115 |
|
✗ |
qoa_ctx->first_frame_pos = first_frame_pos; |
116 |
|
|
|
117 |
|
|
// Setup data pointers to previously allocated data |
118 |
|
✗ |
qoa_ctx->buffer = ((unsigned char *)qoa_ctx) + sizeof(qoaplay_desc); |
119 |
|
✗ |
qoa_ctx->sample_data = (short *)(((unsigned char *)qoa_ctx) + sizeof(qoaplay_desc) + buffer_size); |
120 |
|
|
|
121 |
|
✗ |
qoa_ctx->info.channels = qoa.channels; |
122 |
|
✗ |
qoa_ctx->info.samplerate = qoa.samplerate; |
123 |
|
✗ |
qoa_ctx->info.samples = qoa.samples; |
124 |
|
|
|
125 |
|
✗ |
return qoa_ctx; |
126 |
|
|
} |
127 |
|
|
|
128 |
|
|
// Open QOA file from memory, no FILE pointer required |
129 |
|
✗ |
qoaplay_desc *qoaplay_open_memory(const unsigned char *data, int data_size) |
130 |
|
|
{ |
131 |
|
|
// Read and decode the file header |
132 |
|
|
unsigned char header[QOA_MIN_FILESIZE]; |
133 |
|
|
memcpy(header, data, QOA_MIN_FILESIZE); |
134 |
|
|
|
135 |
|
|
qoa_desc qoa; |
136 |
|
✗ |
unsigned int first_frame_pos = qoa_decode_header(header, QOA_MIN_FILESIZE, &qoa); |
137 |
|
✗ |
if (!first_frame_pos) return NULL; |
138 |
|
|
|
139 |
|
|
// Allocate one chunk of memory for the qoaplay_desc struct |
140 |
|
|
// + the sample data for one frame |
141 |
|
|
// + a buffer to hold one frame of encoded data |
142 |
|
✗ |
unsigned int buffer_size = qoa_max_frame_size(&qoa); |
143 |
|
✗ |
unsigned int sample_data_size = qoa.channels*QOA_FRAME_LEN*sizeof(short)*2; |
144 |
|
✗ |
qoaplay_desc *qoa_ctx = QOA_MALLOC(sizeof(qoaplay_desc) + buffer_size + sample_data_size); |
145 |
|
|
memset(qoa_ctx, 0, sizeof(qoaplay_desc)); |
146 |
|
|
|
147 |
|
|
qoa_ctx->file = NULL; |
148 |
|
|
|
149 |
|
|
// Keep a copy of file data provided to be managed internally |
150 |
|
✗ |
qoa_ctx->file_data = (unsigned char *)QOA_MALLOC(data_size); |
151 |
|
|
memcpy(qoa_ctx->file_data, data, data_size); |
152 |
|
✗ |
qoa_ctx->file_data_size = data_size; |
153 |
|
|
qoa_ctx->file_data_offset = 0; |
154 |
|
✗ |
qoa_ctx->first_frame_pos = first_frame_pos; |
155 |
|
|
|
156 |
|
|
// Setup data pointers to previously allocated data |
157 |
|
✗ |
qoa_ctx->buffer = ((unsigned char *)qoa_ctx) + sizeof(qoaplay_desc); |
158 |
|
✗ |
qoa_ctx->sample_data = (short *)(((unsigned char *)qoa_ctx) + sizeof(qoaplay_desc) + buffer_size); |
159 |
|
|
|
160 |
|
✗ |
qoa_ctx->info.channels = qoa.channels; |
161 |
|
✗ |
qoa_ctx->info.samplerate = qoa.samplerate; |
162 |
|
✗ |
qoa_ctx->info.samples = qoa.samples; |
163 |
|
|
|
164 |
|
✗ |
return qoa_ctx; |
165 |
|
|
} |
166 |
|
|
|
167 |
|
|
// Close QOA file (if open) and free internal memory |
168 |
|
✗ |
void qoaplay_close(qoaplay_desc *qoa_ctx) |
169 |
|
|
{ |
170 |
|
✗ |
if (qoa_ctx->file) fclose(qoa_ctx->file); |
171 |
|
|
|
172 |
|
✗ |
if ((qoa_ctx->file_data) && (qoa_ctx->file_data_size > 0)) |
173 |
|
|
{ |
174 |
|
✗ |
QOA_FREE(qoa_ctx->file_data); |
175 |
|
|
qoa_ctx->file_data_size = 0; |
176 |
|
|
} |
177 |
|
|
|
178 |
|
✗ |
QOA_FREE(qoa_ctx); |
179 |
|
|
} |
180 |
|
|
|
181 |
|
|
// Decode one frame from QOA data |
182 |
|
✗ |
unsigned int qoaplay_decode_frame(qoaplay_desc *qoa_ctx) |
183 |
|
|
{ |
184 |
|
✗ |
if (qoa_ctx->file) qoa_ctx->buffer_len = fread(qoa_ctx->buffer, 1, qoa_max_frame_size(&qoa_ctx->info), qoa_ctx->file); |
185 |
|
|
else |
186 |
|
|
{ |
187 |
|
✗ |
qoa_ctx->buffer_len = qoa_max_frame_size(&qoa_ctx->info); |
188 |
|
✗ |
memcpy(qoa_ctx->buffer, qoa_ctx->file_data + qoa_ctx->file_data_offset, qoa_ctx->buffer_len); |
189 |
|
✗ |
qoa_ctx->file_data_offset += qoa_ctx->buffer_len; |
190 |
|
|
} |
191 |
|
|
|
192 |
|
|
unsigned int frame_len; |
193 |
|
✗ |
qoa_decode_frame(qoa_ctx->buffer, qoa_ctx->buffer_len, &qoa_ctx->info, qoa_ctx->sample_data, &frame_len); |
194 |
|
✗ |
qoa_ctx->sample_data_pos = 0; |
195 |
|
✗ |
qoa_ctx->sample_data_len = frame_len; |
196 |
|
|
|
197 |
|
✗ |
return frame_len; |
198 |
|
|
} |
199 |
|
|
|
200 |
|
|
// Rewind QOA file or memory pointer to beginning |
201 |
|
✗ |
void qoaplay_rewind(qoaplay_desc *qoa_ctx) |
202 |
|
|
{ |
203 |
|
✗ |
if (qoa_ctx->file) fseek(qoa_ctx->file, qoa_ctx->first_frame_pos, SEEK_SET); |
204 |
|
✗ |
else qoa_ctx->file_data_offset = 0; |
205 |
|
|
|
206 |
|
✗ |
qoa_ctx->sample_position = 0; |
207 |
|
✗ |
qoa_ctx->sample_data_len = 0; |
208 |
|
✗ |
qoa_ctx->sample_data_pos = 0; |
209 |
|
|
} |
210 |
|
|
|
211 |
|
|
// Decode required QOA frames |
212 |
|
✗ |
unsigned int qoaplay_decode(qoaplay_desc *qoa_ctx, float *sample_data, int num_samples) |
213 |
|
|
{ |
214 |
|
✗ |
int src_index = qoa_ctx->sample_data_pos*qoa_ctx->info.channels; |
215 |
|
|
int dst_index = 0; |
216 |
|
|
|
217 |
|
✗ |
for (int i = 0; i < num_samples; i++) |
218 |
|
|
{ |
219 |
|
|
// Do we have to decode more samples? |
220 |
|
✗ |
if (qoa_ctx->sample_data_len - qoa_ctx->sample_data_pos == 0) |
221 |
|
|
{ |
222 |
|
✗ |
if (!qoaplay_decode_frame(qoa_ctx)) |
223 |
|
|
{ |
224 |
|
|
// Loop to the beginning |
225 |
|
✗ |
qoaplay_rewind(qoa_ctx); |
226 |
|
✗ |
qoaplay_decode_frame(qoa_ctx); |
227 |
|
|
} |
228 |
|
|
|
229 |
|
|
src_index = 0; |
230 |
|
|
} |
231 |
|
|
|
232 |
|
|
// Normalize to -1..1 floats and write to dest |
233 |
|
✗ |
for (int c = 0; c < qoa_ctx->info.channels; c++) |
234 |
|
|
{ |
235 |
|
✗ |
sample_data[dst_index++] = qoa_ctx->sample_data[src_index++]/32768.0; |
236 |
|
|
} |
237 |
|
|
|
238 |
|
✗ |
qoa_ctx->sample_data_pos++; |
239 |
|
✗ |
qoa_ctx->sample_position++; |
240 |
|
|
} |
241 |
|
|
|
242 |
|
✗ |
return num_samples; |
243 |
|
|
} |
244 |
|
|
|
245 |
|
|
// Get QOA total time duration in seconds |
246 |
|
✗ |
double qoaplay_get_duration(qoaplay_desc *qoa_ctx) |
247 |
|
|
{ |
248 |
|
✗ |
return (double)qoa_ctx->info.samples/(double)qoa_ctx->info.samplerate; |
249 |
|
|
} |
250 |
|
|
|
251 |
|
|
// Get QOA current time position in seconds |
252 |
|
✗ |
double qoaplay_get_time(qoaplay_desc *qoa_ctx) |
253 |
|
|
{ |
254 |
|
✗ |
return (double)qoa_ctx->sample_position/(double)qoa_ctx->info.samplerate; |
255 |
|
|
} |
256 |
|
|
|
257 |
|
|
// Get QOA current audio frame |
258 |
|
✗ |
int qoaplay_get_frame(qoaplay_desc *qoa_ctx) |
259 |
|
|
{ |
260 |
|
✗ |
return qoa_ctx->sample_position/QOA_FRAME_LEN; |
261 |
|
|
} |
262 |
|
|
|
263 |
|
|
// Seek QOA audio frame |
264 |
|
✗ |
void qoaplay_seek_frame(qoaplay_desc *qoa_ctx, int frame) |
265 |
|
|
{ |
266 |
|
|
if (frame < 0) frame = 0; |
267 |
|
|
|
268 |
|
✗ |
if (frame > qoa_ctx->info.samples/QOA_FRAME_LEN) frame = qoa_ctx->info.samples/QOA_FRAME_LEN; |
269 |
|
|
|
270 |
|
✗ |
qoa_ctx->sample_position = frame*QOA_FRAME_LEN; |
271 |
|
✗ |
qoa_ctx->sample_data_len = 0; |
272 |
|
✗ |
qoa_ctx->sample_data_pos = 0; |
273 |
|
|
|
274 |
|
✗ |
unsigned int offset = qoa_ctx->first_frame_pos + frame*qoa_max_frame_size(&qoa_ctx->info); |
275 |
|
|
|
276 |
|
✗ |
if (qoa_ctx->file) fseek(qoa_ctx->file, offset, SEEK_SET); |
277 |
|
✗ |
else qoa_ctx->file_data_offset = offset; |
278 |
|
|
} |
279 |
|
|
|