PJSUA2 Python简易项目调用音频设备失败:无法设置单声道播放通道
PJSUA2 Python软电话Docker部署音频设备初始化失败问题
PJSUA2的Python示例应用pygui开箱即用存在问题,无法添加联系人且触发涉及C代码文件的异常,我不需要GUI功能,因此编写了一个仅满足PJSUA2库基础通话要求的简易无GUI软电话。我已在支持拨打POTS号码的SIP服务商完成注册(此前用PyVoIP测试过该服务商,功能正常),但发起呼叫时项目完全失败,报错信息如下:
20:59:26.067 pjsua_acc.c ..Acc 0: Registration sent 20:59:26.067 pjsua_call.c Making call with acc #0 to -redacted- 20:59:26.067 pjsua_aud.c .Set sound device: capture=-1, playback=-2, mode=0, use_default_settings=0 20:59:26.067 pjsua_aud.c ..Opening sound device (speaker + mic) PCM@16000/1/20ms 20:59:26.068 alsa_dev.c ...Unable to set a channel count of 1 for playback device 'hw:CARD=PCH,DEV=0' 20:59:26.068 pjsua_aud.c ..Opening sound device (speaker + mic) PCM@44100/1/20ms 20:59:26.068 alsa_dev.c ...Unable to set a channel count of 1 for playback device 'hw:CARD=PCH,DEV=0' 20:59:26.068 pjsua_aud.c ..Opening sound device (speaker + mic) PCM@48000/1/20ms 20:59:26.068 alsa_dev.c ...Unable to set a channel count of 1 for playback device 'hw:CARD=PCH,DEV=0' 20:59:26.068 pjsua_aud.c ..Opening sound device (speaker + mic) PCM@32000/1/20ms 20:59:26.069 alsa_dev.c ...Unable to set a channel count of 1 for playback device 'hw:CARD=PCH,DEV=0' 20:59:26.069 pjsua_aud.c ..Opening sound device (speaker + mic) PCM@16000/1/20ms 20:59:26.069 alsa_dev.c ...Unable to set a channel count of 1 for playback device 'hw:CARD=PCH,DEV=0' 20:59:26.069 pjsua_aud.c ..Opening sound device (speaker + mic) PCM@8000/1/20ms 20:59:26.069 alsa_dev.c ...Unable to set a channel count of 1 for playback device 'hw:CARD=PCH,DEV=0' 20:59:26.069 pjsua_aud.c ..Unable to open sound device: Unknown error from audio driver (PJMEDIA_EAUD_SYSERR) [status=420002] 20:59:26.069 pjsua_media.c .Call 0: deinitializing media..
该项目部署在Docker镜像中,已安装libasound2并挂载/dev/snd设备,但仍出现上述错误。我列出了四种可能的问题:
- 容器运行时音频硬件不可用,可能是设备挂载方式不正确
- 端点初始化时使用了不符合音频硬件的配置
- 库启动时使用了不符合音频硬件的配置
- 音频硬件本身存在问题(可能性低,本机双系统均可正常使用音频)
由于我不熟悉C++,希望得到Python层面的解决方案。附完整项目代码、Dockerfile及启动脚本:
Python代码
import sys import pjsua2 as pj class Settings: def __init__(self, sip_user=None, sip_pass=None, sip_registrar_uri=None): self.sip_user = sip_user self.sip_pass = sip_pass self.sip_registrar_uri = sip_registrar_uri # Subclass to extend the Account and get notifications etc. class Account(pj.Account): def onRegState(self, prm): # print("***OnRegState: " + prm.reason) pass # Subclass the Call class to define callbacks etc. class Call(pj.Call): def __init__(self, acc, peer_uri='', chat=None, call_id=pj.PJSUA_INVALID_ID): pj.Call.__init__(self, acc, call_id) def onCallState(self, prm): call_info = self.getInfo() self.connected = call_info.state == pj.PJSIP_INV_STATE_CONFIRMED def onCallMediaState(self, prm): ep = pj.Endpoint() call_info = self.getInfo() print(f'call_info: {call_info}') # for media_item in call_info.media: # if media_item.type == pj.PJMEDIA_TYPE_AUDIO and ( # media_item.status == pj.PJSUA_CALL_MEDIA_ACTIVE or # media_item.status == pj.PJSUA_CALL_MEDIA_REMOTE_HOLD # ): # media = self.getMedia(media_item.index) # audio_media = pj.AudioMedia.typecastFromMedia(media) # Connect ports. # LEFT OFF HERE def read_settings() -> Settings: # TODO: SECURITY: Rip these out, pass in via env vars. return Settings( sip_user='-redacted-', sip_pass='-redacted-', sip_registrar_uri='seattle1.voip.ms', # Seattle VoIP.ms PoP ) def main(): # Retrieve required settings. settings = read_settings() # Set up endpoint. endpoint_config = pj.EpConfig() endpoint = pj.Endpoint() endpoint.libCreate() endpoint.libInit(endpoint_config) # Set up SIP transport. sip_transport_config = pj.TransportConfig() sip_transport_config.port = 5060 endpoint.transportCreate(pj.PJSIP_TRANSPORT_UDP, sip_transport_config) # Start the library. endpoint.libStart() # Set up account. account_config = pj.AccountConfig() account_config.idUri = f'sip:{settings.sip_user}' account_config.regConfig.registrarUri = f'sip:{settings.sip_registrar_uri}' credentials = pj.AuthCredInfo('digest', '*', settings.sip_user, 0, settings.sip_pass) account_config.sipConfig.authCreds.append(credentials) # Create account. account = Account() account.create(account_config) # Create call. call = Call(account) call_param = pj.CallOpParam() call_param.opt.audioCount = 1 # Also tried 2 here, for stereo? Same error... call_param.opt.videoCount = 0 # Dial. try: call.makeCall('-redacted-', call_param) except Exception as error: print(f'Dialing error: {error}') sys.exit(1) # Main event loop. while True: try: endpoint.libHandleEvents(50) except Exception as error: print(f'Error: {error}') break endpoint.libDestroy() if __name__ == "__main__": main()
Dockerfile
FROM python:3-slim AS build_pjsip ENV PJSIP_VERSION="2.13.1" WORKDIR /src RUN apt-get update && apt-get install -y \ wget \ build-essential \ swig \ libasound2-dev RUN wget https://github.com/pjsip/pjproject/archive/refs/tags/${PJSIP_VERSION}.tar.gz && \ tar xfz ${PJSIP_VERSION}.tar.gz RUN mv pjproject-${PJSIP_VERSION} pjproject && \ cd pjproject && \ ./configure CFLAGS="-fPIC" && \ make dep && \ make clean && \ make RUN cd pjproject/pjsip-apps/src/swig/python && \ make && \ make install FROM python:3-slim as release RUN apt-get update && apt-get install -y \ libasound2 ADD . /opt/btm WORKDIR /opt/btm COPY --from=build_pjsip /root/.local/lib /root/.local/lib CMD ["/bin/bash"]
启动脚本
#!/bin/bash docker run \ --rm \ -it \ -v .:/opt/btm \ --device /dev/snd:/dev/snd \ btm-make-a-call
内容的提问来源于stack exchange,提问作者Bruce Bradley
相关产品推荐
相关产品推荐

