spek

Acoustic spectrum analyser https://github.com/alexkay/spek spek.cc
git clone http://git.hanabi.in/repos/spek.git
Log | Files | Refs | README

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 }