spek-pipeline.cc (13319B)
1 #include <wx/intl.h> 2 3 #include <assert.h> 4 #include <math.h> 5 #include <pthread.h> 6 #include <stdint.h> 7 #include <stdlib.h> 8 #include <string.h> 9 10 #include <vector> 11 12 #include "spek-audio.h" 13 #include "spek-fft.h" 14 15 #include "spek-pipeline.h" 16 17 #define ngettext wxPLURAL 18 19 enum 20 { 21 NFFT = 64 // Number of FFTs to pre-fetch. 22 }; 23 24 struct spek_pipeline 25 { 26 std::unique_ptr<AudioFile> file; 27 std::unique_ptr<FFTPlan> fft; 28 int stream; 29 int channel; 30 enum window_function window_function; 31 int samples; 32 spek_pipeline_cb cb; 33 void *cb_data; 34 35 float *coss; // Pre-computed cos table. 36 int nfft; // Size of the FFT transform. 37 int input_size; 38 int input_pos; 39 float *input; 40 float *output; 41 42 pthread_t reader_thread; 43 bool has_reader_thread; 44 pthread_mutex_t reader_mutex; 45 bool has_reader_mutex; 46 pthread_cond_t reader_cond; 47 bool has_reader_cond; 48 pthread_t worker_thread; 49 bool has_worker_thread; 50 pthread_mutex_t worker_mutex; 51 bool has_worker_mutex; 52 pthread_cond_t worker_cond; 53 bool has_worker_cond; 54 bool worker_done; 55 volatile bool quit; 56 }; 57 58 // Forward declarations. 59 static void * reader_func(void *); 60 static void * worker_func(void *); 61 static void reader_sync(struct spek_pipeline *p, int pos); 62 63 struct spek_pipeline * spek_pipeline_open( 64 std::unique_ptr<AudioFile> file, 65 std::unique_ptr<FFTPlan> fft, 66 int stream, 67 int channel, 68 enum window_function window_function, 69 int samples, 70 spek_pipeline_cb cb, 71 void *cb_data 72 ) 73 { 74 spek_pipeline *p = new spek_pipeline(); 75 p->file = std::move(file); 76 p->fft = std::move(fft); 77 p->stream = stream; 78 p->channel = channel; 79 p->window_function = window_function; 80 p->samples = samples; 81 p->cb = cb; 82 p->cb_data = cb_data; 83 84 p->coss = NULL; 85 p->input = NULL; 86 p->output = NULL; 87 p->has_reader_thread = false; 88 p->has_reader_mutex = false; 89 p->has_reader_cond = false; 90 p->has_worker_thread = false; 91 p->has_worker_mutex = false; 92 p->has_worker_cond = false; 93 94 if (!p->file->get_error()) { 95 p->nfft = p->fft->get_input_size(); 96 p->coss = (float*)malloc(p->nfft * sizeof(float)); 97 float cf = 2.0f * (float)M_PI / (p->nfft - 1.0f); 98 for (int i = 0; i < p->nfft; ++i) { 99 p->coss[i] = cosf(cf * i); 100 } 101 p->input_size = p->nfft * (NFFT * 2 + 1); 102 p->input = (float*)malloc(p->input_size * sizeof(float)); 103 p->output = (float*)malloc(p->fft->get_output_size() * sizeof(float)); 104 p->file->start(channel, samples); 105 } 106 107 return p; 108 } 109 110 void spek_pipeline_start(struct spek_pipeline *p) 111 { 112 if (!!p->file->get_error()) { 113 return; 114 } 115 116 p->input_pos = 0; 117 p->worker_done = false; 118 p->quit = false; 119 120 p->has_reader_mutex = !pthread_mutex_init(&p->reader_mutex, NULL); 121 p->has_reader_cond = !pthread_cond_init(&p->reader_cond, NULL); 122 p->has_worker_mutex = !pthread_mutex_init(&p->worker_mutex, NULL); 123 p->has_worker_cond = !pthread_cond_init(&p->worker_cond, NULL); 124 125 p->has_reader_thread = !pthread_create(&p->reader_thread, NULL, &reader_func, p); 126 if (!p->has_reader_thread) { 127 spek_pipeline_close(p); 128 } 129 } 130 131 void spek_pipeline_close(struct spek_pipeline *p) 132 { 133 if (p->has_reader_thread) { 134 p->quit = true; 135 pthread_join(p->reader_thread, NULL); 136 p->has_reader_thread = false; 137 } 138 if (p->has_worker_cond) { 139 pthread_cond_destroy(&p->worker_cond); 140 p->has_worker_cond = false; 141 } 142 if (p->has_worker_mutex) { 143 pthread_mutex_destroy(&p->worker_mutex); 144 p->has_worker_mutex = false; 145 } 146 if (p->has_reader_cond) { 147 pthread_cond_destroy(&p->reader_cond); 148 p->has_reader_cond = false; 149 } 150 if (p->has_reader_mutex) { 151 pthread_mutex_destroy(&p->reader_mutex); 152 p->has_reader_mutex = false; 153 } 154 if (p->output) { 155 free(p->output); 156 p->output = NULL; 157 } 158 if (p->input) { 159 free(p->input); 160 p->input = NULL; 161 } 162 if (p->coss) { 163 free(p->coss); 164 p->coss = NULL; 165 } 166 167 p->file.reset(); 168 169 delete p; 170 } 171 172 std::string spek_pipeline_desc(const struct spek_pipeline *pipeline) 173 { 174 std::vector<std::string> items; 175 176 if (!pipeline->file->get_codec_name().empty()) { 177 items.push_back(pipeline->file->get_codec_name()); 178 } 179 180 if (pipeline->file->get_bit_rate()) { 181 items.push_back(std::string( 182 wxString::Format(_("%d kbps"), (pipeline->file->get_bit_rate() + 500) / 1000).utf8_str() 183 )); 184 } 185 186 if (pipeline->file->get_sample_rate()) { 187 items.push_back(std::string( 188 wxString::Format(_("%d Hz"), pipeline->file->get_sample_rate()).utf8_str() 189 )); 190 } 191 192 // Include bits per sample only if there is no bitrate. 193 if (pipeline->file->get_bits_per_sample() && !pipeline->file->get_bit_rate()) { 194 items.push_back(std::string( 195 wxString::Format( 196 ngettext("%d bit", "%d bits", pipeline->file->get_bits_per_sample()), 197 pipeline->file->get_bits_per_sample() 198 ).utf8_str() 199 )); 200 } 201 202 if (pipeline->file->get_channels()) { 203 items.push_back(std::string( 204 wxString::Format( 205 // TRANSLATORS: first %d is the current channel, second %d is the total number. 206 "channel %d / %d", pipeline->channel + 1, pipeline->file->get_channels() 207 ).utf8_str() 208 )); 209 } 210 211 if (pipeline->file->get_error() == AudioError::OK) { 212 items.push_back(std::string(wxString::Format(wxT("W:%i"), pipeline->nfft).utf8_str())); 213 214 std::string window_function_name; 215 switch (pipeline->window_function) { 216 case WINDOW_HANN: 217 window_function_name = std::string("Hann"); 218 break; 219 case WINDOW_HAMMING: 220 window_function_name = std::string("Hamming"); 221 break; 222 case WINDOW_BLACKMAN_HARRIS: 223 window_function_name = std::string("Blackman–Harris"); 224 break; 225 default: 226 assert(false); 227 } 228 if (window_function_name.size()) { 229 items.push_back("F:" + window_function_name); 230 } 231 } 232 233 std::string desc; 234 for (const auto& item : items) { 235 if (!desc.empty()) { 236 desc.append(", "); 237 } 238 desc.append(item); 239 } 240 241 wxString error; 242 switch (pipeline->file->get_error()) { 243 case AudioError::CANNOT_OPEN_FILE: 244 error = _("Cannot open input file"); 245 break; 246 case AudioError::NO_STREAMS: 247 error = _("Cannot find stream info"); 248 break; 249 case AudioError::NO_AUDIO: 250 error = _("The file contains no audio streams"); 251 break; 252 case AudioError::NO_DECODER: 253 error = _("Cannot find decoder"); 254 break; 255 case AudioError::NO_DURATION: 256 error = _("Unknown duration"); 257 break; 258 case AudioError::NO_CHANNELS: 259 error = _("No audio channels"); 260 break; 261 case AudioError::CANNOT_OPEN_DECODER: 262 error = _("Cannot open decoder"); 263 break; 264 case AudioError::BAD_SAMPLE_FORMAT: 265 error = _("Unsupported sample format"); 266 break; 267 case AudioError::OK: 268 break; 269 } 270 271 auto error_string = std::string(error.utf8_str()); 272 if (desc.empty()) { 273 desc = error_string; 274 } else if (pipeline->stream < pipeline->file->get_streams()) { 275 desc = std::string( 276 wxString::Format( 277 // TRANSLATORS: first %d is the stream number, second %d is the 278 // total number of streams, %s is the stream description. 279 _("Stream %d / %d: %s"), 280 pipeline->stream + 1, pipeline->file->get_streams(), desc.c_str() 281 ).utf8_str() 282 ); 283 } else if (!error_string.empty()) { 284 desc = std::string( 285 // TRANSLATORS: first %s is the error message, second %s is stream description. 286 wxString::Format(_("%s: %s"), error_string.c_str(), desc.c_str()).utf8_str() 287 ); 288 } 289 290 return desc; 291 } 292 293 int spek_pipeline_streams(const struct spek_pipeline *pipeline) 294 { 295 return pipeline->file->get_streams(); 296 } 297 298 int spek_pipeline_channels(const struct spek_pipeline *pipeline) 299 { 300 return pipeline->file->get_channels(); 301 } 302 303 double spek_pipeline_duration(const struct spek_pipeline *pipeline) 304 { 305 return pipeline->file->get_duration(); 306 } 307 308 int spek_pipeline_sample_rate(const struct spek_pipeline *pipeline) 309 { 310 return pipeline->file->get_sample_rate(); 311 } 312 313 static void * reader_func(void *pp) 314 { 315 struct spek_pipeline *p = (spek_pipeline*)pp; 316 317 p->has_worker_thread = !pthread_create(&p->worker_thread, NULL, &worker_func, p); 318 if (!p->has_worker_thread) { 319 return NULL; 320 } 321 322 int pos = 0, prev_pos = 0; 323 int len; 324 while ((len = p->file->read()) > 0) { 325 if (p->quit) break; 326 327 const float *buffer = p->file->get_buffer(); 328 while (len-- > 0) { 329 p->input[pos] = *buffer++; 330 pos = (pos + 1) % p->input_size; 331 332 // Wake up the worker if we have enough data. 333 if ((pos > prev_pos ? pos : pos + p->input_size) - prev_pos == p->nfft * NFFT) { 334 reader_sync(p, prev_pos = pos); 335 } 336 } 337 assert(len == -1); 338 } 339 340 if (pos != prev_pos) { 341 // Process the remaining data. 342 reader_sync(p, pos); 343 } 344 345 // Force the worker to quit. 346 reader_sync(p, -1); 347 pthread_join(p->worker_thread, NULL); 348 349 // Notify the client. 350 p->cb(p->fft->get_output_size(), -1, NULL, p->cb_data); 351 return NULL; 352 } 353 354 static void reader_sync(struct spek_pipeline *p, int pos) 355 { 356 pthread_mutex_lock(&p->reader_mutex); 357 while (!p->worker_done) { 358 pthread_cond_wait(&p->reader_cond, &p->reader_mutex); 359 } 360 p->worker_done = false; 361 pthread_mutex_unlock(&p->reader_mutex); 362 363 pthread_mutex_lock(&p->worker_mutex); 364 p->input_pos = pos; 365 pthread_cond_signal(&p->worker_cond); 366 pthread_mutex_unlock(&p->worker_mutex); 367 } 368 369 static float get_window(enum window_function f, int i, float *coss, int n) { 370 switch (f) { 371 case WINDOW_HANN: 372 return 0.5f * (1.0f - coss[i]); 373 case WINDOW_HAMMING: 374 return 0.53836f - 0.46164f * coss[i]; 375 case WINDOW_BLACKMAN_HARRIS: 376 return 0.35875f - 0.48829f * coss[i] + 0.14128f * coss[2*i % n] - 0.01168f * coss[3*i % n]; 377 default: 378 assert(false); 379 return 0.0f; 380 } 381 } 382 383 static void * worker_func(void *pp) 384 { 385 struct spek_pipeline *p = (spek_pipeline*)pp; 386 387 int sample = 0; 388 int64_t frames = 0; 389 int64_t num_fft = 0; 390 int64_t acc_error = 0; 391 int head = 0, tail = 0; 392 int prev_head = 0; 393 394 memset(p->output, 0, sizeof(float) * p->fft->get_output_size()); 395 396 while (true) { 397 pthread_mutex_lock(&p->reader_mutex); 398 p->worker_done = true; 399 pthread_cond_signal(&p->reader_cond); 400 pthread_mutex_unlock(&p->reader_mutex); 401 402 pthread_mutex_lock(&p->worker_mutex); 403 while (tail == p->input_pos) { 404 pthread_cond_wait(&p->worker_cond, &p->worker_mutex); 405 } 406 tail = p->input_pos; 407 pthread_mutex_unlock(&p->worker_mutex); 408 409 if (tail == -1) { 410 return NULL; 411 } 412 413 while (true) { 414 head = (head + 1) % p->input_size; 415 if (head == tail) { 416 head = prev_head; 417 break; 418 } 419 frames++; 420 421 // If we have enough frames for an FFT or we have 422 // all frames required for the interval run and FFT. 423 bool int_full = 424 acc_error < p->file->get_error_base() && 425 frames == p->file->get_frames_per_interval(); 426 bool int_over = 427 acc_error >= p->file->get_error_base() && 428 frames == 1 + p->file->get_frames_per_interval(); 429 430 if (frames % p->nfft == 0 || ((int_full || int_over) && num_fft == 0)) { 431 prev_head = head; 432 for (int i = 0; i < p->nfft; i++) { 433 float val = p->input[(p->input_size + head - p->nfft + i) % p->input_size]; 434 val *= get_window(p->window_function, i, p->coss, p->nfft); 435 p->fft->set_input(i, val); 436 } 437 p->fft->execute(); 438 num_fft++; 439 for (int i = 0; i < p->fft->get_output_size(); i++) { 440 p->output[i] += p->fft->get_output(i); 441 } 442 } 443 444 // Do we have the FFTs for one interval? 445 if (int_full || int_over) { 446 if (int_over) { 447 acc_error -= p->file->get_error_base(); 448 } else { 449 acc_error += p->file->get_error_per_interval(); 450 } 451 452 for (int i = 0; i < p->fft->get_output_size(); i++) { 453 p->output[i] /= num_fft; 454 } 455 456 if (sample == p->samples) break; 457 p->cb(p->fft->get_output_size(), sample++, p->output, p->cb_data); 458 459 memset(p->output, 0, sizeof(float) * p->fft->get_output_size()); 460 frames = 0; 461 num_fft = 0; 462 } 463 } 464 } 465 }