├── .github └── workflows │ └── ci-tests.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── audio ├── speech.wav └── speech_bab_0dB.wav ├── pesq ├── __init__.py ├── _pesq.py ├── cypesq.pyx ├── dsp.c ├── dsp.h ├── pesq.h ├── pesqdsp.c ├── pesqio.h ├── pesqmain.h ├── pesqmod.c └── pesqpar.h ├── setup.py └── tests ├── requirements.txt └── test_pesq.py /.github/workflows/ci-tests.yml: -------------------------------------------------------------------------------- 1 | name: Testing 2 | 3 | on: 4 | push: 5 | branches: ["master"] 6 | tags: ["*"] 7 | pull_request: 8 | branches: ["master"] 9 | 10 | defaults: 11 | run: 12 | shell: bash 13 | 14 | jobs: 15 | pytester: 16 | runs-on: ubuntu-20.04 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | python-version: ["3.8", "3.9", "3.10"] 21 | timeout-minutes: 20 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: actions/setup-python@v5 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | 28 | - name: Install dependencies 29 | run: | 30 | pip install -e . -r tests/requirements.txt 31 | 32 | - name: Run tests 33 | run: | 34 | pytest tests/ 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pesq/cypesq.c 2 | 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # celery beat schedule file 97 | celerybeat-schedule 98 | 99 | # SageMath parsed files 100 | *.sage.py 101 | 102 | # Environments 103 | .env 104 | .venv 105 | env/ 106 | venv/ 107 | ENV/ 108 | env.bak/ 109 | venv.bak/ 110 | 111 | # Spyder project settings 112 | .spyderproject 113 | .spyproject 114 | 115 | # Rope project settings 116 | .ropeproject 117 | 118 | # mkdocs documentation 119 | /site 120 | 121 | # mypy 122 | .mypy_cache/ 123 | .dmypy.json 124 | dmypy.json 125 | 126 | # Pyre type checker 127 | .pyre/ 128 | 129 | # vIM buffer files 130 | *.swp 131 | *.swo 132 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `pesq` 2 | 3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 4 | 5 | The following is a set of guidelines for contributing to `pesq`, which are hosted in the [PESQ](https://github.com/ludlows/PESQ) on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 6 | 7 | ## I don't want to read this whole thing I just have a question!!! 8 | 9 | > **Note:** Please don't file an issue to ask a question. You'll get faster results by using the resources below. 10 | 11 | We have an official message board with a detailed FAQ and where the community chimes in with helpful advice if you have questions. 12 | 13 | * [Github Discussions, the official `pesq` message board](https://github.com/ludlows/PESQ/discussions) 14 | 15 | ## What should I know before I get started? 16 | 17 | ### PESQ Standard 18 | 19 | Content here will be enriched later. 20 | 21 | 22 | ### How to use `pesq` 23 | 24 | Content here will be enriched later. 25 | 26 | ## How Can I Contribute? 27 | 28 | ### Reporting Bugs 29 | 30 | This section guides you through submitting a bug report for Atom. Following these guidelines helps maintainers and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related reports :mag_right:. 31 | 32 | Before creating bug reports, please first search your questions in the existing issues (both **open** and **closed** ones) as you might find out that you don't need to create one. 33 | 34 | > **Note:** If you find a **Closed** issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one. 35 | 36 | 37 | #### How Do I Submit A (Good) Bug Report? 38 | 39 | Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). 40 | 41 | Explain the problem and include additional details to help maintainers reproduce the problem: 42 | 43 | * **Use a clear and descriptive title** for the issue to identify the problem. 44 | * **Describe the exact steps which reproduce the problem** in as many details as possible. 45 | * **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). 46 | * **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. 47 | * **Explain which behavior you expected to see instead and why.** 48 | * **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. If you use the keyboard while following the steps, **record the GIF with the [Keybinding Resolver](https://github.com/atom/keybinding-resolver) shown**. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. 49 | * **If you're reporting that `pesq` crashed**, include a crash report with a stack trace from the operating system. On macOS, the crash report will be available in `Console.app` under "Diagnostic and usage information" > "User diagnostic reports". Include the crash report in the issue in a [code block](https://help.github.com/articles/markdown-basics/#multiple-lines), a [file attachment](https://help.github.com/articles/file-attachments-on-issues-and-pull-requests/), or put it in a [gist](https://gist.github.com/) and provide link to that gist. 50 | * **If the problem is related to performance or memory**, include a [CPU profile capture](https://flight-manual.atom.io/hacking-atom/sections/debugging/#diagnose-runtime-performance) with your report. 51 | 52 | Provide more context by answering these questions: 53 | 54 | * **Did the problem start happening recently** (e.g. after updating to a new version of `pesq`) or was this always a problem? 55 | * If the problem started happening recently, **can you reproduce the problem in an older version of `pesq`?** What's the most recent version in which the problem doesn't happen? 56 | * **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens. 57 | * If the problem is related to working with files (e.g. opening and editing files), **does the problem happen for all files and projects or only some?** Does the problem happen only when working with local or remote files (e.g. on network drives), with files of a specific type (e.g. .wav & .mp3 files), with large files or files with very long lines, or with files in a specific encoding? Is there anything else special about the files you are using? 58 | 59 | Include details about your configuration and environment: 60 | 61 | * **Which version of `pesq` are you using?** 62 | * **Are you running `pesq` in a virtual machine?** If so, which VM software are you using and which operating systems and versions are used for the host and the guest? 63 | 64 | 65 | ### Pull Requests 66 | 67 | The process described here has several goals: 68 | 69 | - Maintain `pesq`'s quality 70 | - Fix problems that are important to users 71 | - Engage the community in working toward the best possible `pesq` 72 | - Enable a sustainable system for `pesq`'s maintainers to review contributions 73 | 74 | Please follow these steps to have your contribution considered by the maintainers: 75 | 76 | 1. Please first raise the PR on the `dev` branch. We will merge it into `master` branch after testing. 77 | 2. Follow the common good coding styes for Python & C programming language. 78 | 3. After you submit your pull request, verify that all [status checks](https://help.github.com/articles/about-status-checks/) are passing
What if the status checks are failing?If a status check is failing, and you believe that the failure is unrelated to your change, please leave a comment on the pull request explaining why you believe the failure is unrelated. A maintainer will re-run the status check for you. If we conclude that the failure was a false positive, then we will open an issue to track that problem with our status check suite.
79 | 80 | While the prerequisites above must be satisfied prior to having your pull request reviewed, the reviewer(s) may ask you to complete additional design work, tests, or other changes before your pull request can be ultimately accepted. 81 | 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ludlows 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pesq 2 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.6549559.svg)](https://doi.org/10.5281/zenodo.6549559) 3 | [![Downloads](https://pepy.tech/badge/pesq)](https://pepy.tech/project/pesq) 4 | [![Downloads](https://pepy.tech/badge/pesq/month)](https://pepy.tech/project/pesq) 5 | 6 | PESQ (Perceptual Evaluation of Speech Quality) Wrapper for Python Users 7 | 8 | This code is designed for numpy array specially. 9 | 10 | # Requirements 11 | 12 | C compiler 13 | numpy 14 | cython 15 | 16 | 17 | # Install with pip 18 | 19 | ```bash 20 | # PyPi Repository 21 | $ pip install pesq 22 | 23 | # The Latest Version 24 | $ pip install https://github.com/ludlows/python-pesq/archive/master.zip 25 | ``` 26 | 27 | # Usage for narrowband and wideband Modes 28 | 29 | Please note that the sampling rate (frequency) should be 16000 or 8000 (Hz). 30 | 31 | And using 8000Hz is supported for narrowband only. 32 | 33 | The code supports error-handling behaviors now. 34 | 35 | ```python 36 | def pesq(fs, ref, deg, mode='wb', on_error=PesqError.RAISE_EXCEPTION): 37 | """ 38 | Args: 39 | ref: numpy 1D array, reference audio signal 40 | deg: numpy 1D array, degraded audio signal 41 | fs: integer, sampling rate 42 | mode: 'wb' (wide-band) or 'nb' (narrow-band) 43 | on_error: error-handling behavior, it could be PesqError.RETURN_VALUES or PesqError.RAISE_EXCEPTION by default 44 | Returns: 45 | pesq_score: float, P.862.2 Prediction (MOS-LQO) 46 | """ 47 | ``` 48 | Once you select `PesqError.RETURN_VALUES`, the `pesq` function will return -1 when an error occurs. 49 | 50 | Once you select `PesqError.RAISE_EXCEPTION`, the `pesq` function will raise an exception when an error occurs. 51 | 52 | It supports the following errors now: `InvalidSampleRateError`, `OutOfMemoryError`,`BufferTooShortError`,`NoUtterancesError`,`PesqError`(other unknown errors). 53 | 54 | ```python 55 | from scipy.io import wavfile 56 | from pesq import pesq 57 | 58 | rate, ref = wavfile.read("./audio/speech.wav") 59 | rate, deg = wavfile.read("./audio/speech_bab_0dB.wav") 60 | 61 | print(pesq(rate, ref, deg, 'wb')) 62 | print(pesq(rate, ref, deg, 'nb')) 63 | ``` 64 | 65 | # Usage for `multiprocessing` feature 66 | 67 | ```python 68 | def pesq_batch(fs, ref, deg, mode='wb', n_processor=None, on_error=PesqError.RAISE_EXCEPTION): 69 | """ 70 | Running `pesq` using multiple processors 71 | Args: 72 | on_error: 73 | ref: numpy 1D (n_sample,) or 2D array (n_file, n_sample), reference audio signal 74 | deg: numpy 1D (n_sample,) or 2D array (n_file, n_sample), degraded audio signal 75 | fs: integer, sampling rate 76 | mode: 'wb' (wide-band) or 'nb' (narrow-band) 77 | n_processor: cpu_count() (default) or number of processors (chosen by the user) or 0 (without multiprocessing) 78 | on_error: PesqError.RAISE_EXCEPTION (default) or PesqError.RETURN_VALUES 79 | Returns: 80 | pesq_score: list of pesq scores, P.862.2 Prediction (MOS-LQO) 81 | """ 82 | ``` 83 | this function uses `multiprocessing` features to boost time efficiency. 84 | 85 | When the `ref` is an 1-D numpy array and `deg` is a 2-D numpy array, the result of `pesq_batch` is identical to the value of `[pesq(fs, ref, deg[i,:],**kwargs) for i in range(deg.shape[0])]`. 86 | 87 | When the `ref` is a 2-D numpy array and `deg` is a 2-D numpy array, the result of `pesq_batch` is identical to the value of `[pesq(fs, ref[i,:], deg[i,:],**kwargs) for i in range(deg.shape[0])]`. 88 | 89 | 90 | # Correctness 91 | 92 | The correctness is verified by running samples in audio folder. 93 | 94 | PESQ computed by this code in wideband mode is 1.0832337141036987 95 | 96 | PESQ computed by this code in narrowband mode is 1.6072081327438354 97 | 98 | # Note 99 | 100 | Sampling rate (fs|rate) - No default. Must select either 8000Hz or 16000Hz. 101 | 102 | Note there is narrowband (nb) mode only when sampling rate is 8000Hz. 103 | 104 | The original C source code is modified. 105 | 106 | # Who is using `pesq` 107 | 108 | Please click [here](https://github.com/ludlows/python-pesq/network/dependents) to see these repositories, whose owners include `Facebook Research`, `SpeechBrain`, `NVIDIA` .etc. 109 | 110 | # Cite this code 111 | 112 | ``` 113 | @software{miao_wang_2022_6549559, 114 | author = {Miao Wang, Christoph Boeddeker, Rafael G. Dantas and ananda seelan}, 115 | title = {PESQ (Perceptual Evaluation of Speech Quality) Wrapper for Python Users}, 116 | month = may, 117 | year = 2022, 118 | publisher = {Zenodo}, 119 | version = {v0.0.4}, 120 | doi = {10.5281/zenodo.6549559}, 121 | url = {https://doi.org/10.5281/zenodo.6549559}} 122 | ``` 123 | 124 | # Acknowledgement 125 | 126 | This work was funded by the Natural Sciences and Engineering Research Council of Canada. 127 | 128 | This work was also funded by the Concordia University, Montreal, Canada. 129 | -------------------------------------------------------------------------------- /audio/speech.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ludlows/PESQ/af8fb1b636257aab77f1086e46bdeea281382369/audio/speech.wav -------------------------------------------------------------------------------- /audio/speech_bab_0dB.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ludlows/PESQ/af8fb1b636257aab77f1086e46bdeea281382369/audio/speech_bab_0dB.wav -------------------------------------------------------------------------------- /pesq/__init__.py: -------------------------------------------------------------------------------- 1 | # 2019-May 2 | # github.com/ludlows 3 | # Python Wrapper for PESQ Score (narrowband and wideband) 4 | 5 | from ._pesq import pesq, pesq_batch 6 | from ._pesq import PesqError, InvalidSampleRateError, OutOfMemoryError, BufferTooShortError, NoUtterancesError 7 | __all__ = ['pesq', 'pesq_batch', 8 | 'PesqError', 'InvalidSampleRateError', 'OutOfMemoryError', 'BufferTooShortError', 'NoUtterancesError'] 9 | -------------------------------------------------------------------------------- /pesq/_pesq.py: -------------------------------------------------------------------------------- 1 | # 2019-May 2 | # github.com/ludlows 3 | # Python Wrapper for PESQ Score (narrowband and wideband) 4 | 5 | import numpy as np 6 | from multiprocessing import Pool, Queue, Process, cpu_count 7 | from functools import partial 8 | from .cypesq import cypesq, cypesq_retvals, cypesq_error_message as pesq_error_message 9 | from .cypesq import PesqError, InvalidSampleRateError, OutOfMemoryError 10 | from .cypesq import BufferTooShortError, NoUtterancesError 11 | 12 | 13 | __all__ = ['pesq', 'pesq_batch', 'PesqError', 'InvalidSampleRateError', 'OutOfMemoryError', 'BufferTooShortError', 14 | 'NoUtterancesError'] 15 | 16 | USAGE = """ 17 | Run model on reference(ref) and degraded(deg) 18 | Sample rate (fs) - No default. Must select either 8000 or 16000. 19 | Note there is narrow band (nb) mode only when sampling rate is 8000Hz. 20 | """ 21 | 22 | USAGE_BATCH = USAGE + """ 23 | The shapes of ref and deg should be identical if both are 2D numpy arrays. 24 | Once the `ref` is 1D and the `deg` is 2D, the broadcast operation is applied. 25 | """ 26 | 27 | 28 | def _check_fs_mode(mode, fs, usage=USAGE): 29 | if mode != 'wb' and mode != 'nb': 30 | print(usage) 31 | raise ValueError("mode should be either 'nb' or 'wb'") 32 | 33 | if fs != 8000 and fs != 16000: 34 | print(usage) 35 | raise ValueError("fs (sampling frequency) should be either 8000 or 16000") 36 | 37 | if fs == 8000 and mode == 'wb': 38 | print(usage) 39 | raise ValueError("no wide band mode if fs = 8000") 40 | 41 | 42 | def _pesq_inner(ref, deg, fs=16000, mode='wb', on_error=PesqError.RAISE_EXCEPTION): 43 | """ 44 | Args: 45 | ref: numpy 1D array, reference audio signal 46 | deg: numpy 1D array, degraded audio signal 47 | fs: integer, sampling rate 48 | mode: 'wb' (wide-band) or 'nb' (narrow-band) 49 | on_error: PesqError.RAISE_EXCEPTION (default) or PesqError.RETURN_VALUES 50 | Returns: 51 | pesq_score: float, P.862.2 Prediction (MOS-LQO) 52 | """ 53 | max_val = max(np.max(np.abs(ref / 1.0)), np.max(np.abs(deg / 1.0))) 54 | if mode == 'wb': 55 | mode_code = 1 56 | else: 57 | mode_code = 0 58 | if on_error == PesqError.RETURN_VALUES: 59 | return cypesq_retvals( 60 | fs, 61 | (ref / max_val).astype(np.float32), 62 | (deg / max_val).astype(np.float32), 63 | mode_code 64 | ) 65 | return cypesq( 66 | fs, 67 | (ref / max_val).astype(np.float32), 68 | (deg / max_val).astype(np.float32), 69 | mode_code 70 | ) 71 | 72 | 73 | def _processor_coordinator(func, args_q, results_q): 74 | while True: 75 | index, arg = args_q.get() 76 | if index is None: 77 | break 78 | try: 79 | result = func(*arg) 80 | except Exception as e: 81 | result = e 82 | results_q.put((index, result)) 83 | 84 | 85 | def _processor_mapping(func, args, n_processor): 86 | args_q = Queue(maxsize=1) 87 | results_q = Queue() 88 | processors = [Process(target=_processor_coordinator, args=(func, args_q, results_q)) for _ in range(n_processor)] 89 | for p in processors: 90 | p.daemon = True 91 | p.start() 92 | for i, arg in enumerate(args): 93 | args_q.put((i, arg)) 94 | # send stop messages 95 | for _ in range(n_processor): 96 | args_q.put((None, None)) 97 | results = [results_q.get() for _ in range(len(args))] 98 | [p.join() for p in processors] 99 | return [v[1] for v in sorted(results)] 100 | 101 | 102 | def pesq(fs, ref, deg, mode='wb', on_error=PesqError.RAISE_EXCEPTION): 103 | """ 104 | Args: 105 | ref: numpy 1D array, reference audio signal 106 | deg: numpy 1D array, degraded audio signal 107 | fs: integer, sampling rate 108 | mode: 'wb' (wide-band) or 'nb' (narrow-band) 109 | on_error: PesqError.RAISE_EXCEPTION (default) or PesqError.RETURN_VALUES 110 | Returns: 111 | pesq_score: float, P.862.2 Prediction (MOS-LQO) 112 | """ 113 | _check_fs_mode(mode, fs, USAGE) 114 | return _pesq_inner(ref, deg, fs, mode, on_error) 115 | 116 | 117 | def pesq_batch(fs, ref, deg, mode, n_processor=cpu_count(), on_error=PesqError.RAISE_EXCEPTION): 118 | """ 119 | Running `pesq` using multiple processors 120 | Args: 121 | on_error: 122 | ref: numpy 1D (n_sample,) or 2D array (n_file, n_sample), reference audio signal 123 | deg: numpy 1D (n_sample,) or 2D array (n_file, n_sample), degraded audio signal 124 | fs: integer, sampling rate 125 | mode: 'wb' (wide-band) or 'nb' (narrow-band) 126 | n_processor: cpu_count() (default) or number of processors (chosen by the user) or 0 (without multiprocessing) 127 | on_error: PesqError.RAISE_EXCEPTION (default) or PesqError.RETURN_VALUES 128 | Returns: 129 | pesq_score: list of pesq scores, P.862.2 Prediction (MOS-LQO) 130 | """ 131 | _check_fs_mode(mode, fs, USAGE_BATCH) 132 | # check dimension 133 | if len(ref.shape) == 1: 134 | if len(deg.shape) == 1 and ref.shape == deg.shape: 135 | return [_pesq_inner(ref, deg, fs, mode, PesqError.RETURN_VALUES)] 136 | elif len(deg.shape) == 2 and ref.shape[-1] == deg.shape[-1]: 137 | if n_processor <= 0: 138 | pesq_score = [np.nan for i in range(deg.shape[0])] 139 | for i in range(deg.shape[0]): 140 | pesq_score[i] = _pesq_inner(ref, deg[i, :], fs, mode, on_error) 141 | return pesq_score 142 | else: 143 | with Pool(n_processor) as p: 144 | return p.map(partial(_pesq_inner, ref, fs=fs, mode=mode, on_error=on_error), 145 | [deg[i, :] for i in range(deg.shape[0])]) 146 | else: 147 | raise ValueError("The shapes of `deg` is invalid!") 148 | elif len(ref.shape) == 2: 149 | if deg.shape == ref.shape: 150 | if n_processor <= 0: 151 | pesq_score = [np.nan for i in range(deg.shape[0])] 152 | for i in range(deg.shape[0]): 153 | pesq_score[i] = _pesq_inner(ref[i, :], deg[i, :], fs, mode, on_error) 154 | return pesq_score 155 | else: 156 | return _processor_mapping(_pesq_inner, 157 | [(ref[i, :], deg[i, :], fs, mode, on_error) for i in range(deg.shape[0])], 158 | n_processor) 159 | else: 160 | raise ValueError("The shape of `deg` is invalid!") 161 | else: 162 | raise ValueError("The shape of `ref` should be either 1D or 2D!") 163 | -------------------------------------------------------------------------------- /pesq/cypesq.pyx: -------------------------------------------------------------------------------- 1 | # distutils: language=C 2 | 3 | #2019-May 4 | #github.com/ludlows 5 | #Python Wrapper for PESQ Score (narrow band and wide band) 6 | 7 | import cython 8 | cimport numpy as np 9 | 10 | class PesqError(RuntimeError): 11 | # Error Return Values 12 | SUCCESS = 0 13 | UNKNOWN = -1 14 | INVALID_SAMPLE_RATE = -2 15 | OUT_OF_MEMORY_REF = -3 16 | OUT_OF_MEMORY_DEG = -4 17 | OUT_OF_MEMORY_TMP = -5 18 | BUFFER_TOO_SHORT = -6 19 | NO_UTTERANCES_DETECTED = -7 20 | 21 | # On Error Type 22 | RAISE_EXCEPTION = 0 23 | RETURN_VALUES = 1 24 | 25 | class InvalidSampleRateError(PesqError): 26 | pass 27 | 28 | class OutOfMemoryError(PesqError): 29 | pass 30 | 31 | class BufferTooShortError(PesqError): 32 | pass 33 | 34 | class NoUtterancesError(PesqError): 35 | pass 36 | 37 | cdef char** cypesq_error_messages = [ 38 | "Success", 39 | "Unknown", 40 | "Invalid sampling rate", 41 | "Unable to allocate memory for reference buffer", 42 | "Unable to allocate memory for degraded buffer", 43 | "Unable to allocate memory for temporary buffer", 44 | "Buffer needs to be at least 1/4 of a second long", 45 | "No utterances detected" 46 | ] 47 | 48 | cpdef char* cypesq_error_message(int code): 49 | global cypesq_error_messages 50 | 51 | if code > PesqError.SUCCESS: 52 | code = PesqError.SUCCESS 53 | 54 | if code < PesqError.NO_UTTERANCES_DETECTED: 55 | code = PesqError.UNKNOWN 56 | 57 | return cypesq_error_messages[-code] 58 | 59 | cdef extern from "pesq.h": 60 | DEF MAXNUTTERANCES = 50 61 | DEF NB_MODE = 0 62 | DEF WB_MODE = 1 63 | 64 | ctypedef struct SIGNAL_INFO: 65 | char path_name[512] 66 | char file_name[128] 67 | long Nsamples 68 | long apply_swap 69 | long input_filter 70 | 71 | float * data 72 | float * VAD 73 | float * logVAD 74 | 75 | ctypedef struct ERROR_INFO: 76 | long Nutterances 77 | long Largest_uttsize 78 | long Nsurf_samples 79 | 80 | long Crude_DelayEst 81 | float Crude_DelayConf 82 | long UttSearch_Start[MAXNUTTERANCES] 83 | long UttSearch_End[MAXNUTTERANCES] 84 | long Utt_DelayEst[MAXNUTTERANCES] 85 | long Utt_Delay[MAXNUTTERANCES] 86 | float Utt_DelayConf[MAXNUTTERANCES] 87 | long Utt_Start[MAXNUTTERANCES] 88 | long Utt_End[MAXNUTTERANCES] 89 | 90 | float pesq_mos 91 | float mapped_mos 92 | short mode 93 | 94 | cdef extern from "pesqio.h": 95 | cdef void select_rate(long sample_rate, long * Error_Flag, char ** Error_Type) 96 | 97 | 98 | cdef extern from "pesqmain.h": 99 | cdef void pesq_measure(SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, ERROR_INFO * err_info, long * Error_Flag, char ** Error_Type) 100 | 101 | 102 | 103 | cpdef object cypesq_retvals(long sample_rate, 104 | np.ndarray[float, ndim=1, mode="c"] ref_data, 105 | np.ndarray[float, ndim=1, mode="c"] deg_data, 106 | int mode): 107 | # select rate 108 | cdef long error_flag = 0; 109 | cdef char * error_type = "unknown"; 110 | 111 | select_rate(sample_rate, &error_flag, &error_type) 112 | if error_flag != 0: 113 | # They are all literals, this is not a leak (probably) 114 | return PesqError.INVALID_SAMPLE_RATE 115 | 116 | # assign signal 117 | cdef long length_ref 118 | cdef long length_deg 119 | length_ref = ref_data.shape[0] 120 | length_deg = deg_data.shape[0] 121 | 122 | cdef SIGNAL_INFO ref_info, deg_info 123 | 124 | cdef char* ref_name = "reference-signal" 125 | cdef char* deg_name = "degrade-signal" 126 | 127 | cdef int i = 0; 128 | while ref_name[i]: 129 | ref_info.path_name[i] = ref_name[i] 130 | ref_info.file_name[i] = ref_name[i] 131 | i += 1 132 | 133 | ref_info.path_name[i] = 0 134 | ref_info.file_name[i] = 0 135 | 136 | i = 0 137 | while deg_name[i]: 138 | deg_info.path_name[i] = deg_name[i] 139 | deg_info.file_name[i] = deg_name[i] 140 | i += 1 141 | 142 | deg_info.path_name[i] = 0 143 | deg_info.file_name[i] = 0 144 | 145 | 146 | ref_info.Nsamples = length_ref 147 | ref_info.apply_swap = 0 148 | ref_info.input_filter = 1 149 | ref_info.data = &(ref_data[0]) 150 | 151 | 152 | deg_info.Nsamples = length_deg 153 | deg_info.apply_swap = 0 154 | deg_info.input_filter = 1 155 | deg_info.data = &(deg_data[0]) 156 | 157 | # assign error info 158 | cdef ERROR_INFO err_info 159 | err_info.mode = NB_MODE 160 | if mode == 1: 161 | ref_info.input_filter = 2 162 | deg_info.input_filter = 2 163 | err_info.mode = WB_MODE 164 | 165 | pesq_measure(&ref_info, °_info, &err_info, &error_flag, &error_type); 166 | if error_flag != 0: 167 | return error_flag 168 | 169 | return err_info.mapped_mos 170 | 171 | cpdef object cypesq(long sample_rate, 172 | np.ndarray[float, ndim=1, mode="c"] ref_data, 173 | np.ndarray[float, ndim=1, mode="c"] deg_data, 174 | int mode): 175 | cdef object ret = cypesq_retvals(sample_rate, ref_data, deg_data, mode) 176 | 177 | # Null and Positive are valid values. 178 | if ret >= 0: 179 | return ret 180 | 181 | cdef char* error_message = cypesq_error_message(ret) 182 | 183 | if ret == PesqError.INVALID_SAMPLE_RATE: 184 | raise InvalidSampleRateError(error_message) 185 | 186 | if ret in [ PesqError.OUT_OF_MEMORY_REF, PesqError.OUT_OF_MEMORY_DEG, PesqError.OUT_OF_MEMORY_TMP ]: 187 | raise OutOfMemoryError(error_message) 188 | 189 | if ret == PesqError.BUFFER_TOO_SHORT: 190 | raise BufferTooShortError(error_message) 191 | 192 | if ret == PesqError.NO_UTTERANCES_DETECTED: 193 | raise NoUtterancesError(error_message) 194 | 195 | # Raise unknown otherwise 196 | raise PesqError(error_message) 197 | 198 | -------------------------------------------------------------------------------- /pesq/dsp.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ludlows/PESQ/af8fb1b636257aab77f1086e46bdeea281382369/pesq/dsp.c -------------------------------------------------------------------------------- /pesq/dsp.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ludlows/PESQ/af8fb1b636257aab77f1086e46bdeea281382369/pesq/dsp.h -------------------------------------------------------------------------------- /pesq/pesq.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | 3 | Perceptual Evaluation of Speech Quality (PESQ) 4 | ITU-T Recommendations P.862, P.862.1, P.862.2. 5 | Version 2.0 - October 2005. 6 | 7 | **************************************** 8 | PESQ Intellectual Property Rights Notice 9 | **************************************** 10 | 11 | DEFINITIONS: 12 | ------------ 13 | For the purposes of this Intellectual Property Rights Notice 14 | the terms �Perceptual Evaluation of Speech Quality Algorithm� 15 | and �PESQ Algorithm� refer to the objective speech quality 16 | measurement algorithm defined in ITU-T Recommendation P.862; 17 | the term �PESQ Software� refers to the C-code component of P.862. 18 | These definitions also apply to those parts of ITU-T Recommendation 19 | P.862.2 and its associated source code that are common with P.862. 20 | 21 | NOTICE: 22 | ------- 23 | All copyright, trade marks, trade names, patents, know-how and 24 | all or any other intellectual rights subsisting in or used in 25 | connection with including all algorithms, documents and manuals 26 | relating to the PESQ Algorithm and or PESQ Software are and remain 27 | the sole property in law, ownership, regulations, treaties and 28 | patent rights of the Owners identified below. The user may not 29 | dispute or question the ownership of the PESQ Algorithm and 30 | or PESQ Software. 31 | 32 | OWNERS ARE: 33 | ----------- 34 | 35 | 1. British Telecommunications plc (BT), all rights assigned 36 | to Psytechnics Limited 37 | 2. Royal KPN NV, all rights assigned to OPTICOM GmbH 38 | 39 | RESTRICTIONS: 40 | ------------- 41 | 42 | The user cannot: 43 | 44 | 1. alter, duplicate, modify, adapt, or translate in whole or in 45 | part any aspect of the PESQ Algorithm and or PESQ Software 46 | 2. sell, hire, loan, distribute, dispose or put to any commercial 47 | use other than those permitted below in whole or in part any 48 | aspect of the PESQ Algorithm and or PESQ Software 49 | 50 | PERMITTED USE: 51 | -------------- 52 | 53 | The user may: 54 | 55 | 1. Use the PESQ Software to: 56 | i) understand the PESQ Algorithm; or 57 | ii) evaluate the ability of the PESQ Algorithm to perform 58 | its intended function of predicting the speech quality 59 | of a system; or 60 | iii) evaluate the computational complexity of the PESQ Algorithm, 61 | with the limitation that none of said evaluations or its 62 | results shall be used for external commercial use. 63 | 64 | 2. Use the PESQ Software to test if an implementation of the PESQ 65 | Algorithm conforms to ITU-T Recommendation P.862. 66 | 67 | 3. With the prior written permission of both Psytechnics Limited 68 | and OPTICOM GmbH, use the PESQ Software in accordance with the 69 | above Restrictions to perform work that meets all of the following 70 | criteria: 71 | i) the work must contribute directly to the maintenance of an 72 | existing ITU recommendation or the development of a new ITU 73 | recommendation under an approved ITU Study Item; and 74 | ii) the work and its results must be fully described in a 75 | written contribution to the ITU that is presented at a formal 76 | ITU meeting within one year of the start of the work; and 77 | iii) neither the work nor its results shall be put to any 78 | commercial use other than making said contribution to the ITU. 79 | Said permission will be provided on a case-by-case basis. 80 | 81 | 82 | ANY OTHER USE OR APPLICATION OF THE PESQ SOFTWARE AND/OR THE PESQ 83 | ALGORITHM WILL REQUIRE A PESQ LICENCE AGREEMENT, WHICH MAY BE OBTAINED 84 | FROM EITHER OPTICOM GMBH OR PSYTECHNICS LIMITED. 85 | 86 | EACH COMPANY OFFERS OEM LICENSE AGREEMENTS, WHICH COMBINE OEM 87 | IMPLEMENTATIONS OF THE PESQ ALGORITHM TOGETHER WITH A PESQ PATENT LICENSE 88 | AGREEMENT. PESQ PATENT-ONLY LICENSE AGREEMENTS MAY BE OBTAINED FROM OPTICOM. 89 | 90 | 91 | *********************************************************************** 92 | * OPTICOM GmbH * Psytechnics Limited * 93 | * Naegelsbachstr. 38, * Fraser House, 23 Museum Street, * 94 | * D- 91052 Erlangen, Germany * Ipswich IP1 1HN, England * 95 | * Phone: +49 (0) 9131 53020 0 * Phone: +44 (0) 1473 261 800 * 96 | * Fax: +49 (0) 9131 53020 20 * Fax: +44 (0) 1473 261 880 * 97 | * E-mail: info@opticom.de, * E-mail: info@psytechnics.com, * 98 | * www.opticom.de * www.psytechnics.com * 99 | *********************************************************************** 100 | 101 | Further information is also available from www.pesq.org 102 | 103 | *****************************************************************************/ 104 | 105 | #include 106 | #include 107 | 108 | #ifndef TRUE 109 | #define TRUE 1 110 | #endif 111 | 112 | #ifndef FALSE 113 | #define FALSE 0 114 | #endif 115 | 116 | #ifndef LINIIR 117 | #define LINIIR 60 118 | #endif 119 | 120 | #ifndef MAXNUTTERANCES 121 | #define MAXNUTTERANCES 50 122 | #endif 123 | 124 | #ifndef WHOLE_SIGNAL 125 | #define WHOLE_SIGNAL -1 126 | #endif 127 | 128 | #ifndef LSMJ 129 | #define LSMJ 20 130 | #endif 131 | 132 | #ifndef LFBANK 133 | #define LFBANK 35 134 | #endif 135 | 136 | #ifndef DATAPADDING_MSECS 137 | #define DATAPADDING_MSECS 320 138 | #endif 139 | 140 | #ifndef SEARCHBUFFER 141 | #define SEARCHBUFFER 75 142 | #endif 143 | 144 | 145 | #ifndef EPS 146 | #define EPS 1E-12 147 | #endif 148 | 149 | #ifndef MINSPEECHLGTH 150 | #define MINSPEECHLGTH 4 151 | #endif 152 | 153 | 154 | #ifndef JOINSPEECHLGTH 155 | #define JOINSPEECHLGTH 50 156 | #endif 157 | 158 | #ifndef MINUTTLENGTH 159 | #define MINUTTLENGTH 50 160 | #endif 161 | 162 | #ifndef SATDB 163 | #define SATDB 90.31 164 | #endif 165 | 166 | #ifndef FIXDB 167 | #define FIXDB -32.0 168 | #endif 169 | 170 | #ifndef TWOPI 171 | #define TWOPI 6.28318530717959 172 | #endif 173 | 174 | extern int Nb ; 175 | 176 | #define Nfmax 512 177 | 178 | #define Sp_8k 2.764344e-5 179 | #define Sl_8k 1.866055e-1 180 | 181 | #define Sp_16k 6.910853e-006 182 | #define Sl_16k 1.866055e-001 183 | 184 | extern float Sp; 185 | extern float Sl; 186 | 187 | #define Dz 0.312 188 | 189 | #define gamma 0.001 190 | 191 | #define Tl 10000.0f 192 | 193 | #define Ts 10000000.0f 194 | 195 | #define Tt 0.02f 196 | 197 | #define Tn 0.01f 198 | 199 | 200 | #ifndef min 201 | #define min(a,b) (((a) < (b)) ? (a) : (b)) 202 | #endif 203 | 204 | #ifndef max 205 | #define max(a,b) (((a) > (b)) ? (a) : (b)) 206 | #endif 207 | 208 | #ifndef NB_MODE 209 | #define NB_MODE 0 210 | #endif 211 | #ifndef WB_MODE 212 | #define WB_MODE 1 213 | #endif 214 | 215 | extern long Fs; 216 | extern long Downsample; 217 | extern float * InIIR_Hsos; 218 | extern long Align_Nfft; 219 | extern long InIIR_Nsos; 220 | 221 | 222 | extern long Fs_8k; 223 | extern long Downsample_8k; 224 | 225 | 226 | extern long InIIR_Nsos_8k; 227 | extern long Align_Nfft_8k; 228 | 229 | extern long Fs_16k; 230 | extern long Downsample_16k; 231 | extern long InIIR_Nsos_16k; 232 | extern long Align_Nfft_16k; 233 | 234 | #ifndef PESQ_H 235 | #define PESQ_H 236 | 237 | #define PESQ_ERROR_SUCCESS 0 238 | #define PESQ_ERROR_UNKNOWN -1 239 | #define PESQ_ERROR_INVALID_SAMPLE_RATE -2 240 | #define PESQ_ERROR_OUT_OF_MEMORY_REF -3 241 | #define PESQ_ERROR_OUT_OF_MEMORY_DEG -4 242 | #define PESQ_ERROR_OUT_OF_MEMORY_TMP -5 243 | #define PESQ_ERROR_BUFFER_TOO_SHORT -6 244 | #define PESQ_ERROR_NO_UTTERANCES_DETECTED -7 245 | 246 | typedef struct { 247 | char path_name[512]; 248 | char file_name [128]; 249 | long Nsamples; 250 | long apply_swap; 251 | long input_filter; 252 | 253 | float * data; 254 | float * VAD; 255 | float * logVAD; 256 | } SIGNAL_INFO; 257 | 258 | typedef struct { 259 | long Nutterances; 260 | long Largest_uttsize; 261 | long Nsurf_samples; 262 | 263 | long Crude_DelayEst; 264 | float Crude_DelayConf; 265 | long UttSearch_Start[MAXNUTTERANCES]; 266 | long UttSearch_End[MAXNUTTERANCES]; 267 | long Utt_DelayEst[MAXNUTTERANCES]; 268 | long Utt_Delay[MAXNUTTERANCES]; 269 | float Utt_DelayConf[MAXNUTTERANCES]; 270 | long Utt_Start[MAXNUTTERANCES]; 271 | long Utt_End[MAXNUTTERANCES]; 272 | 273 | float pesq_mos; 274 | float mapped_mos; 275 | 276 | short mode; 277 | 278 | } ERROR_INFO; 279 | 280 | 281 | 282 | 283 | 284 | void input_filter( 285 | SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, float * ftmp ); 286 | void apply_filters( float * data, long Nsamples ); 287 | void make_stereo_file (char *, SIGNAL_INFO *, SIGNAL_INFO *); 288 | void make_stereo_file2 (char *, SIGNAL_INFO *, float *); 289 | void select_rate( long sample_rate, 290 | long * Error_Flag, char ** Error_Type ); 291 | int file_exist( char * fname ); 292 | void load_src( long * Error_Flag, char ** Error_Type, 293 | SIGNAL_INFO * sinfo); 294 | void alloc_other( SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, 295 | long * Error_Flag, char ** Error_Type, float ** ftmp); 296 | void calc_VAD( SIGNAL_INFO * pinfo ); 297 | int id_searchwindows( SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, 298 | ERROR_INFO * err_info ); 299 | void id_utterances( SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, 300 | ERROR_INFO * err_info ); 301 | void utterance_split( SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, 302 | ERROR_INFO * err_info, float * ftmp ); 303 | void utterance_locate( SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, 304 | ERROR_INFO * err_info, float * ftmp ); 305 | void auditory_transform( SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, 306 | ERROR_INFO * err_info, long Utt_id, float * ftmp); 307 | void calc_err( SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, 308 | ERROR_INFO * err_info, long Utt_id); 309 | void extract_params( SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, 310 | ERROR_INFO * err_info, long Utt_id, float * ftmp ); 311 | void utterance_process(SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, 312 | ERROR_INFO * err_info, long Utt_id, float * ftmp); 313 | void DC_block( float * data, long Nsamples ); 314 | void apply_filter ( float * data, long Nsamples, int, double [][2] ); 315 | double pow_of (const float * const , long , long, long); 316 | void apply_VAD( 317 | SIGNAL_INFO * pinfo, float * data, float * VAD, float * logVAD ); 318 | void crude_align( 319 | SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, ERROR_INFO * err_info, 320 | long Utt_id, float * ftmp); 321 | void time_align( 322 | SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, ERROR_INFO * err_info, 323 | long Utt_id, float * ftmp ); 324 | void split_align( SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, 325 | ERROR_INFO * err_info, float * ftmp, 326 | long Utt_Start, long Utt_SpeechStart, long Utt_SpeechEnd, long Utt_End, 327 | long Utt_DelayEst, float Utt_DelayConf, 328 | long * Best_ED1, long * Best_D1, float * Best_DC1, 329 | long * Best_ED2, long * Best_D2, float * Best_DC2, 330 | long * Best_BP ); 331 | void pesq_psychoacoustic_model( 332 | SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, 333 | ERROR_INFO * err_info, long * Error_Flag, char ** Error_Type, 334 | float * ftmp); 335 | void apply_pesq( float * x_data, float * ref_surf, 336 | float * y_data, float * deg_surf, long NVAD_windows, float * ftmp, 337 | ERROR_INFO * err_info ); 338 | 339 | #endif //PESQ_H 340 | 341 | #ifndef D_POW_F 342 | #define D_POW_F 2 343 | #endif 344 | 345 | #ifndef D_POW_S 346 | #define D_POW_S 6 347 | #endif 348 | 349 | #ifndef D_POW_T 350 | #define D_POW_T 2 351 | #endif 352 | 353 | #ifndef A_POW_F 354 | #define A_POW_F 1 355 | #endif 356 | 357 | #ifndef A_POW_S 358 | #define A_POW_S 6 359 | #endif 360 | 361 | #ifndef A_POW_T 362 | #define A_POW_T 2 363 | #endif 364 | 365 | #ifndef D_WEIGHT 366 | #define D_WEIGHT 0.1 367 | #endif 368 | 369 | #ifndef A_WEIGHT 370 | #define A_WEIGHT 0.0309 371 | #endif 372 | 373 | /* END OF FILE */ 374 | 375 | -------------------------------------------------------------------------------- /pesq/pesqdsp.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ludlows/PESQ/af8fb1b636257aab77f1086e46bdeea281382369/pesq/pesqdsp.c -------------------------------------------------------------------------------- /pesq/pesqio.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | 3 | Perceptual Evaluation of Speech Quality (PESQ) 4 | ITU-T Recommendations P.862, P.862.1, P.862.2. 5 | Version 2.0 - October 2005. 6 | 7 | **************************************** 8 | PESQ Intellectual Property Rights Notice 9 | **************************************** 10 | 11 | DEFINITIONS: 12 | ------------ 13 | For the purposes of this Intellectual Property Rights Notice 14 | the terms �Perceptual Evaluation of Speech Quality Algorithm� 15 | and �PESQ Algorithm� refer to the objective speech quality 16 | measurement algorithm defined in ITU-T Recommendation P.862; 17 | the term �PESQ Software� refers to the C-code component of P.862. 18 | These definitions also apply to those parts of ITU-T Recommendation 19 | P.862.2 and its associated source code that are common with P.862. 20 | 21 | NOTICE: 22 | ------- 23 | All copyright, trade marks, trade names, patents, know-how and 24 | all or any other intellectual rights subsisting in or used in 25 | connection with including all algorithms, documents and manuals 26 | relating to the PESQ Algorithm and or PESQ Software are and remain 27 | the sole property in law, ownership, regulations, treaties and 28 | patent rights of the Owners identified below. The user may not 29 | dispute or question the ownership of the PESQ Algorithm and 30 | or PESQ Software. 31 | 32 | OWNERS ARE: 33 | ----------- 34 | 35 | 1. British Telecommunications plc (BT), all rights assigned 36 | to Psytechnics Limited 37 | 2. Royal KPN NV, all rights assigned to OPTICOM GmbH 38 | 39 | RESTRICTIONS: 40 | ------------- 41 | 42 | The user cannot: 43 | 44 | 1. alter, duplicate, modify, adapt, or translate in whole or in 45 | part any aspect of the PESQ Algorithm and or PESQ Software 46 | 2. sell, hire, loan, distribute, dispose or put to any commercial 47 | use other than those permitted below in whole or in part any 48 | aspect of the PESQ Algorithm and or PESQ Software 49 | 50 | PERMITTED USE: 51 | -------------- 52 | 53 | The user may: 54 | 55 | 1. Use the PESQ Software to: 56 | i) understand the PESQ Algorithm; or 57 | ii) evaluate the ability of the PESQ Algorithm to perform 58 | its intended function of predicting the speech quality 59 | of a system; or 60 | iii) evaluate the computational complexity of the PESQ Algorithm, 61 | with the limitation that none of said evaluations or its 62 | results shall be used for external commercial use. 63 | 64 | 2. Use the PESQ Software to test if an implementation of the PESQ 65 | Algorithm conforms to ITU-T Recommendation P.862. 66 | 67 | 3. With the prior written permission of both Psytechnics Limited 68 | and OPTICOM GmbH, use the PESQ Software in accordance with the 69 | above Restrictions to perform work that meets all of the following 70 | criteria: 71 | i) the work must contribute directly to the maintenance of an 72 | existing ITU recommendation or the development of a new ITU 73 | recommendation under an approved ITU Study Item; and 74 | ii) the work and its results must be fully described in a 75 | written contribution to the ITU that is presented at a formal 76 | ITU meeting within one year of the start of the work; and 77 | iii) neither the work nor its results shall be put to any 78 | commercial use other than making said contribution to the ITU. 79 | Said permission will be provided on a case-by-case basis. 80 | 81 | 82 | ANY OTHER USE OR APPLICATION OF THE PESQ SOFTWARE AND/OR THE PESQ 83 | ALGORITHM WILL REQUIRE A PESQ LICENCE AGREEMENT, WHICH MAY BE OBTAINED 84 | FROM EITHER OPTICOM GMBH OR PSYTECHNICS LIMITED. 85 | 86 | EACH COMPANY OFFERS OEM LICENSE AGREEMENTS, WHICH COMBINE OEM 87 | IMPLEMENTATIONS OF THE PESQ ALGORITHM TOGETHER WITH A PESQ PATENT LICENSE 88 | AGREEMENT. PESQ PATENT-ONLY LICENSE AGREEMENTS MAY BE OBTAINED FROM OPTICOM. 89 | 90 | 91 | *********************************************************************** 92 | * OPTICOM GmbH * Psytechnics Limited * 93 | * Naegelsbachstr. 38, * Fraser House, 23 Museum Street, * 94 | * D- 91052 Erlangen, Germany * Ipswich IP1 1HN, England * 95 | * Phone: +49 (0) 9131 53020 0 * Phone: +44 (0) 1473 261 800 * 96 | * Fax: +49 (0) 9131 53020 20 * Fax: +44 (0) 1473 261 880 * 97 | * E-mail: info@opticom.de, * E-mail: info@psytechnics.com, * 98 | * www.opticom.de * www.psytechnics.com * 99 | *********************************************************************** 100 | 101 | Further information is also available from www.pesq.org 102 | 103 | *****************************************************************************/ 104 | 105 | #include 106 | #include "pesq.h" 107 | #include "dsp.h" 108 | 109 | // extern int Nb; 110 | 111 | void make_stereo_file (char *stereo_path_name, SIGNAL_INFO *ref_info, SIGNAL_INFO *deg_info) { 112 | make_stereo_file2 (stereo_path_name, ref_info, deg_info-> data); 113 | } 114 | 115 | void make_stereo_file2 (char *stereo_path_name, SIGNAL_INFO *ref_info, float *deg) { 116 | 117 | long i; 118 | long h; 119 | short *buffer; 120 | FILE *outputFile; 121 | long n; 122 | 123 | n = ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000) - 2 * SEARCHBUFFER * Downsample; 124 | 125 | buffer = (short *) safe_malloc (2 * n * sizeof (short)); 126 | 127 | if ((outputFile = fopen (stereo_path_name, "wb")) == NULL) { 128 | printf ("MakeStereoFile : cannot open output file %s!", stereo_path_name); 129 | return; 130 | } 131 | 132 | for (i = 0; i < n; i++) { 133 | h = (int) ref_info-> data [SEARCHBUFFER * Downsample + i] / 2; 134 | if (h < -32767) h = -32767; 135 | if (h > 32767) h = 32767; 136 | h = (short) h; 137 | buffer [2*i] = (short) h; 138 | h = (int) deg [SEARCHBUFFER * Downsample + i] / 2; 139 | if (h < -32767) h = -32767; 140 | if (h > 32767) h = 32767; 141 | h = (short) h; 142 | buffer [2*i + 1] = (short) h; 143 | } 144 | 145 | fwrite (buffer, sizeof (short) * 2, n, outputFile); 146 | 147 | fclose (outputFile); 148 | safe_free (buffer); 149 | } 150 | 151 | extern float InIIR_Hsos_16k []; 152 | extern float InIIR_Hsos_8k []; 153 | extern long InIIR_Nsos; 154 | 155 | void select_rate( long sample_rate, long * Error_Flag, char ** Error_Type ) 156 | { 157 | if( Fs == sample_rate ) 158 | return; 159 | if( Fs_16k == sample_rate ) 160 | { 161 | Fs = Fs_16k; 162 | Downsample = Downsample_16k; 163 | InIIR_Hsos = InIIR_Hsos_16k; 164 | InIIR_Nsos = InIIR_Nsos_16k; 165 | Align_Nfft = Align_Nfft_16k; 166 | return; 167 | } 168 | if( Fs_8k == sample_rate ) 169 | { 170 | Fs = Fs_8k; 171 | Downsample = Downsample_8k; 172 | InIIR_Hsos = InIIR_Hsos_8k; 173 | InIIR_Nsos = InIIR_Nsos_8k; 174 | Align_Nfft = Align_Nfft_8k; 175 | return; 176 | } 177 | 178 | (*Error_Flag) = -1; 179 | (*Error_Type) = "Invalid sample rate specified"; 180 | } 181 | 182 | int file_exist( char * fname ) 183 | { 184 | FILE * fp = fopen( fname, "rb" ); 185 | if( fp == NULL ) 186 | return 0; 187 | else 188 | { 189 | fclose( fp ); 190 | return 1; 191 | } 192 | } 193 | 194 | void load_src( long * Error_Flag, char ** Error_Type, 195 | SIGNAL_INFO * sinfo) 196 | { 197 | // long name_len; 198 | // long file_size; 199 | // long header_size = 0; 200 | long Nsamples; 201 | // long to_read; 202 | long read_count; 203 | // long count; 204 | 205 | float *read_ptr; 206 | 207 | float *input_data; 208 | 209 | // short *p_input; 210 | // char s; 211 | // char *p_byte; 212 | //FILE *Src_file = fopen( sinfo-> path_name, "rb" ); 213 | 214 | // input_data = (short *) safe_malloc( 16384 * sizeof(short) ); 215 | input_data = (float *) safe_malloc( sinfo-> Nsamples * sizeof(float) ); 216 | memcpy( input_data, sinfo->data, sinfo-> Nsamples * sizeof(float) ); 217 | if( input_data == NULL ) 218 | { 219 | *Error_Flag = 1; 220 | *Error_Type = "Could not allocate storage for file reading"; 221 | printf ("%s!\n", *Error_Type); 222 | //fclose( Src_file ); 223 | return; 224 | } 225 | 226 | /* if( Src_file == NULL ) 227 | { 228 | *Error_Flag = 1; 229 | *Error_Type = "Could not open source file"; 230 | printf ("%s!\n", *Error_Type); 231 | safe_free( input_data ); 232 | return; 233 | } 234 | 235 | if( fseek( Src_file, 0L, SEEK_END ) != 0 ) 236 | { 237 | *Error_Flag = 1; 238 | *Error_Type = "Could not reach end of source file"; 239 | safe_free( input_data ); 240 | printf ("%s!\n", *Error_Type); 241 | fclose( Src_file ); 242 | return; 243 | } 244 | file_size = ftell( Src_file ); 245 | if( file_size < 0L ) 246 | { 247 | *Error_Flag = 1; 248 | *Error_Type = "Could not measure length of source file"; 249 | safe_free( input_data ); 250 | printf ("%s!\n", *Error_Type); 251 | fclose( Src_file ); 252 | return; 253 | } 254 | if( fseek( Src_file, 0L, SEEK_SET ) != 0 ) 255 | { 256 | *Error_Flag = 1; 257 | *Error_Type = "Could not reach start of source file"; 258 | safe_free( input_data ); 259 | printf ("%s!\n", *Error_Type); 260 | fclose( Src_file ); 261 | return; 262 | } 263 | name_len = strlen( sinfo-> path_name ); 264 | if( name_len > 4 ) 265 | { 266 | if( strcmp( sinfo-> path_name + name_len - 4, ".wav" ) == 0 ) 267 | header_size = 22; 268 | if( strcmp( sinfo-> path_name + name_len - 4, ".WAV" ) == 0 ) 269 | header_size = 22; 270 | if( strcmp( sinfo-> path_name + name_len - 4, ".raw" ) == 0 ) 271 | header_size = 0; 272 | if( strcmp( sinfo-> path_name + name_len - 4, ".src" ) == 0 ) 273 | header_size = 0; 274 | } 275 | if( name_len > 2 ) 276 | { 277 | if( strcmp( sinfo-> path_name + name_len - 2, ".s" ) == 0 ) 278 | header_size = 0; 279 | } 280 | 281 | if( header_size > 0 ) 282 | fread( input_data, 2, header_size, Src_file ); 283 | */ 284 | // Nsamples = (file_size / 2) - header_size; 285 | Nsamples = sinfo-> Nsamples; 286 | sinfo-> Nsamples = Nsamples + 2 * SEARCHBUFFER * Downsample; 287 | 288 | sinfo-> data = (float *) safe_malloc( (sinfo->Nsamples + DATAPADDING_MSECS * (Fs / 1000)) * sizeof(float) ); 289 | if( sinfo-> data == NULL ) 290 | { 291 | *Error_Flag = 1; 292 | *Error_Type = "Failed to allocate memory for source file"; 293 | safe_free( input_data ); 294 | printf ("%s!\n", *Error_Type); 295 | // fclose( Src_file ); 296 | return; 297 | } 298 | 299 | read_ptr = sinfo-> data; 300 | for( read_count = SEARCHBUFFER*Downsample; read_count > 0; read_count-- ){ 301 | *(read_ptr++) = 0.0f; 302 | } 303 | 304 | /*to_read = Nsamples; 305 | while( to_read > 16384 ) 306 | { 307 | read_count = fread( input_data, sizeof(short), 16384, Src_file ); 308 | if( read_count < 16384 ) 309 | { 310 | *Error_Flag = 1; 311 | *Error_Type = "Error reading source file."; 312 | printf ("%s!\n", *Error_Type); 313 | safe_free( input_data ); 314 | safe_free( sinfo-> data ); 315 | sinfo-> data = NULL; 316 | fclose( Src_file ); 317 | return; 318 | } 319 | if( sinfo-> apply_swap ) 320 | { 321 | p_byte = (char *)input_data; 322 | for( count = 0L; count < read_count; count++ ) 323 | { 324 | s = p_byte[count << 1]; 325 | p_byte[count << 1] = p_byte[(count << 1)+1]; 326 | p_byte[(count << 1)+1] = s; 327 | } 328 | } 329 | to_read -= read_count; 330 | p_input = input_data; 331 | while( read_count > 0 ) 332 | { 333 | read_count--; 334 | *(read_ptr++) = (float)(*(p_input++)); 335 | } 336 | } 337 | read_count = fread( input_data, sizeof(short), to_read, Src_file ); 338 | if( read_count < to_read ) 339 | { 340 | *Error_Flag = 1; 341 | *Error_Type = "Error reading source file"; 342 | printf ("%s!\n", *Error_Type); 343 | safe_free( input_data ); 344 | safe_free( sinfo-> data ); 345 | sinfo-> data = NULL; 346 | fclose( Src_file ); 347 | return; 348 | } 349 | if( sinfo-> apply_swap ) 350 | { 351 | p_byte = (char *)input_data; 352 | for( count = 0L; count < read_count; count++ ) 353 | { 354 | s = p_byte[count << 1]; 355 | p_byte[count << 1] = p_byte[(count << 1)+1]; 356 | p_byte[(count << 1)+1] = s; 357 | } 358 | } 359 | p_input = input_data; 360 | while( read_count > 0 ) 361 | { 362 | read_count--; 363 | *(read_ptr++) = (float)(*(p_input++)); 364 | } 365 | */ 366 | read_count = Nsamples; 367 | float * input_data_copy = input_data; 368 | while( read_count > 0 ){ 369 | read_count--; 370 | *(read_ptr++) = (float)(*(input_data_copy++)); 371 | } 372 | 373 | 374 | for( read_count = DATAPADDING_MSECS * (Fs / 1000) + SEARCHBUFFER * Downsample; 375 | read_count > 0; read_count-- ) 376 | *(read_ptr++) = 0.0f; 377 | 378 | //fclose( Src_file ); 379 | safe_free( input_data ); 380 | input_data = NULL; 381 | 382 | sinfo-> VAD = safe_malloc( sinfo-> Nsamples * sizeof(float) / Downsample ); 383 | sinfo-> logVAD = safe_malloc( sinfo-> Nsamples * sizeof(float) / Downsample ); 384 | if( (sinfo-> VAD == NULL) || (sinfo-> logVAD == NULL)) 385 | { 386 | *Error_Flag = 1; 387 | *Error_Type = "Failed to allocate memory for VAD"; 388 | printf ("%s!\n", *Error_Type); 389 | return; 390 | } 391 | } 392 | 393 | void alloc_other( SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, 394 | long * Error_Flag, char ** Error_Type, float ** ftmp) 395 | { 396 | *ftmp = (float *)safe_malloc( 397 | max( max( 398 | (*ref_info).Nsamples + DATAPADDING_MSECS * (Fs / 1000), 399 | (*deg_info).Nsamples + DATAPADDING_MSECS * (Fs / 1000) ), 400 | 12 * Align_Nfft) * sizeof(float) ); 401 | if( (*ftmp) == NULL ) 402 | { 403 | *Error_Flag = 2; 404 | *Error_Type = "Failed to allocate memory for temporary storage."; 405 | printf ("%s!\n", *Error_Type); 406 | return; 407 | } 408 | } 409 | 410 | /* END OF FILE */ 411 | -------------------------------------------------------------------------------- /pesq/pesqmain.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | #include 5 | #include 6 | #include "pesq.h" 7 | #include "dsp.h" 8 | 9 | 10 | // #define ITU_RESULTS_FILE "pesq_results.txt" 11 | 12 | extern int Nb; 13 | double compute_pesq(int argc, char **argv , 14 | float* reference, int ref_len, float* degraded, int deg_len); 15 | 16 | void pesq_measure(SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, 17 | ERROR_INFO * err_info, long * Error_Flag, char ** Error_Type); 18 | 19 | 20 | void usage (void) { 21 | printf ("Usage:\n"); 22 | printf (" PESQ HELP Displays this text\n"); 23 | printf (" PESQ [options] ref deg\n"); 24 | printf (" Run model on reference ref and degraded deg\n"); 25 | printf ("\n"); 26 | printf ("Options: +8000 +16000 +swap +wb\n"); 27 | printf (" Sample rate - No default. Must select either +8000 or +16000.\n"); 28 | printf (" Swap byte order - machine native format by default. Select +swap for byteswap.\n"); 29 | printf (" Default mode of operation is P.862 (narrowband handset listening). Select +wb \n"); 30 | printf (" to use P.862.2 wideband extension (headphone listening).\n"); 31 | printf ("\n"); 32 | printf ("File names may not begin with a + character.\n"); 33 | printf ("\n"); 34 | printf ("Files with names ending .wav or .WAV are assumed to have a 44-byte header, which"); 35 | printf (" is automatically skipped. All other file types are assumed to have no header.\n"); 36 | } 37 | 38 | 39 | 40 | 41 | double compute_pesq(int argc, char **argv, 42 | float* reference, int ref_len, float* degraded, int deg_len){ 43 | int arg; 44 | int names = 0; 45 | long sample_rate = -1; 46 | 47 | SIGNAL_INFO ref_info; 48 | SIGNAL_INFO deg_info; 49 | ERROR_INFO err_info; 50 | 51 | long Error_Flag = 0; 52 | char * Error_Type = "Unknown error type."; 53 | 54 | if (Error_Flag == 0) { 55 | printf("\n"); 56 | printf("Perceptual Evaluation of Speech Quality (PESQ)\n"); 57 | printf("\n"); 58 | 59 | if (argc < 3){ 60 | usage (); 61 | return 0; 62 | } else { 63 | 64 | strcpy (ref_info.path_name, ""); 65 | ref_info.apply_swap = 0; 66 | strcpy (deg_info.path_name, ""); 67 | deg_info.apply_swap = 0; 68 | ref_info.input_filter = 1; 69 | deg_info.input_filter = 1; 70 | err_info.mode = NB_MODE; 71 | 72 | for (arg = 1; arg < argc; arg++) { 73 | if (argv [arg] [0] == '+') { 74 | if (strcmp (argv [arg], "+swap") == 0) { 75 | ref_info.apply_swap = 1; 76 | deg_info.apply_swap = 1; 77 | } else if (strcmp (argv [arg], "+wb") == 0) { 78 | ref_info.input_filter = 2; 79 | deg_info.input_filter = 2; 80 | err_info.mode = WB_MODE; 81 | } else { 82 | if (strcmp (argv [arg], "+16000") == 0) { 83 | sample_rate = 16000L; 84 | } else { 85 | if (strcmp (argv [arg], "+8000") == 0) { 86 | sample_rate = 8000L; 87 | } else { 88 | usage (); 89 | fprintf (stderr, "Invalid parameter '%s'.\n", argv [arg]); 90 | return 1; 91 | } 92 | } 93 | } 94 | } else { 95 | switch (names) { 96 | case 0: 97 | strcpy (ref_info.path_name, argv [arg]); 98 | break; 99 | case 1: 100 | strcpy (deg_info.path_name, argv [arg]); 101 | break; 102 | default: 103 | usage (); 104 | fprintf (stderr, "Invalid parameter '%s'.\n", argv [arg]); 105 | return 1; 106 | } 107 | names++; 108 | } 109 | } 110 | 111 | if (sample_rate == -1) { 112 | printf ("PESQ Error. Must specify either +8000 or +16000 sample frequency option!\n"); 113 | exit (1); 114 | } 115 | 116 | if (sample_rate == 8000L && err_info.mode == WB_MODE ) { 117 | printf ("PESQ Error. P.862.2 operation must use 16kHz sample rate\n"); 118 | exit (1); 119 | } 120 | 121 | strcpy (ref_info. file_name, ref_info. path_name); 122 | if (strrchr (ref_info. file_name, '\\') != NULL) { 123 | strcpy (ref_info. file_name, 1 + strrchr (ref_info. file_name, '\\')); 124 | } 125 | if (strrchr (ref_info. file_name, '/') != NULL) { 126 | strcpy (ref_info. file_name, 1 + strrchr (ref_info. file_name, '/')); 127 | } 128 | 129 | strcpy (deg_info. file_name, deg_info. path_name); 130 | if (strrchr (deg_info. file_name, '\\') != NULL) { 131 | strcpy (deg_info. file_name, 1 + strrchr (deg_info. file_name, '\\')); 132 | } 133 | if (strrchr (deg_info. file_name, '/') != NULL) { 134 | strcpy (deg_info. file_name, 1 + strrchr (deg_info. file_name, '/')); 135 | } 136 | 137 | select_rate(sample_rate, &Error_Flag, &Error_Type); 138 | 139 | ref_info.data = reference; 140 | ref_info.Nsamples = ref_len; 141 | deg_info.data = degraded; 142 | deg_info.Nsamples = deg_len; 143 | 144 | pesq_measure (&ref_info, °_info, &err_info, &Error_Flag, &Error_Type); 145 | } 146 | } 147 | 148 | if (Error_Flag == 0) { 149 | if ( err_info.mode == NB_MODE ) 150 | printf ("\nP.862 Prediction (Raw MOS, MOS-LQO): = %.3f\t%.3f\n", (double) err_info.pesq_mos, 151 | (double) err_info.mapped_mos); 152 | else 153 | printf ("\nP.862.2 Prediction (MOS-LQO): = %.3f\n", (double) err_info.mapped_mos); 154 | return err_info.mapped_mos; 155 | } else { 156 | printf ("An error of type %ld ", Error_Flag); 157 | if (Error_Type != NULL) { 158 | printf (" (%s) occurred during processing.\n", Error_Type); 159 | } else { 160 | printf ("occurred during processing.\n"); 161 | } 162 | 163 | return 0; 164 | } 165 | } 166 | 167 | double align_filter_dB [26] [2] = {{0.,-500}, 168 | {50., -500}, 169 | {100., -500}, 170 | {125., -500}, 171 | {160., -500}, 172 | {200., -500}, 173 | {250., -500}, 174 | {300., -500}, 175 | {350., 0}, 176 | {400., 0}, 177 | {500., 0}, 178 | {600., 0}, 179 | {630., 0}, 180 | {800., 0}, 181 | {1000., 0}, 182 | {1250., 0}, 183 | {1600., 0}, 184 | {2000., 0}, 185 | {2500., 0}, 186 | {3000., 0}, 187 | {3250., 0}, 188 | {3500., -500}, 189 | {4000., -500}, 190 | {5000., -500}, 191 | {6300., -500}, 192 | {8000., -500}}; 193 | 194 | 195 | double standard_IRS_filter_dB [26] [2] = {{ 0., -200}, 196 | { 50., -40}, 197 | {100., -20}, 198 | {125., -12}, 199 | {160., -6}, 200 | {200., 0}, 201 | {250., 4}, 202 | {300., 6}, 203 | {350., 8}, 204 | {400., 10}, 205 | {500., 11}, 206 | {600., 12}, 207 | {700., 12}, 208 | {800., 12}, 209 | {1000., 12}, 210 | {1300., 12}, 211 | {1600., 12}, 212 | {2000., 12}, 213 | {2500., 12}, 214 | {3000., 12}, 215 | {3250., 12}, 216 | {3500., 4}, 217 | {4000., -200}, 218 | {5000., -200}, 219 | {6300., -200}, 220 | {8000., -200}}; 221 | 222 | 223 | #define TARGET_AVG_POWER 1E7 224 | 225 | void fix_power_level (SIGNAL_INFO *info, char *name, long maxNsamples) 226 | { 227 | long n = info-> Nsamples; 228 | long i; 229 | float *align_filtered = (float *) safe_malloc ((n + DATAPADDING_MSECS * (Fs / 1000)) * sizeof (float)); 230 | float global_scale; 231 | float power_above_300Hz; 232 | 233 | for (i = 0; i < n + DATAPADDING_MSECS * (Fs / 1000); i++) { 234 | align_filtered [i] = info-> data [i]; 235 | } 236 | apply_filter (align_filtered, info-> Nsamples, 26, align_filter_dB); 237 | 238 | power_above_300Hz = (float) pow_of (align_filtered, 239 | SEARCHBUFFER * Downsample, 240 | n - SEARCHBUFFER * Downsample + DATAPADDING_MSECS * (Fs / 1000), 241 | maxNsamples - 2 * SEARCHBUFFER * Downsample + DATAPADDING_MSECS * (Fs / 1000)); 242 | 243 | global_scale = (float) sqrt (TARGET_AVG_POWER / power_above_300Hz); 244 | 245 | for (i = 0; i < n; i++) { 246 | info-> data [i] *= global_scale; 247 | } 248 | 249 | safe_free (align_filtered); 250 | } 251 | 252 | long WB_InIIR_Nsos; 253 | float * WB_InIIR_Hsos; 254 | long WB_InIIR_Nsos_8k = 1L; 255 | float WB_InIIR_Hsos_8k[LINIIR] = { 256 | 2.6657628f, -5.3315255f, 2.6657628f, -1.8890331f, 0.89487434f }; 257 | long WB_InIIR_Nsos_16k = 1L; 258 | float WB_InIIR_Hsos_16k[LINIIR] = { 259 | 2.740826f, -5.4816519f, 2.740826f, -1.9444777f, 0.94597794f }; 260 | 261 | void pesq_measure (SIGNAL_INFO * ref_info, SIGNAL_INFO * deg_info, 262 | ERROR_INFO * err_info, long * Error_Flag, char ** Error_Type) 263 | { 264 | float * ftmp = NULL; 265 | 266 | //ref_info-> data = NULL; 267 | ref_info-> VAD = NULL; 268 | ref_info-> logVAD = NULL; 269 | 270 | //deg_info-> data = NULL; 271 | deg_info-> VAD = NULL; 272 | deg_info-> logVAD = NULL; 273 | 274 | // If Error_Flag was already set to something different than zero, 275 | // Report unknown error. In practice shouldn't happen, as the function that 276 | // calls this function returns if its Error_Flag wasn't zero. 277 | if (*Error_Flag != 0) { 278 | *Error_Flag = PESQ_ERROR_UNKNOWN; 279 | // We're rewriting the flag, but keeping the Error_Type for later 280 | goto cleanup; 281 | } 282 | 283 | // Load Reference Buffer 284 | load_src(Error_Flag, Error_Type, ref_info); 285 | if (*Error_Flag != 0) { 286 | *Error_Flag = PESQ_ERROR_OUT_OF_MEMORY_REF; 287 | goto cleanup; 288 | } 289 | 290 | // Load Degraded Buffer 291 | load_src(Error_Flag, Error_Type, deg_info); 292 | if (*Error_Flag != 0) { 293 | *Error_Flag = PESQ_ERROR_OUT_OF_MEMORY_DEG; 294 | goto cleanup; 295 | } 296 | 297 | // If one of the buffers has less than 1/4 of a second, return error. 298 | if ((ref_info-> Nsamples - 2 * SEARCHBUFFER * Downsample < Fs / 4) || 299 | (deg_info-> Nsamples - 2 * SEARCHBUFFER * Downsample < Fs / 4)) 300 | { 301 | *Error_Flag = PESQ_ERROR_BUFFER_TOO_SHORT; 302 | *Error_Type = "Reference or Degraded below 1/4 second - processing stopped "; 303 | goto cleanup; 304 | } 305 | 306 | // Allocate temporary storage 307 | alloc_other(ref_info, deg_info, Error_Flag, Error_Type, &ftmp); 308 | if (*Error_Flag != 0) { 309 | *Error_Flag = PESQ_ERROR_OUT_OF_MEMORY_TMP; 310 | goto cleanup; 311 | } 312 | 313 | int maxNsamples = max (ref_info-> Nsamples, deg_info-> Nsamples); 314 | float * model_ref; 315 | float * model_deg; 316 | long i; 317 | // FILE *resultsFile; 318 | 319 | // printf (" Level normalization...\n"); 320 | fix_power_level (ref_info, "reference", maxNsamples); 321 | fix_power_level (deg_info, "degraded", maxNsamples); 322 | 323 | // printf (" IRS filtering...\n"); 324 | if( Fs == 16000 ) { 325 | WB_InIIR_Nsos = WB_InIIR_Nsos_16k; 326 | WB_InIIR_Hsos = WB_InIIR_Hsos_16k; 327 | } else { 328 | WB_InIIR_Nsos = WB_InIIR_Nsos_8k; 329 | WB_InIIR_Hsos = WB_InIIR_Hsos_8k; 330 | } 331 | if( ref_info->input_filter == 1 ) { 332 | apply_filter (ref_info-> data, ref_info-> Nsamples, 26, standard_IRS_filter_dB); 333 | } else { 334 | for( i = 0; i < 16; i++ ) { 335 | ref_info->data[SEARCHBUFFER * Downsample + i - 1] 336 | *= (float)i / 16.0f; 337 | ref_info->data[ref_info->Nsamples - SEARCHBUFFER * Downsample - i] 338 | *= (float)i / 16.0f; 339 | } 340 | IIRFilt( WB_InIIR_Hsos, WB_InIIR_Nsos, NULL, 341 | ref_info->data + SEARCHBUFFER * Downsample, 342 | ref_info->Nsamples - 2 * SEARCHBUFFER * Downsample, NULL ); 343 | } 344 | if( deg_info->input_filter == 1 ) { 345 | apply_filter (deg_info-> data, deg_info-> Nsamples, 26, standard_IRS_filter_dB); 346 | } else { 347 | for( i = 0; i < 16; i++ ) { 348 | deg_info->data[SEARCHBUFFER * Downsample + i - 1] 349 | *= (float)i / 16.0f; 350 | deg_info->data[deg_info->Nsamples - SEARCHBUFFER * Downsample - i] 351 | *= (float)i / 16.0f; 352 | } 353 | IIRFilt( WB_InIIR_Hsos, WB_InIIR_Nsos, NULL, 354 | deg_info->data + SEARCHBUFFER * Downsample, 355 | deg_info->Nsamples - 2 * SEARCHBUFFER * Downsample, NULL ); 356 | } 357 | 358 | model_ref = (float *) safe_malloc ((ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000)) * sizeof (float)); 359 | model_deg = (float *) safe_malloc ((deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000)) * sizeof (float)); 360 | 361 | for (i = 0; i < ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { 362 | model_ref [i] = ref_info-> data [i]; 363 | } 364 | 365 | for (i = 0; i < deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { 366 | model_deg [i] = deg_info-> data [i]; 367 | } 368 | 369 | input_filter( ref_info, deg_info, ftmp ); 370 | 371 | // printf (" Variable delay compensation...\n"); 372 | calc_VAD (ref_info); 373 | calc_VAD (deg_info); 374 | 375 | crude_align (ref_info, deg_info, err_info, WHOLE_SIGNAL, ftmp); 376 | 377 | utterance_locate (ref_info, deg_info, err_info, ftmp); 378 | 379 | for (i = 0; i < ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { 380 | ref_info-> data [i] = model_ref [i]; 381 | } 382 | 383 | for (i = 0; i < deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { 384 | deg_info-> data [i] = model_deg [i]; 385 | } 386 | 387 | safe_free (model_ref); 388 | safe_free (model_deg); 389 | 390 | if (ref_info-> Nsamples < deg_info-> Nsamples) { 391 | float *new_ref = (float *) safe_malloc((deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000)) * sizeof(float)); 392 | long i; 393 | for (i = 0; i < ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { 394 | new_ref [i] = ref_info-> data [i]; 395 | } 396 | for (i = ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); 397 | i < deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { 398 | new_ref [i] = 0.0f; 399 | } 400 | safe_free (ref_info-> data); 401 | ref_info-> data = new_ref; 402 | new_ref = NULL; 403 | } else { 404 | if (ref_info-> Nsamples > deg_info-> Nsamples) { 405 | float *new_deg = (float *) safe_malloc((ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000)) * sizeof(float)); 406 | long i; 407 | for (i = 0; i < deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { 408 | new_deg [i] = deg_info-> data [i]; 409 | } 410 | for (i = deg_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); 411 | i < ref_info-> Nsamples + DATAPADDING_MSECS * (Fs / 1000); i++) { 412 | new_deg [i] = 0.0f; 413 | } 414 | safe_free (deg_info-> data); 415 | deg_info-> data = new_deg; 416 | new_deg = NULL; 417 | } 418 | } 419 | 420 | pesq_psychoacoustic_model(ref_info, deg_info, err_info, Error_Flag, Error_Type, ftmp); 421 | // We're not checking Error_Flag and returning here as we still need to do 422 | // some clean-up before returning. 423 | 424 | if ( err_info->mode == NB_MODE ) // narrow band 425 | { 426 | // printf("narrow\n"); 427 | err_info->mapped_mos = 0.999f+4.0f/(1.0f+(float)exp((-1.4945f*err_info->pesq_mos+4.6607f))); 428 | } 429 | else // wide band 430 | { 431 | // printf("wide\n"); 432 | err_info->mapped_mos = 0.999f+4.0f/(1.0f+(float)exp((-1.3669f*err_info->pesq_mos+3.8224f))); 433 | err_info->pesq_mos = -1.0; 434 | } 435 | 436 | cleanup: 437 | // Ensuring we're avoiding a double-free scenario 438 | if (ref_info->data != NULL) { safe_free(ref_info->data); } 439 | if (ref_info->VAD != NULL) { safe_free(ref_info->VAD); } 440 | if (ref_info->logVAD != NULL) { safe_free(ref_info->logVAD); } 441 | if (deg_info->data != NULL) { safe_free(deg_info->data); } 442 | if (deg_info->VAD != NULL) { safe_free(deg_info->VAD); } 443 | if (deg_info->logVAD != NULL) { safe_free(deg_info->logVAD); } 444 | 445 | if (ftmp != NULL) { 446 | safe_free(ftmp); 447 | } 448 | 449 | // resultsFile = fopen (ITU_RESULTS_FILE, "at"); 450 | // 451 | // if (resultsFile != NULL) { 452 | // long start, end; 453 | // 454 | // if (0 != fseek (resultsFile, 0, SEEK_SET)) { 455 | // printf ("Could not move to start of results file %s!\n", ITU_RESULTS_FILE); 456 | // exit (1); 457 | // } 458 | // start = ftell (resultsFile); 459 | // 460 | // if (0 != fseek (resultsFile, 0, SEEK_END)) { 461 | // printf ("Could not move to end of results file %s!\n", ITU_RESULTS_FILE); 462 | // exit (1); 463 | // } 464 | // end = ftell (resultsFile); 465 | // 466 | // if (start == end) { 467 | // fprintf (resultsFile, "REFERENCE\t DEGRADED\t PESQMOS\t MOSLQO\t SAMPLE_FREQ\t MODE\n"); 468 | // fflush (resultsFile); 469 | // } 470 | // 471 | // fprintf (resultsFile, "%s----\t ", ref_info-> path_name); 472 | // fprintf (resultsFile, "%s----\t ", deg_info-> path_name); 473 | // 474 | // fprintf (resultsFile, "%.4f\t ", err_info->pesq_mos); 475 | // fprintf (resultsFile, "%.4f\t ", err_info->mapped_mos); 476 | // fprintf (resultsFile, "%d\t", Fs); 477 | // 478 | // if ( err_info->mode == NB_MODE ) 479 | // fprintf (resultsFile, "nb"); 480 | // else 481 | // fprintf (resultsFile, "wb"); 482 | // 483 | // fprintf (resultsFile, "\n", Fs); 484 | // 485 | // fclose (resultsFile); 486 | // } 487 | } 488 | 489 | /* END OF FILE */ 490 | -------------------------------------------------------------------------------- /pesq/pesqmod.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ludlows/PESQ/af8fb1b636257aab77f1086e46bdeea281382369/pesq/pesqmod.c -------------------------------------------------------------------------------- /pesq/pesqpar.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ludlows/PESQ/af8fb1b636257aab77f1086e46bdeea281382369/pesq/pesqpar.h -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # 2019-May 2 | # github.com/ludlows 3 | # Python Wrapper for PESQ Score (narrowband and wideband) 4 | from setuptools import find_packages 5 | from setuptools import setup, Extension 6 | 7 | with open("README.md", "r") as fh: 8 | long_description = fh.read() 9 | 10 | 11 | class CyPesqExtension(Extension): 12 | def __init__(self, *args, **kwargs): 13 | self._include = [] 14 | super().__init__(*args, **kwargs) 15 | 16 | @property 17 | def include_dirs(self): 18 | import numpy 19 | return self._include + [numpy.get_include()] 20 | 21 | @include_dirs.setter 22 | def include_dirs(self, dirs): 23 | self._include = dirs 24 | 25 | 26 | extensions = [ 27 | CyPesqExtension( 28 | "cypesq", 29 | ["pesq/cypesq.pyx", "pesq/dsp.c", "pesq/pesqdsp.c", "pesq/pesqmod.c"], 30 | include_dirs=['pesq'], 31 | language="c") 32 | ] 33 | setup( 34 | name="pesq", 35 | version="0.0.5", 36 | author="ludlows", 37 | description="Python Wrapper for PESQ Score (narrow band and wide band)", 38 | long_description=long_description, 39 | long_description_content_type="text/markdown", 40 | url="https://github.com/ludlows/PESQ", 41 | packages=find_packages(), 42 | package_data={'pesq': ["*.pyx", "*.h", "dsp.c", "pesqdsp.c", "pesqmod.c"]}, 43 | ext_package='pesq', 44 | ext_modules=extensions, 45 | setup_requires=['setuptools>=18.0', 'cython', 'numpy<2.0'], 46 | install_requires=['numpy<2.0'], 47 | classifiers=[ 48 | "Programming Language :: Python", 49 | "License :: OSI Approved :: MIT License", 50 | "Operating System :: OS Independent", 51 | ] 52 | ) 53 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | coverage 3 | 4 | scipy 5 | -------------------------------------------------------------------------------- /tests/test_pesq.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import numpy as np 4 | import pytest 5 | import scipy.io.wavfile 6 | 7 | from pesq import pesq, pesq_batch, NoUtterancesError, PesqError 8 | 9 | 10 | def test(): 11 | data_dir = Path(__file__).parent.parent / 'audio' 12 | ref_path = data_dir / 'speech.wav' 13 | deg_path = data_dir / 'speech_bab_0dB.wav' 14 | 15 | sample_rate, ref = scipy.io.wavfile.read(ref_path) 16 | sample_rate, deg = scipy.io.wavfile.read(deg_path) 17 | 18 | score = pesq(ref=ref, deg=deg, fs=sample_rate, mode='wb') 19 | 20 | assert score == 1.0832337141036987, score 21 | 22 | score = pesq(ref=ref, deg=deg, fs=sample_rate, mode='nb') 23 | 24 | assert score == 1.6072081327438354, score 25 | return score 26 | 27 | 28 | def test_no_utterances_nb_mode(): 29 | sample_rate = 8000 30 | silent_ref = np.zeros(sample_rate) 31 | deg = np.random.randn(sample_rate) 32 | 33 | with pytest.raises(NoUtterancesError) as e: 34 | pesq(ref=silent_ref, deg=deg, fs=sample_rate, mode='nb') 35 | 36 | score = pesq(ref=silent_ref, deg=deg, fs=sample_rate, mode='nb', 37 | on_error=PesqError.RETURN_VALUES) 38 | 39 | assert score == PesqError.NO_UTTERANCES_DETECTED, score 40 | return score 41 | 42 | 43 | def test_no_utterances_wb_mode(): 44 | sample_rate = 16000 45 | silent_ref = np.zeros(sample_rate) 46 | deg = np.random.randn(sample_rate) 47 | 48 | with pytest.raises(NoUtterancesError) as e: 49 | pesq(ref=silent_ref, deg=deg, fs=sample_rate, mode='wb') 50 | 51 | score = pesq(ref=silent_ref, deg=deg, fs=sample_rate, mode='wb', 52 | on_error=PesqError.RETURN_VALUES) 53 | 54 | assert score == PesqError.NO_UTTERANCES_DETECTED, score 55 | return score 56 | 57 | 58 | def test_pesq_batch(): 59 | data_dir = Path(__file__).parent.parent / 'audio' 60 | ref_path = data_dir / 'speech.wav' 61 | deg_path = data_dir / 'speech_bab_0dB.wav' 62 | 63 | sample_rate, ref = scipy.io.wavfile.read(ref_path) 64 | sample_rate, deg = scipy.io.wavfile.read(deg_path) 65 | 66 | n_file = 10 67 | ideally = np.array([1.0832337141036987 for i in range(n_file)]) 68 | 69 | # 1D - 1D 70 | score = pesq_batch(ref=ref, deg=deg, fs=sample_rate, mode='wb') 71 | assert score == [1.0832337141036987], score 72 | 73 | # 1D - 2D 74 | deg_2d = np.repeat(deg[np.newaxis, :], n_file, axis=0) 75 | scores = pesq_batch(ref=ref, deg=deg_2d, fs=sample_rate, mode='wb') 76 | assert np.allclose(np.array(scores), ideally), scores 77 | 78 | # 2D - 2D 79 | ref_2d = np.repeat(ref[np.newaxis, :], n_file, axis=0) 80 | scores = pesq_batch(ref=ref_2d, deg=deg_2d, fs=sample_rate, mode='wb') 81 | assert np.allclose(np.array(scores), ideally), scores 82 | 83 | # narrowband 84 | score = pesq_batch(ref=ref, deg=deg, fs=sample_rate, mode='nb') 85 | assert score == [1.6072081327438354], score 86 | 87 | # 1D - 2D multiprocessing 88 | deg_2d = np.repeat(deg[np.newaxis, :], n_file, axis=0) 89 | scores = pesq_batch(ref=ref, deg=deg_2d, fs=sample_rate, mode='wb', n_processor=4) 90 | assert np.allclose(np.array(scores), ideally), scores 91 | 92 | # 2D - 2D multiprocessing 93 | ref_2d = np.repeat(ref[np.newaxis, :], n_file, axis=0) 94 | scores = pesq_batch(ref=ref_2d, deg=deg_2d, fs=sample_rate, mode='wb', n_processor=4) 95 | assert np.allclose(np.array(scores), ideally), scores 96 | 97 | 98 | # def test_time_efficiency(): 99 | # data_dir = Path(__file__).parent.parent / 'audio' 100 | # ref_path = data_dir / 'speech.wav' 101 | # deg_path = data_dir / 'speech_bab_0dB.wav' 102 | # 103 | # sample_rate, ref = scipy.io.wavfile.read(ref_path) 104 | # sample_rate, deg = scipy.io.wavfile.read(deg_path) 105 | # import time 106 | # nums = [100, 1000, 10000] 107 | # durations = [] 108 | # n_processors = 8 109 | # degs = [np.repeat(deg[np.newaxis, :], n, axis=0) for n in nums] 110 | # for d, n in zip(degs, nums): 111 | # start = time.time() 112 | # pesq_batch(ref=ref, deg=d, fs=sample_rate, mode='wb', n_processor=n_processors) 113 | # end = time.time() 114 | # durations.append(end - start) 115 | # print(durations) 116 | # # [5.192636251449585, 30.032038688659668, 294.47159910202026] 117 | 118 | 119 | # if __name__ == "__main__": 120 | # test() 121 | # test_no_utterances_nb_mode() 122 | # test_no_utterances_wb_mode() 123 | # test_pesq_batch() 124 | 125 | --------------------------------------------------------------------------------