calls.py rewriting

This commit is contained in:
ingvar1995 2017-06-13 21:40:54 +03:00
parent 464fba23c5
commit 6d705deb55
1 changed files with 107 additions and 65 deletions

View File

@ -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)