mirror of
https://github.com/azlux/pymumble
synced 2024-11-23 13:56:26 +00:00
143 lines
5.0 KiB
Python
143 lines
5.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
import time
|
|
from threading import Lock
|
|
from collections import deque
|
|
|
|
import opuslib
|
|
|
|
from .constants import *
|
|
|
|
|
|
class SoundQueue:
|
|
"""
|
|
Per user storage of received audio frames
|
|
Takes care of the decoding of the received audio
|
|
"""
|
|
def __init__(self, mumble_object):
|
|
self.mumble_object = mumble_object
|
|
|
|
self.queue = deque()
|
|
self.start_sequence = None
|
|
self.start_time = None
|
|
|
|
self.receive_sound = True
|
|
|
|
self.lock = Lock()
|
|
|
|
# to be sure, create every supported decoders for all users
|
|
# sometime, clients still use a codec for a while after server request another...
|
|
self.decoders = {
|
|
PYMUMBLE_AUDIO_TYPE_OPUS: opuslib.Decoder(PYMUMBLE_SAMPLERATE, 1)
|
|
}
|
|
|
|
def set_receive_sound(self, value):
|
|
"""Define if received sounds must be kept or discarded in this specific queue (user)"""
|
|
if value:
|
|
self.receive_sound = True
|
|
else:
|
|
self.receive_sound = False
|
|
|
|
def add(self, audio, sequence, type, target):
|
|
"""Add a new audio frame to the queue, after decoding"""
|
|
if not self.receive_sound:
|
|
return None
|
|
|
|
self.lock.acquire()
|
|
|
|
try:
|
|
pcm = self.decoders[type].decode(audio, PYMUMBLE_READ_BUFFER_SIZE)
|
|
|
|
if not self.start_sequence or sequence <= self.start_sequence:
|
|
# New sequence started
|
|
self.start_time = time.time()
|
|
self.start_sequence = sequence
|
|
calculated_time = self.start_time
|
|
else:
|
|
# calculating position in current sequence
|
|
calculated_time = self.start_time + (sequence - self.start_sequence) * PYMUMBLE_SEQUENCE_DURATION
|
|
|
|
newsound = SoundChunk(pcm, sequence, len(pcm), calculated_time, type, target)
|
|
|
|
if not self.mumble_object.callbacks.get_callback(PYMUMBLE_CLBK_SOUNDRECEIVED):
|
|
self.queue.appendleft(newsound)
|
|
|
|
if len(self.queue) > 1 and self.queue[0].time < self.queue[1].time:
|
|
# sort the audio chunk if it came out of order
|
|
cpt = 0
|
|
while cpt < len(self.queue) - 1 and self.queue[cpt].time < self.queue[cpt+1].time:
|
|
tmp = self.queue[cpt+1]
|
|
self.queue[cpt+1] = self.queue[cpt]
|
|
self.queue[cpt] = tmp
|
|
|
|
self.lock.release()
|
|
return newsound
|
|
except KeyError:
|
|
self.lock.release()
|
|
self.mumble_object.Log.error("Codec not supported (audio packet type {0})".format(type))
|
|
except Exception as e:
|
|
self.lock.release()
|
|
self.mumble_object.Log.error("error while decoding audio. sequence:{seq}, type:{type}. {error}".format(seq=sequence, type=type, error=str(e)))
|
|
|
|
def is_sound(self):
|
|
"""Boolean to check if there is a sound frame in the queue"""
|
|
if len(self.queue) > 0:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def get_sound(self, duration=None):
|
|
"""Return the first sound of the queue and discard it"""
|
|
self.lock.acquire()
|
|
|
|
if len(self.queue) > 0:
|
|
if duration is None or self.first_sound().duration <= duration:
|
|
result = self.queue.pop()
|
|
else:
|
|
result = self.first_sound().extract_sound(duration)
|
|
else:
|
|
result = None
|
|
|
|
self.lock.release()
|
|
return result
|
|
|
|
def first_sound(self):
|
|
"""Return the first sound of the queue, but keep it"""
|
|
if len(self.queue) > 0:
|
|
return self.queue[-1]
|
|
else:
|
|
return None
|
|
|
|
|
|
class SoundChunk:
|
|
"""
|
|
Object that contains the actual audio frame, in PCM format"""
|
|
def __init__(self, pcm, sequence, size, calculated_time, type, target, timestamp=time.time()):
|
|
self.timestamp = timestamp # measured time of arrival of the sound
|
|
self.time = calculated_time # calculated time of arrival of the sound (based on sequence)
|
|
self.pcm = pcm # audio data
|
|
self.sequence = sequence # sequence of the packet
|
|
self.size = size # size
|
|
self.duration = float(size) / 2 / PYMUMBLE_SAMPLERATE # duration in sec
|
|
self.type = type # type of the audio (codec)
|
|
self.target = target # target of the audio
|
|
|
|
def extract_sound(self, duration):
|
|
"""Extract part of the chunk, leaving a valid chunk for the remaining part"""
|
|
size = int(duration*2*PYMUMBLE_SAMPLERATE)
|
|
result = SoundChunk(
|
|
self.pcm[:size],
|
|
self.sequence,
|
|
size,
|
|
self.time,
|
|
self.type,
|
|
self.target,
|
|
self.timestamp
|
|
)
|
|
|
|
self.pcm = self.pcm[size:]
|
|
self.duration -= duration
|
|
self.time += duration
|
|
self.size -= size
|
|
|
|
return result
|