├── .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 | [](https://doi.org/10.5281/zenodo.6549559)
3 | [](https://pepy.tech/project/pesq)
4 | [](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 |
--------------------------------------------------------------------------------