sound fixes

This commit is contained in:
emdee@spm.plastiras.org 2024-02-11 08:10:36 +00:00
parent dda2a9147a
commit 76ad2ccd44
15 changed files with 80 additions and 61 deletions

1
.gitignore vendored
View File

@ -2,7 +2,6 @@
.pylint.out .pylint.out
*.pyc *.pyc
*.pyo *.pyo
*.bak
toxygen/toxcore toxygen/toxcore
tests/tests tests/tests

View File

@ -4,6 +4,9 @@
# usually this is installed by your OS package manager and pip may not # usually this is installed by your OS package manager and pip may not
# detect the right version, so we leave these commented # detect the right version, so we leave these commented
# PyQt5 >= 5.15.10 # PyQt5 >= 5.15.10
# this is not on pypi yet but is required - get it from
# https://git.plastiras.org/emdee/toxygen_wrapper
# toxygen_wrapper == 1.0.0
QtPy >= 2.4.1 QtPy >= 2.4.1
PyAudio >= 0.2.13 PyAudio >= 0.2.13
numpy >= 1.26.1 numpy >= 1.26.1
@ -17,6 +20,7 @@ sounddevice >= 0.3.15
coloredlogs >= 15.0.1 coloredlogs >= 15.0.1
# this is optional # this is optional
# qtconsole >= 5.4.3 # qtconsole >= 5.4.3
# this is not on pypi yet - get it from # this is not on pypi yet but is optional for qweechat - get it from
# https://git.plastiras.org/emdee/toxygen_wrapper # https://git.plastiras.org/emdee/qweechat
# toxygen_wrapper == 1.0.0 # qweechat_wrapper == 0.0.1

View File

@ -1,9 +1,3 @@
import os import os
import sys import sys
path = os.path.dirname(os.path.realpath(__file__)) # curr dir
sys.path.insert(0, os.path.join(path, 'styles'))
sys.path.insert(0, os.path.join(path, 'plugins'))
sys.path.insert(0, os.path.join(path, 'third_party'))
sys.path.insert(0, path)

View File

@ -37,6 +37,12 @@ with ts.ignoreStderr():
__maintainer__ = 'Ingvar' __maintainer__ = 'Ingvar'
__version__ = '1.0.0' # was 0.5.0+ __version__ = '1.0.0' # was 0.5.0+
path = os.path.dirname(os.path.realpath(__file__)) # curr dir
sys.path.insert(0, os.path.join(path, 'styles'))
sys.path.insert(0, os.path.join(path, 'plugins'))
sys.path.insert(0, os.path.join(path, 'third_party'))
sys.path.insert(0, path)
sleep = time.sleep sleep = time.sleep
os.environ['QT_API'] = os.environ.get('QT_API', 'pyqt5') os.environ['QT_API'] = os.environ.get('QT_API', 'pyqt5')

View File

@ -839,14 +839,15 @@ class App:
def loop(self, n) -> None: def loop(self, n) -> None:
""" """
Im guessings - there are 3 sleeps - time, tox, and Qt Im guessing - there are 4 sleeps - time, tox, and Qt gevent
""" """
interval = self._tox.iteration_interval() interval = self._tox.iteration_interval()
for i in range(n): for i in range(n):
self._tox.iterate() self._tox.iterate()
QtCore.QThread.msleep(interval) QtCore.QThread.msleep(interval)
# NO QtCore.QCoreApplication.processEvents() # NO?
sleep(interval / 1000.0) QtCore.QCoreApplication.processEvents()
# sleep(interval / 1000.0)
def _test_tox(self) -> None: def _test_tox(self) -> None:
self.test_net(iMax=8) self.test_net(iMax=8)
@ -918,7 +919,6 @@ class App:
LOG.info(f"Connected # {i}" +' : ' +repr(status)) LOG.info(f"Connected # {i}" +' : ' +repr(status))
break break
LOG.trace(f"Connected status #{i}: {status}") LOG.trace(f"Connected status #{i}: {status}")
self.loop(2)
def _test_env(self) -> None: def _test_env(self) -> None:
_settings = self._settings _settings = self._settings

