From 4286f4433b9c233932b7af7b8a0cd89da84b643f Mon Sep 17 00:00:00 2001 From: Rafael Dantas Date: Wed, 14 Oct 2020 21:17:17 +0100 Subject: [PATCH 1/5] Improving error handling on pesq_measure - cypesq will now call cypesq_retvals and raise an exception if the returned value is negative - cypesq_retvals will not crash-and-burn if no utterances are detected in the buffer - pesq_measure will now return early and override the Error_Flag with its own values instead of exposing internal Error_Flags - pow_of exit(1)'s were changed to asserts - Adding vIM buffer files to .gitignore --- .gitignore | 4 + pesq/cypesq.pyx | 72 +++++++++++- pesq/pesq.h | 12 +- pesq/pesqmain.h | 292 ++++++++++++++++++++++++------------------------ pesq/pesqmod.c | 32 +++--- 5 files changed, 245 insertions(+), 167 deletions(-) diff --git a/.gitignore b/.gitignore index c903f03..bf7521b 100644 --- a/.gitignore +++ b/.gitignore @@ -125,3 +125,7 @@ dmypy.json # Pyre type checker .pyre/ + +# vIM buffer files +*.swp +*.swo diff --git a/pesq/cypesq.pyx b/pesq/cypesq.pyx index 0ac0d0c..0e6ed84 100644 --- a/pesq/cypesq.pyx +++ b/pesq/cypesq.pyx @@ -5,7 +5,30 @@ #Python Wrapper for PESQ Score (narrow band and wide band) import cython -cimport numpy as np +cimport numpy as np + +cpdef enum PesqErrorCode: + SUCCESS = 0, + UNKNOWN = -1, + INVALID_SAMPLE_RATE = -2, + OUT_OF_MEMORY = -3, + BUFFER_TOO_SHORT = -4, + NO_UTTERANCES_DETECTED = -5 + +class PesqError(RuntimeError): + pass + +class InvalidSampleRateError(PesqError): + pass + +class OutOfMemoryError(PesqError): + pass + +class BufferTooShortError(PesqError): + pass + +class NoUtterancesError(PesqError): + pass cdef extern from "pesq.h": DEF MAXNUTTERANCES = 50 @@ -49,14 +72,26 @@ cdef extern from "pesqio.h": cdef extern from "pesqmain.h": cdef void pesq_measure(SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, ERROR_INFO * err_info, long * Error_Flag, char ** Error_Type) +# Well, Globals are evil, but this is just storing the last message. +# It should be ok-ish. +cdef char* last_error_message = "unknown"; + +cpdef char* cypesq_last_error_message(): + return last_error_message -cpdef object cypesq(long sample_rate, np.ndarray[float, ndim=1, mode="c"] ref_data, np.ndarray[float, ndim=1, mode="c"] deg_data, int mode): +cpdef object cypesq_retvals(long sample_rate, + np.ndarray[float, ndim=1, mode="c"] ref_data, + np.ndarray[float, ndim=1, mode="c"] deg_data, + int mode): # select rate cdef long error_flag = 0; cdef char * error_type = "unknown"; + select_rate(sample_rate, &error_flag, &error_type) if error_flag != 0: - return -1 + last_error_message = error_type # They are all literals, this is not a leak (probably) + return PesqErrorCode.INVALID_SAMPLE_RATE + # assign signal cdef long length_ref cdef long length_deg @@ -107,8 +142,35 @@ cpdef object cypesq(long sample_rate, np.ndarray[float, ndim=1, mode="c"] ref_da err_info.mode = WB_MODE pesq_measure(&ref_info, °_info, &err_info, &error_flag, &error_type); - if error_flag!=0: - return -1 + if error_flag != 0: + last_error_message = error_type + return error_flag + return err_info.mapped_mos +cpdef object cypesq(long sample_rate, + np.ndarray[float, ndim=1, mode="c"] ref_data, + np.ndarray[float, ndim=1, mode="c"] deg_data, + int mode): + cdef object ret = cypesq_retvals(sample_rate, ref_data, deg_data, mode) + + # Null and Positive are valid values. + if ret >= 0: + return ret + + elif ret == PesqErrorCode.INVALID_SAMPLE_RATE: + raise InvalidSampleRateError(last_error_message) + + elif ret == PesqErrorCode.OUT_OF_MEMORY: + raise OutOfMemoryError(last_error_message) + + elif ret == PesqErrorCode.BUFFER_TOO_SHORT: + raise BufferTooShortError(last_error_message) + + elif ret == PesqErrorCode.NO_UTTERANCES_DETECTED: + raise NoUtterancesError(last_error_message) + + # Raise unknown otherwise + else: + raise PesqError(last_error_message) diff --git a/pesq/pesq.h b/pesq/pesq.h index d6869a7..0b8dbcf 100644 --- a/pesq/pesq.h +++ b/pesq/pesq.h @@ -234,6 +234,12 @@ extern long Align_Nfft_16k; #ifndef PESQ_H #define PESQ_H +#define PESQ_ERROR_UNKNOWN -1 +#define PESQ_ERROR_INVALID_SAMPLE_RATE -2 +#define PESQ_ERROR_OUT_OF_MEMORY -3 +#define PESQ_ERROR_BUFFER_TOO_SHORT -4 +#define PESQ_ERROR_NO_UTTERANCES_DETECTED -5 + typedef struct { char path_name[512]; char file_name [128]; @@ -320,8 +326,9 @@ void split_align( SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, long * Best_ED2, long * Best_D2, float * Best_DC2, long * Best_BP ); void pesq_psychoacoustic_model( -SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, -ERROR_INFO * err_info, float * ftmp); + SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, + ERROR_INFO * err_info, long * Error_Flag, char ** Error_Type, + float * ftmp); void apply_pesq( float * x_data, float * ref_surf, float * y_data, float * deg_surf, long NVAD_windows, float * ftmp, ERROR_INFO * err_info ); @@ -359,5 +366,6 @@ ERROR_INFO * err_info ); #ifndef A_WEIGHT #define A_WEIGHT 0.0309 #endif + /* END OF FILE */ diff --git a/pesq/pesqmain.h b/pesq/pesqmain.h index c9596fc..bf424b4 100644 --- a/pesq/pesqmain.h +++ b/pesq/pesqmain.h @@ -257,7 +257,7 @@ float WB_InIIR_Hsos_8k[LINIIR] = { long WB_InIIR_Nsos_16k = 1L; float WB_InIIR_Hsos_16k[LINIIR] = { 2.740826f, -5.4816519f, 2.740826f, -1.9444777f, 0.94597794f }; - + void pesq_measure (SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, ERROR_INFO * err_info, long * Error_Flag, char ** Error_Type) { @@ -271,168 +271,175 @@ void pesq_measure (SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, deg_info-> VAD = NULL; deg_info-> logVAD = NULL; - if ((*Error_Flag) == 0){ -// printf ("Reading reference file %s...", ref_info-> path_name); + // If Error_Flag was already set to something different than zero, + // Report unknown error. In practice shouldn't happen, as the function that + // calls this function returns if its Error_Flag wasn't zero. + if (*Error_Flag != 0) { + *Error_Flag = PESQ_ERROR_UNKNOWN; + // We're rewriting the flag, but keeping the Error_Type for later + return; + } - load_src (Error_Flag, Error_Type, ref_info); - // if ((*Error_Flag) == 0){} - // printf ("done.\n"); + // Load Reference Buffer + load_src(Error_Flag, Error_Type, ref_info); + if (*Error_Flag != 0) { + *Error_Flag = PESQ_ERROR_OUT_OF_MEMORY; + return; } - if ((*Error_Flag) == 0) - { -// printf ("Reading degraded file %s...", deg_info-> path_name); - load_src (Error_Flag, Error_Type, deg_info); - // if ((*Error_Flag) == 0){} - // printf ("done.\n"); + // Load Degraded Buffer + load_src(Error_Flag, Error_Type, deg_info); + if (*Error_Flag != 0) { + *Error_Flag = PESQ_ERROR_OUT_OF_MEMORY; + return; } - if (((ref_info-> Nsamples - 2 * SEARCHBUFFER * Downsample < Fs / 4) || - (deg_info-> Nsamples - 2 * SEARCHBUFFER * Downsample < Fs / 4)) && - ((*Error_Flag) == 0)) + // If one of the buffers has less than 1/4 of a second, return error. + if ((ref_info-> Nsamples - 2 * SEARCHBUFFER * Downsample < Fs / 4) || + (deg_info-> Nsamples - 2 * SEARCHBUFFER * Downsample < Fs / 4)) { - (*Error_Flag) = 2; - (*Error_Type) = "Reference or Degraded below 1/4 second - processing stopped "; + *Error_Flag = PESQ_ERROR_BUFFER_TOO_SHORT; + *Error_Type = "Reference or Degraded below 1/4 second - processing stopped "; + return; } - if ((*Error_Flag) == 0) - { - alloc_other (ref_info, deg_info, Error_Flag, Error_Type, &ftmp); + // Allocate temporary storage + alloc_other(ref_info, deg_info, Error_Flag, Error_Type, &ftmp); + if (*Error_Flag != 0) { + *Error_Flag = PESQ_ERROR_OUT_OF_MEMORY; + return; } - if ((*Error_Flag) == 0) - { - int maxNsamples = max (ref_info-> Nsamples, deg_info-> Nsamples); - float * model_ref; - float * model_deg; - long i; - // FILE *resultsFile; - - // printf (" Level normalization...\n"); - fix_power_level (ref_info, "reference", maxNsamples); - fix_power_level (deg_info, "degraded", maxNsamples); - - // printf (" IRS filtering...\n"); - if( Fs == 16000 ) { - WB_InIIR_Nsos = WB_InIIR_Nsos_16k; - WB_InIIR_Hsos = WB_InIIR_Hsos_16k; - } else { - WB_InIIR_Nsos = WB_InIIR_Nsos_8k; - WB_InIIR_Hsos = WB_InIIR_Hsos_8k; - } - if( ref_info->input_filter == 1 ) { - apply_filter (ref_info-> data, ref_info-> Nsamples, 26, standard_IRS_filter_dB); - } else { - for( i = 0; i < 16; i++ ) { - ref_info->data[SEARCHBUFFER * Downsample + i - 1] - *= (float)i / 16.0f; - ref_info->data[ref_info->Nsamples - SEARCHBUFFER * Downsample - i] - *= (float)i / 16.0f; - } - IIRFilt( WB_InIIR_Hsos, WB_InIIR_Nsos, NULL, - ref_info->data + SEARCHBUFFER * Downsample, - ref_info->Nsamples - 2 * SEARCHBUFFER * Downsample, NULL ); - } - if( deg_info->input_filter == 1 ) { - apply_filter (deg_info-> data, deg_info-> Nsamples, 26, standard_IRS_filter_dB); - } else { - for( i = 0; i < 16; i++ ) { - deg_info->data[SEARCHBUFFER * Downsample + i - 1] - *= (float)i / 16.0f; - deg_info->data[deg_info->Nsamples - SEARCHBUFFER * Downsample - i] - *= (float)i / 16.0f; - } - IIRFilt( WB_InIIR_Hsos, WB_InIIR_Nsos, NULL, - deg_info->data + SEARCHBUFFER * Downsample, - deg_info->Nsamples - 2 * SEARCHBUFFER * Downsample, NULL ); - } + int maxNsamples = max (ref_info-> Nsamples, deg_info-> Nsamples); + float * model_ref; + float * model_deg; + long i; + // FILE *resultsFile; - model_ref = (float *) safe_malloc ((ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000)) * sizeof (float)); - model_deg = (float *) safe_malloc ((deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000)) * sizeof (float)); + // printf (" Level normalization...\n"); + fix_power_level (ref_info, "reference", maxNsamples); + fix_power_level (deg_info, "degraded", maxNsamples); - for (i = 0; i < ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { - model_ref [i] = ref_info-> data [i]; + // printf (" IRS filtering...\n"); + if( Fs == 16000 ) { + WB_InIIR_Nsos = WB_InIIR_Nsos_16k; + WB_InIIR_Hsos = WB_InIIR_Hsos_16k; + } else { + WB_InIIR_Nsos = WB_InIIR_Nsos_8k; + WB_InIIR_Hsos = WB_InIIR_Hsos_8k; + } + if( ref_info->input_filter == 1 ) { + apply_filter (ref_info-> data, ref_info-> Nsamples, 26, standard_IRS_filter_dB); + } else { + for( i = 0; i < 16; i++ ) { + ref_info->data[SEARCHBUFFER * Downsample + i - 1] + *= (float)i / 16.0f; + ref_info->data[ref_info->Nsamples - SEARCHBUFFER * Downsample - i] + *= (float)i / 16.0f; } - - for (i = 0; i < deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { - model_deg [i] = deg_info-> data [i]; + IIRFilt( WB_InIIR_Hsos, WB_InIIR_Nsos, NULL, + ref_info->data + SEARCHBUFFER * Downsample, + ref_info->Nsamples - 2 * SEARCHBUFFER * Downsample, NULL ); + } + if( deg_info->input_filter == 1 ) { + apply_filter (deg_info-> data, deg_info-> Nsamples, 26, standard_IRS_filter_dB); + } else { + for( i = 0; i < 16; i++ ) { + deg_info->data[SEARCHBUFFER * Downsample + i - 1] + *= (float)i / 16.0f; + deg_info->data[deg_info->Nsamples - SEARCHBUFFER * Downsample - i] + *= (float)i / 16.0f; } - - input_filter( ref_info, deg_info, ftmp ); + IIRFilt( WB_InIIR_Hsos, WB_InIIR_Nsos, NULL, + deg_info->data + SEARCHBUFFER * Downsample, + deg_info->Nsamples - 2 * SEARCHBUFFER * Downsample, NULL ); + } - // printf (" Variable delay compensation...\n"); - calc_VAD (ref_info); - calc_VAD (deg_info); - - crude_align (ref_info, deg_info, err_info, WHOLE_SIGNAL, ftmp); + model_ref = (float *) safe_malloc ((ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000)) * sizeof (float)); + model_deg = (float *) safe_malloc ((deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000)) * sizeof (float)); - utterance_locate (ref_info, deg_info, err_info, ftmp); + for (i = 0; i < ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { + model_ref [i] = ref_info-> data [i]; + } + + for (i = 0; i < deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { + model_deg [i] = deg_info-> data [i]; + } + + input_filter( ref_info, deg_info, ftmp ); + + // printf (" Variable delay compensation...\n"); + calc_VAD (ref_info); + calc_VAD (deg_info); + crude_align (ref_info, deg_info, err_info, WHOLE_SIGNAL, ftmp); + + utterance_locate (ref_info, deg_info, err_info, ftmp); + + for (i = 0; i < ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { + ref_info-> data [i] = model_ref [i]; + } + + for (i = 0; i < deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { + deg_info-> data [i] = model_deg [i]; + } + + safe_free (model_ref); + safe_free (model_deg); + + if (ref_info-> Nsamples < deg_info-> Nsamples) { + float *new_ref = (float *) safe_malloc((deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000)) * sizeof(float)); + long i; for (i = 0; i < ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { - ref_info-> data [i] = model_ref [i]; + new_ref [i] = ref_info-> data [i]; } - - for (i = 0; i < deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { - deg_info-> data [i] = model_deg [i]; + for (i = ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); + i < deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { + new_ref [i] = 0.0f; } - - safe_free (model_ref); - safe_free (model_deg); - - if ((*Error_Flag) == 0) { - if (ref_info-> Nsamples < deg_info-> Nsamples) { - float *new_ref = (float *) safe_malloc((deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000)) * sizeof(float)); - long i; - for (i = 0; i < ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { - new_ref [i] = ref_info-> data [i]; - } - for (i = ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); - i < deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { - new_ref [i] = 0.0f; - } - safe_free (ref_info-> data); - ref_info-> data = new_ref; - new_ref = NULL; - } else { - if (ref_info-> Nsamples > deg_info-> Nsamples) { - float *new_deg = (float *) safe_malloc((ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000)) * sizeof(float)); - long i; - for (i = 0; i < deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { - new_deg [i] = deg_info-> data [i]; - } - for (i = deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); - i < ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { - new_deg [i] = 0.0f; - } - safe_free (deg_info-> data); - deg_info-> data = new_deg; - new_deg = NULL; - } + safe_free (ref_info-> data); + ref_info-> data = new_ref; + new_ref = NULL; + } else { + if (ref_info-> Nsamples > deg_info-> Nsamples) { + float *new_deg = (float *) safe_malloc((ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000)) * sizeof(float)); + long i; + for (i = 0; i < deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { + new_deg [i] = deg_info-> data [i]; } - } + for (i = deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); + i < ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { + new_deg [i] = 0.0f; + } + safe_free (deg_info-> data); + deg_info-> data = new_deg; + new_deg = NULL; + } + } - // printf (" Acoustic model processing...\n"); - pesq_psychoacoustic_model (ref_info, deg_info, err_info, ftmp); - - safe_free (ref_info-> data); - safe_free (ref_info-> VAD); - safe_free (ref_info-> logVAD); - safe_free (deg_info-> data); - safe_free (deg_info-> VAD); - safe_free (deg_info-> logVAD); - safe_free (ftmp); - - if ( err_info->mode == NB_MODE ) // narrow band - { - // printf("narrow\n"); - err_info->mapped_mos = 0.999f+4.0f/(1.0f+(float)exp((-1.4945f*err_info->pesq_mos+4.6607f))); - } - else // wide band - { - // printf("wide\n"); - err_info->mapped_mos = 0.999f+4.0f/(1.0f+(float)exp((-1.3669f*err_info->pesq_mos+3.8224f))); - err_info->pesq_mos = -1.0; - } + pesq_psychoacoustic_model(ref_info, deg_info, err_info, Error_Flag, Error_Type, ftmp); + // We're not checking Error_Flag and returning here as we still need to do + // some clean-up before returning. + + safe_free (ref_info-> data); + safe_free (ref_info-> VAD); + safe_free (ref_info-> logVAD); + safe_free (deg_info-> data); + safe_free (deg_info-> VAD); + safe_free (deg_info-> logVAD); + safe_free (ftmp); + + if ( err_info->mode == NB_MODE ) // narrow band + { + // printf("narrow\n"); + err_info->mapped_mos = 0.999f+4.0f/(1.0f+(float)exp((-1.4945f*err_info->pesq_mos+4.6607f))); + } + else // wide band + { + // printf("wide\n"); + err_info->mapped_mos = 0.999f+4.0f/(1.0f+(float)exp((-1.3669f*err_info->pesq_mos+3.8224f))); + err_info->pesq_mos = -1.0; + } // resultsFile = fopen (ITU_RESULTS_FILE, "at"); // @@ -472,9 +479,6 @@ void pesq_measure (SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, // // fclose (resultsFile); // } - - } - } /* END OF FILE */ diff --git a/pesq/pesqmod.c b/pesq/pesqmod.c index 60e4ce7..e4ad009 100644 --- a/pesq/pesqmod.c +++ b/pesq/pesqmod.c @@ -102,6 +102,7 @@ Further information is also available from www.pesq.org *****************************************************************************/ +#include #include #include #include "pesq.h" @@ -584,17 +585,12 @@ void multiply_with_asymmetry_factor (float *disturbance_dens, } double pow_of (const float * const x, long start_sample, long stop_sample, long divisor) { + assert(start_sample >= 0); + assert(start_sample <= stop_sample); + long i; double power = 0; - if (start_sample < 0) { - exit (1); - } - - if (start_sample > stop_sample) { - exit (1); - } - for (i = start_sample; i < stop_sample; i++) { float h = x [i]; power += h * h; @@ -767,10 +763,12 @@ float integral_of (float *x, long frames_after_start) { #define DEBUG_FR 0 -void pesq_psychoacoustic_model(SIGNAL_INFO * ref_info, - SIGNAL_INFO * deg_info, - ERROR_INFO * err_info, - float * ftmp) +void pesq_psychoacoustic_model(SIGNAL_INFO * ref_info, + SIGNAL_INFO * deg_info, + ERROR_INFO * err_info, + long * Error_Flag, + char ** Error_Type, + float * ftmp) { long maxNsamples = max (ref_info-> Nsamples, deg_info-> Nsamples); @@ -858,8 +856,9 @@ void pesq_psychoacoustic_model(SIGNAL_INFO * ref_info, abs_thresh_power = abs_thresh_power_16k; break; default: - printf ("Invalid sample frequency!\n"); - exit (1); + *Error_Flag = PESQ_ERROR_INVALID_SAMPLE_RATE; + *Error_Type = "Invalid sample frequency!\n"; + return; } samples_to_skip_at_start = 0; @@ -943,8 +942,9 @@ void pesq_psychoacoustic_model(SIGNAL_INFO * ref_info, short_term_fft (Nf, ref_info, Whanning, start_sample_ref, hz_spectrum_ref, fft_tmp); if (err_info-> Nutterances < 1) { - printf ("Processing error!\n"); - exit (1); + *Error_Flag = PESQ_ERROR_NO_UTTERANCES_DETECTED; + *Error_Type = "No utterances!\n"; + return; } utt = err_info-> Nutterances - 1; From d80d32e8fd2b0c9654ebbede2e7e813b56933396 Mon Sep 17 00:00:00 2001 From: Rafael Dantas Date: Wed, 14 Oct 2020 22:21:53 +0100 Subject: [PATCH 2/5] Exporting the new API --- pesq/__init__.py | 34 ++++++++++++++++++++++++++++++---- pesq/cypesq.pyx | 22 +++++++++++++--------- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/pesq/__init__.py b/pesq/__init__.py index 7606862..60c2a51 100644 --- a/pesq/__init__.py +++ b/pesq/__init__.py @@ -3,14 +3,18 @@ # Python Wrapper for PESQ Score (narrow band and wide band) import numpy as np -from .cypesq import cypesq +from .cypesq import cypesq, cypesq_retvals, cypesq_last_error_message + +class PesqOnError: + RAISE_EXCEPTION = 0 + RETURN_VALUES = 1 USAGE = """ Run model on reference ref and degraded deg Sample rate (fs) - No default. Must select either 8000 or 16000. Note there is narrow band (nb) mode only when sampling rate is 8000Hz. """ -def pesq(fs, ref, deg, mode): +def pesq(fs, ref, deg, mode, on_error=PesqOnError.RAISE_EXCEPTION): """ Args: ref: numpy 1D array, reference audio signal @@ -23,14 +27,36 @@ def pesq(fs, ref, deg, mode): if mode != 'wb' and mode != 'nb': print(USAGE) raise ValueError("mode should be either 'nb' or 'wb'") + if fs != 8000 and fs != 16000: print(USAGE) raise ValueError("fs (sampling frequency) should be either 8000 or 16000") + if fs == 8000 and mode == 'wb': print(USAGE) raise ValueError("no wide band mode if fs = 8000") + maxval = max(np.max(np.abs(ref/1.0)), np.max(np.abs(deg/1.0))) + if mode == 'wb': - return cypesq(fs, (ref/maxval).astype(np.float32), (deg/maxval).astype(np.float32), 1) - return cypesq(fs, (ref/maxval).astype(np.float32), (deg/maxval).astype(np.float32), 0) + mode_code = 1 + else: + mode_code = 0 + + if on_error == PesqOnError.RETURN_VALUES: + return cypesq_retvals( + fs, + (ref/maxval).astype(np.float32), + (deg/maxval).astype(np.float32), + mode_code + ) + else: + return cypesq( + fs, + (ref/maxval).astype(np.float32), + (deg/maxval).astype(np.float32), + mode_code + ) +def pesq_last_error_message(): + return cypesq_last_error_message() diff --git a/pesq/cypesq.pyx b/pesq/cypesq.pyx index 0e6ed84..ac77468 100644 --- a/pesq/cypesq.pyx +++ b/pesq/cypesq.pyx @@ -76,6 +76,10 @@ cdef extern from "pesqmain.h": # It should be ok-ish. cdef char* last_error_message = "unknown"; +cdef void set_last_error_message(char* msg): + global last_error_message + last_error_message = msg + cpdef char* cypesq_last_error_message(): return last_error_message @@ -89,7 +93,8 @@ cpdef object cypesq_retvals(long sample_rate, select_rate(sample_rate, &error_flag, &error_type) if error_flag != 0: - last_error_message = error_type # They are all literals, this is not a leak (probably) + # They are all literals, this is not a leak (probably) + set_last_error_message(error_type) return PesqErrorCode.INVALID_SAMPLE_RATE # assign signal @@ -143,7 +148,7 @@ cpdef object cypesq_retvals(long sample_rate, pesq_measure(&ref_info, °_info, &err_info, &error_flag, &error_type); if error_flag != 0: - last_error_message = error_type + set_last_error_message(error_type) return error_flag return err_info.mapped_mos @@ -157,20 +162,19 @@ cpdef object cypesq(long sample_rate, # Null and Positive are valid values. if ret >= 0: return ret - - elif ret == PesqErrorCode.INVALID_SAMPLE_RATE: + + if ret == PesqErrorCode.INVALID_SAMPLE_RATE: raise InvalidSampleRateError(last_error_message) - elif ret == PesqErrorCode.OUT_OF_MEMORY: + if ret == PesqErrorCode.OUT_OF_MEMORY: raise OutOfMemoryError(last_error_message) - elif ret == PesqErrorCode.BUFFER_TOO_SHORT: + if ret == PesqErrorCode.BUFFER_TOO_SHORT: raise BufferTooShortError(last_error_message) - elif ret == PesqErrorCode.NO_UTTERANCES_DETECTED: + if ret == PesqErrorCode.NO_UTTERANCES_DETECTED: raise NoUtterancesError(last_error_message) # Raise unknown otherwise - else: - raise PesqError(last_error_message) + raise PesqError(last_error_message) From 73ee30ec2a8a83d2b854dc8cf3cc2dbe582478f1 Mon Sep 17 00:00:00 2001 From: Rafael Dantas Date: Wed, 14 Oct 2020 22:43:36 +0100 Subject: [PATCH 3/5] Exporting the error classes --- pesq/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pesq/__init__.py b/pesq/__init__.py index 60c2a51..a558187 100644 --- a/pesq/__init__.py +++ b/pesq/__init__.py @@ -4,6 +4,8 @@ import numpy as np from .cypesq import cypesq, cypesq_retvals, cypesq_last_error_message +from .cypesq import PesqError, InvalidSampleRateError, OutOfMemoryError +from .cypesq import BufferTooShortError, NoUtterancesError class PesqOnError: RAISE_EXCEPTION = 0 From 5f3cd2a32e96860073138f7c07f61e59b3c82299 Mon Sep 17 00:00:00 2001 From: Rafael Dantas Date: Thu, 15 Oct 2020 23:06:02 +0100 Subject: [PATCH 4/5] Test implementation and plugging possible leaks --- pesq/__init__.py | 13 ++------ pesq/cypesq.pyx | 77 ++++++++++++++++++++++++++++------------------ pesq/pesq.h | 9 ++++-- pesq/pesqmain.h | 37 ++++++++++++---------- tests/test_pesq.py | 34 ++++++++++++++++++-- 5 files changed, 109 insertions(+), 61 deletions(-) mode change 100644 => 100755 tests/test_pesq.py diff --git a/pesq/__init__.py b/pesq/__init__.py index a558187..eb4d85b 100644 --- a/pesq/__init__.py +++ b/pesq/__init__.py @@ -3,20 +3,16 @@ # Python Wrapper for PESQ Score (narrow band and wide band) import numpy as np -from .cypesq import cypesq, cypesq_retvals, cypesq_last_error_message +from .cypesq import cypesq, cypesq_retvals, cypesq_error_message as pesq_error_message from .cypesq import PesqError, InvalidSampleRateError, OutOfMemoryError from .cypesq import BufferTooShortError, NoUtterancesError -class PesqOnError: - RAISE_EXCEPTION = 0 - RETURN_VALUES = 1 - USAGE = """ Run model on reference ref and degraded deg Sample rate (fs) - No default. Must select either 8000 or 16000. Note there is narrow band (nb) mode only when sampling rate is 8000Hz. """ -def pesq(fs, ref, deg, mode, on_error=PesqOnError.RAISE_EXCEPTION): +def pesq(fs, ref, deg, mode, on_error=PesqError.RAISE_EXCEPTION): """ Args: ref: numpy 1D array, reference audio signal @@ -45,7 +41,7 @@ def pesq(fs, ref, deg, mode, on_error=PesqOnError.RAISE_EXCEPTION): else: mode_code = 0 - if on_error == PesqOnError.RETURN_VALUES: + if on_error == PesqError.RETURN_VALUES: return cypesq_retvals( fs, (ref/maxval).astype(np.float32), @@ -59,6 +55,3 @@ def pesq(fs, ref, deg, mode, on_error=PesqOnError.RAISE_EXCEPTION): (deg/maxval).astype(np.float32), mode_code ) - -def pesq_last_error_message(): - return cypesq_last_error_message() diff --git a/pesq/cypesq.pyx b/pesq/cypesq.pyx index ac77468..bd33667 100644 --- a/pesq/cypesq.pyx +++ b/pesq/cypesq.pyx @@ -7,16 +7,20 @@ import cython cimport numpy as np -cpdef enum PesqErrorCode: - SUCCESS = 0, - UNKNOWN = -1, - INVALID_SAMPLE_RATE = -2, - OUT_OF_MEMORY = -3, - BUFFER_TOO_SHORT = -4, - NO_UTTERANCES_DETECTED = -5 - class PesqError(RuntimeError): - pass + # Error Return Values + SUCCESS = 0 + UNKNOWN = -1 + INVALID_SAMPLE_RATE = -2 + OUT_OF_MEMORY_REF = -3 + OUT_OF_MEMORY_DEG = -4 + OUT_OF_MEMORY_TMP = -5 + BUFFER_TOO_SHORT = -6 + NO_UTTERANCES_DETECTED = -7 + + # On Error Type + RAISE_EXCEPTION = 0 + RETURN_VALUES = 1 class InvalidSampleRateError(PesqError): pass @@ -30,6 +34,28 @@ class BufferTooShortError(PesqError): class NoUtterancesError(PesqError): pass +cdef char** cypesq_error_messages = [ + "Success", + "Unknown", + "Invalid sampling rate", + "Unable to allocate memory for reference buffer", + "Unable to allocate memory for degraded buffer", + "Unable to allocate memory for temporary buffer", + "Buffer needs to be at least 1/4 of a second long", + "No utterances detected" +] + +cpdef char* cypesq_error_message(int code): + global cypesq_error_messages + + if code > PesqError.SUCCESS: + code = PesqError.SUCCESS + + if code < PesqError.NO_UTTERANCES_DETECTED: + code = PesqError.UNKNOWN + + return cypesq_error_messages[-code] + cdef extern from "pesq.h": DEF MAXNUTTERANCES = 50 DEF NB_MODE = 0 @@ -72,16 +98,7 @@ cdef extern from "pesqio.h": cdef extern from "pesqmain.h": cdef void pesq_measure(SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, ERROR_INFO * err_info, long * Error_Flag, char ** Error_Type) -# Well, Globals are evil, but this is just storing the last message. -# It should be ok-ish. -cdef char* last_error_message = "unknown"; -cdef void set_last_error_message(char* msg): - global last_error_message - last_error_message = msg - -cpdef char* cypesq_last_error_message(): - return last_error_message cpdef object cypesq_retvals(long sample_rate, np.ndarray[float, ndim=1, mode="c"] ref_data, @@ -94,8 +111,7 @@ cpdef object cypesq_retvals(long sample_rate, select_rate(sample_rate, &error_flag, &error_type) if error_flag != 0: # They are all literals, this is not a leak (probably) - set_last_error_message(error_type) - return PesqErrorCode.INVALID_SAMPLE_RATE + return PesqError.INVALID_SAMPLE_RATE # assign signal cdef long length_ref @@ -148,7 +164,6 @@ cpdef object cypesq_retvals(long sample_rate, pesq_measure(&ref_info, °_info, &err_info, &error_flag, &error_type); if error_flag != 0: - set_last_error_message(error_type) return error_flag return err_info.mapped_mos @@ -163,18 +178,20 @@ cpdef object cypesq(long sample_rate, if ret >= 0: return ret - if ret == PesqErrorCode.INVALID_SAMPLE_RATE: - raise InvalidSampleRateError(last_error_message) + cdef char* error_message = cypesq_error_message(ret) + + if ret == PesqError.INVALID_SAMPLE_RATE: + raise InvalidSampleRateError(error_message) - if ret == PesqErrorCode.OUT_OF_MEMORY: - raise OutOfMemoryError(last_error_message) + if ret in [ PesqError.OUT_OF_MEMORY_REF, PesqError.OUT_OF_MEMORY_DEG, PesqError.OUT_OF_MEMORY_TMP ]: + raise OutOfMemoryError(error_message) - if ret == PesqErrorCode.BUFFER_TOO_SHORT: - raise BufferTooShortError(last_error_message) + if ret == PesqError.BUFFER_TOO_SHORT: + raise BufferTooShortError(error_message) - if ret == PesqErrorCode.NO_UTTERANCES_DETECTED: - raise NoUtterancesError(last_error_message) + if ret == PesqError.NO_UTTERANCES_DETECTED: + raise NoUtterancesError(error_message) # Raise unknown otherwise - raise PesqError(last_error_message) + raise PesqError(error_message) diff --git a/pesq/pesq.h b/pesq/pesq.h index 0b8dbcf..e5d257b 100644 --- a/pesq/pesq.h +++ b/pesq/pesq.h @@ -234,11 +234,14 @@ extern long Align_Nfft_16k; #ifndef PESQ_H #define PESQ_H +#define PESQ_ERROR_SUCCESS 0 #define PESQ_ERROR_UNKNOWN -1 #define PESQ_ERROR_INVALID_SAMPLE_RATE -2 -#define PESQ_ERROR_OUT_OF_MEMORY -3 -#define PESQ_ERROR_BUFFER_TOO_SHORT -4 -#define PESQ_ERROR_NO_UTTERANCES_DETECTED -5 +#define PESQ_ERROR_OUT_OF_MEMORY_REF -3 +#define PESQ_ERROR_OUT_OF_MEMORY_DEG -4 +#define PESQ_ERROR_OUT_OF_MEMORY_TMP -5 +#define PESQ_ERROR_BUFFER_TOO_SHORT -6 +#define PESQ_ERROR_NO_UTTERANCES_DETECTED -7 typedef struct { char path_name[512]; diff --git a/pesq/pesqmain.h b/pesq/pesqmain.h index bf424b4..526d085 100644 --- a/pesq/pesqmain.h +++ b/pesq/pesqmain.h @@ -277,21 +277,21 @@ void pesq_measure (SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, if (*Error_Flag != 0) { *Error_Flag = PESQ_ERROR_UNKNOWN; // We're rewriting the flag, but keeping the Error_Type for later - return; + goto cleanup; } // Load Reference Buffer load_src(Error_Flag, Error_Type, ref_info); if (*Error_Flag != 0) { - *Error_Flag = PESQ_ERROR_OUT_OF_MEMORY; - return; + *Error_Flag = PESQ_ERROR_OUT_OF_MEMORY_REF; + goto cleanup; } // Load Degraded Buffer load_src(Error_Flag, Error_Type, deg_info); if (*Error_Flag != 0) { - *Error_Flag = PESQ_ERROR_OUT_OF_MEMORY; - return; + *Error_Flag = PESQ_ERROR_OUT_OF_MEMORY_DEG; + goto cleanup; } // If one of the buffers has less than 1/4 of a second, return error. @@ -300,14 +300,14 @@ void pesq_measure (SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, { *Error_Flag = PESQ_ERROR_BUFFER_TOO_SHORT; *Error_Type = "Reference or Degraded below 1/4 second - processing stopped "; - return; + goto cleanup; } // Allocate temporary storage alloc_other(ref_info, deg_info, Error_Flag, Error_Type, &ftmp); if (*Error_Flag != 0) { - *Error_Flag = PESQ_ERROR_OUT_OF_MEMORY; - return; + *Error_Flag = PESQ_ERROR_OUT_OF_MEMORY_TMP; + goto cleanup; } int maxNsamples = max (ref_info-> Nsamples, deg_info-> Nsamples); @@ -421,14 +421,6 @@ void pesq_measure (SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, // We're not checking Error_Flag and returning here as we still need to do // some clean-up before returning. - safe_free (ref_info-> data); - safe_free (ref_info-> VAD); - safe_free (ref_info-> logVAD); - safe_free (deg_info-> data); - safe_free (deg_info-> VAD); - safe_free (deg_info-> logVAD); - safe_free (ftmp); - if ( err_info->mode == NB_MODE ) // narrow band { // printf("narrow\n"); @@ -441,6 +433,19 @@ void pesq_measure (SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, err_info->pesq_mos = -1.0; } +cleanup: + // Ensuring we're avoiding a double-free scenario + if (ref_info->data != NULL) { safe_free(ref_info->data); } + if (ref_info->VAD != NULL) { safe_free(ref_info->VAD); } + if (ref_info->logVAD != NULL) { safe_free(ref_info->logVAD); } + if (deg_info->data != NULL) { safe_free(deg_info->data); } + if (deg_info->VAD != NULL) { safe_free(deg_info->VAD); } + if (deg_info->logVAD != NULL) { safe_free(deg_info->logVAD); } + + if (ftmp != NULL) { + safe_free(ftmp); + } + // resultsFile = fopen (ITU_RESULTS_FILE, "at"); // // if (resultsFile != NULL) { diff --git a/tests/test_pesq.py b/tests/test_pesq.py old mode 100644 new mode 100755 index b53259b..29511bc --- a/tests/test_pesq.py +++ b/tests/test_pesq.py @@ -1,9 +1,12 @@ -from pathlib import Path +#!/usr/bin/python3 +import pytest +import numpy as np import scipy.io.wavfile -from pesq import pesq +from pathlib import Path +from pesq import pesq, NoUtterancesError, PesqError def test(): data_dir = Path(__file__).parent.parent / 'audio' @@ -20,3 +23,30 @@ def test(): score = pesq(ref=ref, deg=deg, fs=sample_rate, mode='nb') assert score == 1.6072081327438354, score + +def test_no_utterances_nb_mode(): + SAMPLE_RATE = 8000 + silent_ref = np.zeros(SAMPLE_RATE) + deg = np.random.randn(SAMPLE_RATE) + + with pytest.raises(NoUtterancesError) as e: + pesq(ref=silent_ref, deg=deg, fs=SAMPLE_RATE, mode='nb') + + score = pesq(ref=silent_ref, deg=deg, fs=SAMPLE_RATE, mode='nb', + on_error=PesqError.RETURN_VALUES) + + assert score == PesqError.NO_UTTERANCES_DETECTED, score + +def test_no_utterances_wb_mode(): + SAMPLE_RATE = 16000 + silent_ref = np.zeros(SAMPLE_RATE) + deg = np.random.randn(SAMPLE_RATE) + + with pytest.raises(NoUtterancesError) as e: + pesq(ref=silent_ref, deg=deg, fs=SAMPLE_RATE, mode='wb') + + score = pesq(ref=silent_ref, deg=deg, fs=SAMPLE_RATE, mode='wb', + on_error=PesqError.RETURN_VALUES) + + assert score == PesqError.NO_UTTERANCES_DETECTED, score + From 3e3962f33528c365b820ba7542e13eb6706dd23a Mon Sep 17 00:00:00 2001 From: "Rafael G. Dantas" Date: Sat, 14 Nov 2020 21:40:48 +0000 Subject: [PATCH 5/5] Removing unnecessary hashbang There is no need for it. --- tests/test_pesq.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_pesq.py b/tests/test_pesq.py index 29511bc..ebdfe79 100755 --- a/tests/test_pesq.py +++ b/tests/test_pesq.py @@ -1,5 +1,3 @@ -#!/usr/bin/python3 - import pytest import numpy as np import scipy.io.wavfile