calls.py rewriting
This commit is contained in:
parent
464fba23c5
commit
6d705deb55
1 changed files with 107 additions and 65 deletions
172
toxygen/calls.py
172
toxygen/calls.py
|
@ -7,15 +7,47 @@ import cv2
|
||||||
import itertools
|
import itertools
|
||||||
import numpy as np
|
import numpy as np
|
||||||
# TODO: play sound until outgoing call will be started or cancelled and add timeout
|
# TODO: play sound until outgoing call will be started or cancelled and add timeout
|
||||||
# TODO: rewrite logic
|
|
||||||
|
|
||||||
|
|
||||||
class Call:
|
class Call:
|
||||||
|
|
||||||
def __init__(self, audio=False, video=False):
|
def __init__(self, out_audio, out_video, in_audio=False, in_video=False):
|
||||||
self.audio = audio
|
self._in_audio = in_audio
|
||||||
self.video = video
|
self._in_video = in_video
|
||||||
# TODO: add widget for call
|
self._out_audio = out_audio
|
||||||
|
self._out_video = out_video
|
||||||
|
|
||||||
|
def get_in_audio(self):
|
||||||
|
return self._in_audio
|
||||||
|
|
||||||
|
def set_in_audio(self, value):
|
||||||
|
self._in_audio = value
|
||||||
|
|
||||||
|
in_audio = property(get_in_audio, set_in_audio)
|
||||||
|
|
||||||
|
def get_out_audio(self):
|
||||||
|
return self._out_audio
|
||||||
|
|
||||||
|
def set_out_audio(self, value):
|
||||||
|
self._out_audio = value
|
||||||
|
|
||||||
|
out_audio = property(get_out_audio, set_out_audio)
|
||||||
|
|
||||||
|
def get_in_video(self):
|
||||||
|
return self._in_video
|
||||||
|
|
||||||
|
def set_in_video(self, value):
|
||||||
|
self._in_video = value
|
||||||
|
|
||||||
|
in_video = property(get_in_video, set_in_video)
|
||||||
|
|
||||||
|
def get_out_video(self):
|
||||||
|
return self._out_video
|
||||||
|
|
||||||
|
def set_out_video(self, value):
|
||||||
|
self._in_video = value
|
||||||
|
|
||||||
|
out_video = property(get_out_video, set_out_video)
|
||||||
|
|
||||||
|
|
||||||
class AV:
|
class AV:
|
||||||
|
@ -41,6 +73,9 @@ class AV:
|
||||||
self._video_thread = None
|
self._video_thread = None
|
||||||
self._video_running = False
|
self._video_running = False
|
||||||
|
|
||||||
|
self._video_width = 640
|
||||||
|
self._video_height = 480
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self._running = False
|
self._running = False
|
||||||
self.stop_audio_thread()
|
self.stop_audio_thread()
|
||||||
|
@ -57,15 +92,15 @@ class AV:
|
||||||
"""Call friend with specified number"""
|
"""Call friend with specified number"""
|
||||||
self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0)
|
self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0)
|
||||||
self._calls[friend_number] = Call(audio, video)
|
self._calls[friend_number] = Call(audio, video)
|
||||||
self.start_audio_thread()
|
|
||||||
self.start_video_thread()
|
|
||||||
|
|
||||||
def accept_call(self, friend_number, audio_enabled, video_enabled):
|
def accept_call(self, friend_number, audio_enabled, video_enabled):
|
||||||
|
|
||||||
if self._running:
|
if self._running:
|
||||||
self._calls[friend_number] = Call(audio_enabled, video_enabled)
|
self._calls[friend_number] = Call(audio_enabled, video_enabled)
|
||||||
self._toxav.answer(friend_number, 32 if audio_enabled else 0, 5000 if video_enabled else 0)
|
self._toxav.answer(friend_number, 32 if audio_enabled else 0, 5000 if video_enabled else 0)
|
||||||
self.start_audio_thread()
|
if audio_enabled:
|
||||||
|
self.start_audio_thread()
|
||||||
|
if video_enabled:
|
||||||
|
self.start_video_thread()
|
||||||
|
|
||||||
def finish_call(self, friend_number, by_friend=False):
|
def finish_call(self, friend_number, by_friend=False):
|
||||||
|
|
||||||
|
@ -73,20 +108,25 @@ class AV:
|
||||||
self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL'])
|
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]
|
||||||
if not len(self._calls):
|
if not len(list(filter(lambda c: c.out_audio, self._calls))):
|
||||||
self.stop_audio_thread()
|
self.stop_audio_thread()
|
||||||
|
if not len(list(filter(lambda c: c.out_video, self._calls))):
|
||||||
|
self.stop_video_thread()
|
||||||
|
|
||||||
def toxav_call_state_cb(self, friend_number, state):
|
def toxav_call_state_cb(self, friend_number, state):
|
||||||
"""
|
"""
|
||||||
New call state
|
New call state
|
||||||
"""
|
"""
|
||||||
pass # TODO: ignore?
|
call = self._calls[friend_number]
|
||||||
# if self._running:
|
|
||||||
#
|
call.in_audio = state | TOXAV_FRIEND_CALL_STATE['SENDING_A']
|
||||||
# if state & TOXAV_FRIEND_CALL_STATE['ACCEPTING_A']:
|
call.in_video = state | TOXAV_FRIEND_CALL_STATE['SENDING_V']
|
||||||
# self._calls[friend_number].audio = True
|
|
||||||
# if state & TOXAV_FRIEND_CALL_STATE['ACCEPTING_V']:
|
if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_A'] and call.out_audio:
|
||||||
# self._calls[friend_number].video = True
|
self.start_audio_thread()
|
||||||
|
|
||||||
|
if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_V'] and call.out_video:
|
||||||
|
self.start_video_thread()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Threads
|
# Threads
|
||||||
|
@ -136,10 +176,13 @@ class AV:
|
||||||
|
|
||||||
self._video_running = True
|
self._video_running = True
|
||||||
|
|
||||||
|
self._video_width = 640 # TODO: use settings
|
||||||
|
self._video_height = 480
|
||||||
|
|
||||||
self._video = cv2.VideoCapture(0)
|
self._video = cv2.VideoCapture(0)
|
||||||
self._video.set(cv2.CAP_PROP_FPS, 25)
|
self._video.set(cv2.CAP_PROP_FPS, 25)
|
||||||
self._video.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
|
self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width)
|
||||||
self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
|
self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height)
|
||||||
|
|
||||||
self._video_thread = threading.Thread(target=self.send_video)
|
self._video_thread = threading.Thread(target=self.send_video)
|
||||||
self._video_thread.start()
|
self._video_thread.start()
|
||||||
|
@ -170,9 +213,6 @@ class AV:
|
||||||
output=True)
|
output=True)
|
||||||
self._out_stream.write(samples)
|
self._out_stream.write(samples)
|
||||||
|
|
||||||
def video_chunk(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# AV sending
|
# AV sending
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
@ -187,7 +227,7 @@ class AV:
|
||||||
pcm = self._audio_stream.read(self._audio_sample_count)
|
pcm = self._audio_stream.read(self._audio_sample_count)
|
||||||
if pcm:
|
if pcm:
|
||||||
for friend_num in self._calls:
|
for friend_num in self._calls:
|
||||||
if self._calls[friend_num].audio:
|
if self._calls[friend_num].out_audio:
|
||||||
try:
|
try:
|
||||||
self._toxav.audio_send_frame(friend_num, pcm, self._audio_sample_count,
|
self._toxav.audio_send_frame(friend_num, pcm, self._audio_sample_count,
|
||||||
self._audio_channels, self._audio_rate)
|
self._audio_channels, self._audio_rate)
|
||||||
|
@ -199,15 +239,18 @@ class AV:
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
|
|
||||||
def send_video(self):
|
def send_video(self):
|
||||||
|
"""
|
||||||
|
This method sends video to friends
|
||||||
|
"""
|
||||||
while self._video_running:
|
while self._video_running:
|
||||||
try:
|
try:
|
||||||
result, frame = self._video.read()
|
result, frame = self._video.read()
|
||||||
if result:
|
if result:
|
||||||
height, width, channels = frame.shape
|
height, width, channels = frame.shape
|
||||||
for friend_num in self._calls:
|
for friend_num in self._calls:
|
||||||
if self._calls[friend_num].video:
|
if self._calls[friend_num].out_video:
|
||||||
try:
|
try:
|
||||||
y, u, v = convert_bgr_to_yuv(frame)
|
y, u, v = self.convert_bgr_to_yuv(frame)
|
||||||
self._toxav.video_send_frame(friend_num, width, height, y, u, v)
|
self._toxav.video_send_frame(friend_num, width, height, y, u, v)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
@ -216,51 +259,50 @@ class AV:
|
||||||
|
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
def convert_bgr_to_yuv(self, frame):
|
||||||
|
"""
|
||||||
|
:param frame: input bgr frame
|
||||||
|
:return y, u, v: y, u, v values of frame
|
||||||
|
|
||||||
def convert_bgr_to_yuv(frame): # TODO: remove hardcoded values
|
How this function works:
|
||||||
"""
|
OpenCV creates YUV420 frame from BGR
|
||||||
:param frame: input bgr frame
|
This frame has following structure and size:
|
||||||
:return y, u, v: y, u, v values of frame
|
width, height - dim of input frame
|
||||||
|
width, height * 1.5 - dim of output frame
|
||||||
|
|
||||||
How this function works:
|
width
|
||||||
OpenCV creates YUV420 frame from BGR
|
-------------------------
|
||||||
This frame has following structure and size:
|
| |
|
||||||
width, height - dim of input frame
|
| Y | height
|
||||||
width, height * 1.5 - dim of output frame
|
| |
|
||||||
|
-------------------------
|
||||||
|
| | |
|
||||||
|
| U even | U odd | height // 4
|
||||||
|
| | |
|
||||||
|
-------------------------
|
||||||
|
| | |
|
||||||
|
| V even | V odd | height // 4
|
||||||
|
| | |
|
||||||
|
-------------------------
|
||||||
|
|
||||||
width
|
width // 2 width // 2
|
||||||
-------------------------
|
|
||||||
| |
|
|
||||||
| Y | height
|
|
||||||
| |
|
|
||||||
-------------------------
|
|
||||||
| | |
|
|
||||||
| U even | U odd | height // 4
|
|
||||||
| | |
|
|
||||||
-------------------------
|
|
||||||
| | |
|
|
||||||
| V even | V odd | height // 4
|
|
||||||
| | |
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
width // 2 width // 2
|
Y, U, V can be extracted using slices and joined in one list using itertools.chain.from_iterable()
|
||||||
|
Function returns bytes(y), bytes(u), bytes(v), because it is required for ctypes
|
||||||
|
"""
|
||||||
|
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV_I420)
|
||||||
|
|
||||||
Y, U, V can be extracted using slices and joined in one list using itertools.chain.from_iterable()
|
y = frame[:self._video_height, :].tolist()
|
||||||
Function returns bytes(y), bytes(u), bytes(v), because it is required for ctypes
|
y = list(itertools.chain.from_iterable(y))
|
||||||
"""
|
|
||||||
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV_I420)
|
|
||||||
|
|
||||||
y = frame[:480, :].tolist()
|
u = np.zeros((self._video_width // 2, self._video_height // 2), dtype=np.int)
|
||||||
y = list(itertools.chain.from_iterable(y))
|
u[::2, :] = frame[self._video_height:self._video_height * 5 // 4, :self._video_height // 2]
|
||||||
|
u[1::2, :] = frame[self._video_height:self._video_height * 5 // 4, self._video_height // 2:]
|
||||||
|
u = list(itertools.chain.from_iterable(u))
|
||||||
|
|
||||||
u = np.zeros((240, 320), dtype=np.int)
|
v = np.zeros((self._video_width // 2, self._video_height // 2), dtype=np.int)
|
||||||
u[::2, :] = frame[480:600, :320]
|
v[::2, :] = frame[self._video_height * 5 // 4:, :self._video_height // 2]
|
||||||
u[1::2, :] = frame[480:600, 320:]
|
v[1::2, :] = frame[self._video_height * 5 // 4:, self._video_height // 2:]
|
||||||
u = list(itertools.chain.from_iterable(u))
|
v = list(itertools.chain.from_iterable(v))
|
||||||
|
|
||||||
v = np.zeros((240, 320), dtype=np.int)
|
return bytes(y), bytes(u), bytes(v)
|
||||||
v[::2, :] = frame[600:, :320]
|
|
||||||
v[1::2, :] = frame[600:, 320:]
|
|
||||||
v = list(itertools.chain.from_iterable(v))
|
|
||||||
|
|
||||||
return bytes(y), bytes(u), bytes(v)
|
|
||||||
|
|
Loading…
Reference in a new issue