View File

@ -18,7 +18,15 @@ import toxygen_wrapper.tests.support_testing as ts
from middleware.threads import invoke_in_main_thread from middleware.threads import invoke_in_main_thread
from middleware.threads import BaseThread from middleware.threads import BaseThread
sleep = time.sleep
from qtpy import QtCore
# sleep = time.sleep
def sleep(fSec):
mSec = int(fSec * 1000.0)
QtCore.QThread.msleep(mSec)
# GLib:ERROR:../glib-2.78.4/glib/gmain.c:3428:g_main_dispatch: assertion failed: (source)
# Bail out! GLib:ERROR:../glib-2.78.4/glib/gmain.c:3428:g_main_dispatch: assertion failed: (source)
QtCore.QCoreApplication.processEvents()
global LOG global LOG
import logging import logging
@ -71,10 +79,8 @@ class AV(common.tox_save.ToxAvSave):
# was iOutput = self._settings._args.audio['output'] # was iOutput = self._settings._args.audio['output']
iInput = self._settings['audio']['input'] iInput = self._settings['audio']['input']
self.lPaSampleratesI = ts.lSdSamplerates(iInput) self.lPaSampleratesI = ts.lSdSamplerates(iInput)
if not self.lPaSampleratesI: LOG.warn(f"empty self.lPaSampleratesI iInput={iInput}")
iOutput = self._settings['audio']['output'] iOutput = self._settings['audio']['output']
self.lPaSampleratesO = ts.lSdSamplerates(iOutput) self.lPaSampleratesO = ts.lSdSamplerates(iOutput)
if not self.lPaSampleratesO: LOG.warn(f"empty self.lPaSampleratesO iOutput={iOutput}")
global oPYA global oPYA
oPYA = self._audio = pyaudio.PyAudio() oPYA = self._audio = pyaudio.PyAudio()
@ -140,8 +146,6 @@ class AV(common.tox_save.ToxAvSave):
def finish_call(self, friend_number, by_friend=False): def finish_call(self, friend_number, by_friend=False):
LOG.debug(f"finish_call {friend_number}") LOG.debug(f"finish_call {friend_number}")
if not by_friend:
self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL'])
if friend_number in self._calls: if friend_number in self._calls:
del self._calls[friend_number] del self._calls[friend_number]
try: try:
@ -155,6 +159,10 @@ class AV(common.tox_save.ToxAvSave):
# dunno # dunno
self.stop_audio_thread() self.stop_audio_thread()
self.stop_video_thread() self.stop_video_thread()
if not by_friend:
LOG.debug(f"finish_call before call_control {friend_number}")
self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL'])
LOG.debug(f"finish_call after call_control {friend_number}")
def finish_not_started_call(self, friend_number): def finish_not_started_call(self, friend_number):
if friend_number in self: if friend_number in self:
@ -203,15 +211,15 @@ class AV(common.tox_save.ToxAvSave):
#?? dunno - cancel call? - no let the user do it #?? dunno - cancel call? - no let the user do it
# return # return
# just guessing here in case that's a false negative # just guessing here in case that's a false negative
lPaSamplerates = [round(oPYA.get_device_info_by_index(iInput)['defaultSampleRate'])] lPaSamplerates = [round(self._audio.get_device_info_by_index(iInput)['defaultSampleRate'])]
if lPaSamplerates and self._audio_rate_pa in lPaSamplerates: if lPaSamplerates and self._audio_rate_pa in lPaSamplerates:
pass pass
elif lPaSamplerates: elif lPaSamplerates:
LOG_WARN(f"{self._audio_rate_pa} not in {lPaSamplerates}") LOG_WARN(f"{self._audio_rate_pa} not in {lPaSamplerates}")
self._audio_rate_pa = lPaSamplerates[0] self._audio_rate_pa = lPaSamplerates[0]
LOG_WARN(f"Setting audio_rate to: {lPaSamplerates[0]}") LOG_WARN(f"Setting audio_rate to: {lPaSamplerates[0]}")
elif 'defaultSampleRate' in oPYA.get_device_info_by_index(iInput): elif 'defaultSampleRate' in self._audio.get_device_info_by_index(iInput):
self._audio_rate_pa = oPYA.get_device_info_by_index(iInput)['defaultSampleRate'] self._audio_rate_pa = self._audio.get_device_info_by_index(iInput)['defaultSampleRate']
else: else:
LOG_WARN(f"{self._audio_rate_pa} not in {lPaSamplerates}") LOG_WARN(f"{self._audio_rate_pa} not in {lPaSamplerates}")
# a float is in here - must it be int? # a float is in here - must it be int?
@ -227,7 +235,7 @@ class AV(common.tox_save.ToxAvSave):
self._audio_rate_pa = lPaSamplerates[0] self._audio_rate_pa = lPaSamplerates[0]
if bSTREAM_CALLBACK: if bSTREAM_CALLBACK:
self._audio_stream = oPYA.open(format=pyaudio.paInt16, self._audio_stream = self._audio.open(format=pyaudio.paInt16,
rate=self._audio_rate_pa, rate=self._audio_rate_pa,
channels=self._audio_channels, channels=self._audio_channels,
input=True, input=True,
@ -242,7 +250,7 @@ class AV(common.tox_save.ToxAvSave):
self._audio_stream.close() self._audio_stream.close()
else: else:
self._audio_stream = oPYA.open(format=pyaudio.paInt16, self._audio_stream = self._audio.open(format=pyaudio.paInt16,
rate=self._audio_rate_pa, rate=self._audio_rate_pa,
channels=self._audio_channels, channels=self._audio_channels,
input=True, input=True,
@ -306,8 +314,7 @@ class AV(common.tox_save.ToxAvSave):
s['video']['width'], s['video']['width'],
s['video']['height']) s['video']['height'])
else: else:
with ts.ignoreStdout(): with ts.ignoreStdout(): import cv2
import cv2
if s['video']['device'] == 0: if s['video']['device'] == 0:
# webcam # webcam
self._video = cv2.VideoCapture(s['video']['device'], cv2.DSHOW) self._video = cv2.VideoCapture(s['video']['device'], cv2.DSHOW)
@ -366,23 +373,24 @@ class AV(common.tox_save.ToxAvSave):
LOG.warn(f"{rate} not in {self.lPaSampleratesO}") LOG.warn(f"{rate} not in {self.lPaSampleratesO}")
LOG.warn(f"Setting audio_rate to: {self.lPaSampleratesO[0]}") LOG.warn(f"Setting audio_rate to: {self.lPaSampleratesO[0]}")
rate = self.lPaSampleratesO[0] rate = self.lPaSampleratesO[0]
elif 'defaultSampleRate' in oPYA.get_device_info_by_index(iOutput): elif 'defaultSampleRate' in self._audio.get_device_info_by_index(iOutput):
rate = round(oPYA.get_device_info_by_index(iOutput)['defaultSampleRate']) rate = round(self._audio.get_device_info_by_index(iOutput)['defaultSampleRate'])
LOG.warn(f"Setting rate to {rate} empty self.lPaSampleratesO") LOG.warn(f"Setting rate to {rate} empty self.lPaSampleratesO")
else: else:
LOG.warn(f"Using rate {rate} empty self.lPaSampleratesO") LOG.warn(f"Using rate {rate} empty self.lPaSampleratesO")
if type(rate) == float: if type(rate) == float:
rate = round(rate) rate = round(rate)
# test output device?
# [Errno -9985] Device unavailable
try: try:
with ts.ignoreStderr(): with ts.ignoreStderr():
self._out_stream = oPYA.open(format=pyaudio.paInt16, self._out_stream = self._audio.open(format=pyaudio.paInt16,
channels=channels_count, channels=channels_count,
rate=rate, rate=rate,
output_device_index=iOutput, output_device_index=iOutput,
output=True) output=True)
except Exception as e: except Exception as e:
LOG.error(f"Error playing audio_chunk creating self._out_stream {e}") LOG.error(f"Error playing audio_chunk creating self._out_stream output_device_index={iOutput} {e}")
invoke_in_main_thread(util_ui.message_box, invoke_in_main_thread(util_ui.message_box,
str(e), str(e),
util_ui.tr("Error Chunking audio")) util_ui.tr("Error Chunking audio"))

View File

@ -114,44 +114,49 @@ class CallsManager:
util_ui.tr('ERROR Accepting call from {friend_number}')) util_ui.tr('ERROR Accepting call from {friend_number}'))
else: else:
self._main_screen.active_call() self._main_screen.active_call()
finally: finally:
# does not terminate call - just the av_widget # does not terminate call - just the av_widget
if friend_number in self._incoming_calls: LOG.debug(f"CM.accept_call close av_widget")
self._incoming_calls.remove(friend_number)
try: try:
self._call_widgets[friend_number].close() self._call_widgets[friend_number].close()
del self._call_widgets[friend_number] del self._call_widgets[friend_number]
except: if friend_number in self._incoming_calls:
self._incoming_calls.remove(friend_number)
except Exception as e:
# RuntimeError: wrapped C/C++ object of type IncomingCallWidget has been deleted # RuntimeError: wrapped C/C++ object of type IncomingCallWidget has been deleted
pass LOG.warn(f" closed self._call_widgets[{friend_number}] {e}")
LOG.debug(f" closed self._call_widgets[{friend_number}]") LOG.debug(f" closed self._call_widgets[{friend_number}]")
def stop_call(self, friend_number, by_friend): def stop_call(self, friend_number, by_friend):
""" """
Stop call with friend Stop call with friend
""" """
LOG.debug(__name__+f" stop_call {friend_number}") LOG.debug(f"CM.stop_call friend={friend_number}")
if friend_number in self._incoming_calls: if friend_number in self._incoming_calls:
self._incoming_calls.remove(friend_number) self._incoming_calls.remove(friend_number)
is_declined = True is_declined = True
else: else:
is_declined = False is_declined = False
self._main_screen.call_finished()
self._callav.finish_call(friend_number, by_friend) # finish or decline call
if friend_number in self._call_widgets: if friend_number in self._call_widgets:
LOG.debug(f"CM.stop_call _call_widgets close")
self._call_widgets[friend_number].close() self._call_widgets[friend_number].close()
del self._call_widgets[friend_number] del self._call_widgets[friend_number]
def destroy_window(): LOG.debug(f"CM.stop_call _main_screen.call_finished")
#??? FixMed self._main_screen.call_finished()
is_video = self._callav.is_video_call(friend_number) self._callav.finish_call(friend_number, by_friend) # finish or decline call
if is_video: is_video = self._callav.is_video_call(friend_number)
import cv2 if is_video:
def destroy_window():
#??? FixMe
with ts.ignoreStdout(): import cv2
cv2.destroyWindow(str(friend_number)) cv2.destroyWindow(str(friend_number))
LOG.debug(f"CM.stop_call destroy_window")
threading.Timer(2.0, destroy_window).start()
threading.Timer(2.0, destroy_window).start() LOG.debug(f"CM.stop_call _call_finished_event")
self._call_finished_event(friend_number, is_declined) self._call_finished_event(friend_number, is_declined)
def friend_exit(self, friend_number): def friend_exit(self, friend_number):

