PyVoIp отправляет звук в очень плохом качестве - просто шум

Рейтинг: 0Ответов: 1Опубликовано: 25.06.2023

Я использую pyvoip 2.0.0-dev(потому что она поддерживает OPTIONS запросы и digest auth). Я использую этот аудио файл. Когда вызываю функцию write_audio в телефоне слышны только шумы. Это может быть связано с тем что sip сервер не поддерживает PCMU/PCMA кодеки или другые проблемы с сервером?

Возникла вторая проблема - функция read_audio не работает, ничего не возвращает

def voip_callback(call: VoIPCall):
    print("CALLBACK STATEE", call.state)
    while call.state == CallState.ANSWERED:
        r = call.read_audio(blocking=True)
        print(r)
        call.write_audio(r)

        time.sleep(0.01)

Если blocking=True цикл блочиться, если blocking=False всегда возвращаются одинаковые пакеты:

b'\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80'

Если записать это аудио в файл - ничего не услышишь. Если отправлять сразу в write_audio - в телефоне тоже ничего не услышишь

Полный код оставлю здесь, выше виделил только основной:

    def voip_callback(self, call: VoIPCall):
        is_incoming_call = call.id not in self.calls
        if is_incoming_call:
            phone = call.request.headers["From"]["user"]
            self.calls[call.id] = call
            self.ws.send_sync({
                "status": "success",
                "event": "calls:incoming_call",
                "data": {"call_id": call.id, "state": "PENDING", "phone": phone}
            })
        else:
            phone = call.request.headers["To"]["user"]

        self.voice_buffers[call.id] = {
            "receive": BytesIO(),
            "send": BytesIO()
        }
        self.calls_statuses[call.id] = {
            "webrtc": False,
            "ws": False
        }
        start_time = time.time()
        answered_at = 0

        wait_sound_is_activated = False
        dialing_sound_is_activated = False
        is_answered = False
        not_connect_webrtc_sound_is_activated = False
        sent_is_answered_by_user = False

        stop_at = None

        try:
            while call.state != CallState.ENDED:
                now = time.time()
                call_seconds = now-start_time
                if answered_at != 0:
                    answered_seconds = now-answered_at
                else:
                    answered_seconds = 0

                print(call.state)
                call_status = self.calls_statuses[call.id]
                if stop_at is not None and now >= stop_at:
                    if call.state in (CallState.DIALING, CallState.PROGRESS):
                        call.cancel()
                    else:
                        try:
                            call.hangup()
                        except InvalidStateError:
                            call.bye()

                    break

                if call.state == CallState.ANSWERED and not is_incoming_call and not sent_is_answered_by_user:
                    # Send ws message when user answer the call
                    self.ws.send_sync({
                        "status": "success",
                        "event": "calls:answered",
                        "data": {"call_id": call.id, "state": "ANSWERED"}
                    })
                    sent_is_answered_by_user = True

                operator_is_connected = call_status["webrtc"] and call_status["ws"]
                if call.state == CallState.ANSWERED and operator_is_connected:
                    # User and operator can speak only if call is answered and operator connect by ws and webrtc
                    is_answered = True
                    if answered_at == 0:
                        answered_at = now

                    self.voice_buffers[call.id]["receive"].write(call.read_audio(blocking=False))
                    call.write_audio(self.voice_buffers[call.id]["send"].read())
                elif (
                        call.state == CallState.ANSWERED and
                        answered_seconds >= 15 and
                        call_status["ws"] and
                        not call_status["webrtc"] and
                        not not_connect_webrtc_sound_is_activated
                ):
                    # If operator doenst connect to webrtc
                    print("ANSWERED 2: Operator dont connected to webrtc after 15 seconds")
                    call.write_audio(self.wait_sound)
                    stop_at = now+30
                    not_connect_webrtc_sound_is_activated = True
                elif (
                        call.state == CallState.RINGING and
                        call_seconds >= 30 and
                        is_incoming_call and
                        not wait_sound_is_activated
                ):
                    # User wait for operator answer
                    print("RINGING 1: User wait for operator answer")
                    # call.write_audio(self.wait_sound)
                    stop_at = now+30
                    wait_sound_is_activated = True
                elif (
                        call.state in (CallState.DIALING, CallState.PROGRESS) and
                        call_seconds >= 10 and
                        not is_incoming_call and
                        not dialing_sound_is_activated and
                        operator_is_connected
                ):
                    # Operator wait for user answer
                    print("DIALING 1: Operator wait for user answer")
                    self.voice_buffers[call.id]["receive"].write(self.dialing_sound)
                    stop_at = now+10
                    dialing_sound_is_activated = True
                elif (
                        call.state == CallState.ANSWERED and
                        call_seconds >= 5 and
                        not is_incoming_call and
                        not dialing_sound_is_activated
                ):
                    print("ELSE, FOR TESTING WRITE AUDIO")
                    call.write_audio(self.wait_sound)
                    stop_at = now+30
                    dialing_sound_is_activated = True

                time.sleep(0.01)
        except Exception as e:
            print(e)
        finally:
            self.calls.pop(call.id)
            self.calls_statuses.pop(call.id)
            self.ws.send_sync({
                "status": "success",
                "event": "calls:ended",
                "data": {
                    "state": "LOST" if is_incoming_call and not is_answered else "ENDED",
                    "call_id": call.id
                }
            })

Возможно есть какие-то решения по улучшению, критику з радостью прийму

Ответы

▲ 1Принят

Для решения необходима конвертация файла в подходящий аудио формат, можно воспользоваться двумя подходами:

  • через консольную комманду
  • через программу с интерфейсом.

Для первого случая понадобится установка ffmpeg установка для linux (перед установкой можно проверить не установлена ли она уже):

sudo apt-get install ffmpeg

После того как программа будет установлена, нужно можно проверить ее версию, чтобы убедится в том что консоль находит программу, достаточно ввывести информацию о версии, так:

ffmpeg -version

Если пути настроенны правильно то программа выведет информацию, иначе команда будет не распознана и отобразиться ошибка. Далее можно перейти в каталог с файлами и сделать выполнить следующий код подставив имена входного и выходного файлов:

 ffmpeg -i wait.wav -ar 8000 -ac 1 -acodec pcm_u8 wait1.wav

Тут -i — имя входного файла, -ar — частота семпла, -ac — число каналов, -acodec pcm_u8 задает 8 битный акдио кодек PCM, после этого идет имя выходного файла.

Для второго способа можно установить кроссплатформенную программу audacity которая в своей работе так же использует ffmpeg

После установки можно открыть файл с помощью программы, откроется подобное окно:

введите сюда описание изображения

Как видно звуковых дорожек 2 поэтому нужно оставить только одну. Для этог в выпадающем меню:

введите сюда описание изображения

Нужно выбрать комманду Split stereo to mono (В переводе: разделить стерео на моно)

После чего вместо одного двухканального трека появятся 2 одноканальных:

введите сюда описание изображения

Далее нужно удалить любой из каналов нажав на крестик:

введите сюда описание изображения

После чего можно выбрать частоту семпла (8000) в нижнем левом углу окна из выпадающего списка, таким образом задасться частота для проекта, (хотя в треке это никак не отобразиться):

введите сюда описание изображения

После этого можно экспортировать, через File -> Export -> Export as WAV в диалоговом окне сохранения нужно выбрать 8-bit PCM в качестве кодирования, поменять имя файла (желательно), после чего нажать Save

введите сюда описание изображения

Интерфейс программы в разных OS может отличаться, но я думаю, все выглядит похоже.