PortAudio library not found by sounddevice during

2020-08-05 18:04发布

问题:

PROBLEM: Similar to this question for Linux, I'm running into a OSError: PortAudio library not found after I package my app with pyinstaller and execute the .exe. (Note, I do not encounter any issues when running my unpackaged app via shell. This only happens when I package it with pyinstaller).

Although installing portaudio via package manager seems to work for Linux, this doesn't seem to be an option for Windows, since the sounddevice docs say, "If you are using Mac OS X or Windows, the library will be installed automagically with pip" and doing a pip search portaudio only returns wrappers

QUESTION What do I need to do to get sounddevice to work in my Windows environment? Why does it work in my shell, but not after packaging the app?

FULL ERROR:

(venv) λ my-app.exe
Traceback (most recent call last):
  File "site-packages\sounddevice.py", line 71, in <module>
OSError: PortAudio library not found

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "point-of-service-recorder.py", line 11, in <module>
    import sounddevice as sd
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "c:\path\to\my\app\venv\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 627, in exec_module
    exec(bytecode, module.__dict__)
  File "site-packages\sounddevice.py", line 83, in <module>
OSError: cannot load library 'C:\Users\TYLER~1.HIT\AppData\Local\Temp\_MEI250242\_sounddevice_data\portaudio-binaries\libportaudio32bit.dll': error 0x7e
[23156] Failed to execute script point-of-service-recorder

BUILD OUTPUT

  • Selected output from running pyinstaller build:
[...]

22251 INFO: Looking for dynamic libraries
22260 INFO: Cannot get manifest resource from non-PE file c:\users\path\to\app\venv\lib\site-packages\_soundfile_data\COPYING
22262 WARNING: Can not get binary dependencies for file: c:\users\path\to\app\venv\lib\site-packages\_soundfile_data\COPYING
22262 WARNING:   Reason: 'DOS Header magic not found.'

INSTALLED PACKAGES:

  • note that cffi, sounddevice, and setuptools are present, as specified by the requirements. portaudio is not listed, but I assumed it is included in sounddevice, like the docs say (?)
(venv) λ pip list            
Package        Version       
-------------- ---------     
altgraph       0.16.1              
certifi        2019.6.16     
cffi           1.12.3        
chardet        3.0.4         
Click          7.0           
Flask          1.1.1         
Flask-WTF      0.14.2        
future         0.17.1        
idna           2.8           
itsdangerous   1.1.0         
Jinja2         2.10.1        
MarkupSafe     1.1.1         
numpy          1.17.0        
pefile         2019.4.18     
pip            19.2.2        
pycparser      2.19          
pydub          0.23.1        
PyInstaller    3.5           
pywin32-ctypes 0.2.0         
requests       2.22.0        
setuptools     41.0.1        
sounddevice    0.3.13        
SoundFile      0.10.2        
urllib3        1.25.3        
waitress       1.3.0         
Werkzeug       0.15.5        
wheel          0.33.4        
WTForms        2.2.1         

回答1:

Somewhat embarrassed by this, but I wasn't including the needed port audio .dlls in the build. Copying them to the env\site-packages and updating sounddevice.py to point to the correct .dll worked for me


EDITS

The pre-built dlls can be found at: https://github.com/spatialaudio/portaudio-binaries

I added them to a /bin directory and updated sounddevice.py to use them (code below)


Here's my updated sounddevice.py from v0.3.13 (manual changes marked by #!)

import atexit as _atexit
import os as _os
import platform as _platform
import sys as _sys
from ctypes.util import find_library as _find_library
from _sounddevice import ffi as _ffi


#! built path to my dlls (in my `bin` dir)
#! use _platform.architecture to infer whether to use 32 or 64-bit dll
try:
    _libname = 'libportaudio' + _platform.architecture()[0] + '.dll'
    _libname = _os.path.join('bin', _libname)
    _lib = _ffi.dlopen(_libname)
except OSError:
    if _platform.system() == 'Windows':  #! use Windows, not default 'Darwin'
        _libname = 'libportaudio' + _platform.architecture()[0] + '.dll'
    else:
        #! custom error
        raise OSError('PortAudio library not found! Make sure the system is Windows 64 or 32 bit and you have the '
                      'correct libportaudio dll saved in  site-packages')

#! keep rest the same

When setting up my dev environment, I run this bat script to update the default sounddevice.py in the virtual env with my custom one:

echo Updating sounddevice.py ...
copy .\sounddevice.py .\venv\Lib\site-packages\sounddevice.py /y
copy .\libportaudio32bit.dll .\venv\Lib\site-packages\libportaudio32bit.dll /y
copy .\libportaudio64bit.dll .\venv\Lib\site-packages\libportaudio64bit.dll /y


Tip: Make sure you lock the sounddevice version (in requirements.txt for example) so future versions don't break this hack



回答2:

I packed libportaudio32bit.dll into my exe file, and deleted these codes:

# import _sounddevice_data
# _libname = _os.path.join(
#     next(iter(_sounddevice_data.__path__)), 'portaudio-binaries', _libname)
_lib = _ffi.dlopen(_libname)

in sounddevice.py, and now it is working.