View File

@ -365,7 +365,7 @@ def callback_audio(calls_manager):
""" """
New audio chunk New audio chunk
""" """
LOG_DEBUG(f"callback_audio #{friend_number}") #trace LOG_DEBUG(f"callback_audio #{friend_number}")
# dunno was .call # dunno was .call
calls_manager._call.audio_chunk( calls_manager._call.audio_chunk(
bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]), bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
@ -402,7 +402,7 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u
It can be created from initial y, u, v using slices It can be created from initial y, u, v using slices
""" """
LOG_DEBUG(f"video_receive_frame from toxav_video_receive_frame_cb={friend_number}") LOG_DEBUG(f"video_receive_frame from toxav_video_receive_frame_cb={friend_number}")
import cv2 with ts.ignoreStdout(): import cv2
import numpy as np import numpy as np
try: try:
y_size = abs(max(width, abs(ystride))) y_size = abs(max(width, abs(ystride)))

File diff suppressed because one or more lines are too long

View File

@ -118,7 +118,7 @@ class IncomingCallWidget(widgets.CenteredWidget):
self.thread = None self.thread = None
def stop(self): def stop(self):
LOG.debug(f"stop from {self._friend_number}") LOG.debug(f"stop from friend_number={self._friend_number}")
if self._processing: if self._processing:
self.close() self.close()
if self.thread is not None: if self.thread is not None:
@ -147,7 +147,9 @@ class IncomingCallWidget(widgets.CenteredWidget):
try: try:
self._calls_manager.accept_call(self._friend_number, True, False) self._calls_manager.accept_call(self._friend_number, True, False)
finally: finally:
self.stop() #? self.stop()
LOG.debug(f" accept_call_with_audio NOT stop from={self._friend_number}")
pass
def accept_call_with_video(self): def accept_call_with_video(self):
# ts.trepan_handler() # ts.trepan_handler()

