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