qoaplay.c (10503B)
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 }