View File

@ -1,5 +1,6 @@
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*- # -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import os import os
import traceback
from qtpy import uic from qtpy import uic
from qtpy import QtCore, QtGui, QtWidgets from qtpy import QtCore, QtGui, QtWidgets
@ -701,10 +702,14 @@ class MainWindow(QtWidgets.QMainWindow):
if self._we: if self._we:
self._we.show() self._we.show()
return return
LOG.info("Loading WeechatConsole") try:
from qweechat import qweechat
LOG.info("Loading WeechatConsole")
except ImportError as e:
LOG.error(f"ImportError Loading import qweechat {e} {sys.path}")
LOG.debug(traceback.print_exc())
return
from third_party.qweechat import qweechat
from third_party.qweechat import config
try: try:
# WeeChat backported from PySide6 to PyQt5 # WeeChat backported from PySide6 to PyQt5
LOG.info("Adding WeechatConsole") LOG.info("Adding WeechatConsole")

View File

@ -4,8 +4,7 @@ import urllib
import re import re
from qtpy import QtCore, QtGui, QtWidgets from qtpy import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import pyqtSignal from qtpy.QtCore import Signal
Signal = pyqtSignal
from ui.widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit from ui.widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit
import utils.util as util import utils.util as util

View File

@ -594,7 +594,7 @@ class VideoSettings(CenteredWidget):
def _update_ui(self): def _update_ui(self):
try: try:
import cv2 with ts.ignoreStdout(): import cv2
except ImportError: except ImportError:
cv2 = None cv2 = None
self.deviceComboBox.currentIndexChanged.connect(self._device_changed) self.deviceComboBox.currentIndexChanged.connect(self._device_changed)

View File

@ -43,7 +43,7 @@ class WidgetsFactory:
def create_video_settings_window(self): def create_video_settings_window(self):
try: try:
import cv2 with ts.ignoreStdout(): import cv2
except ImportError: except ImportError:
cv2 = None cv2 = None
if cv2 is None: return None if cv2 is None: return None

View File

@ -10,11 +10,11 @@ import subprocess
global LOG global LOG
import logging import logging
LOG = logging.getLogger('app.'+__name__) LOG = logging.getLogger('app.'+__name__)
log = lambda x: LOG.info(x)
TIMEOUT=10 TIMEOUT=10
def connection_available(): def connection_available():
return False
try: try:
urllib.request.urlopen('http://216.58.192.142', timeout=TIMEOUT) # google.com urllib.request.urlopen('http://216.58.192.142', timeout=TIMEOUT) # google.com
return True return True