2022-09-27 13:51:50 +00:00
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
2022-09-27 12:38:39 +00:00
import itertools
2022-11-17 15:26:55 +00:00
import threading
import time
2022-09-27 13:51:50 +00:00
2022-11-17 15:26:55 +00:00
import pyaudio
import wrapper_tests . support_testing as ts
import common . tox_save
2022-09-27 12:38:39 +00:00
from av import screen_sharing
from av . call import Call
2022-09-27 13:51:50 +00:00
from main import sleep
2022-11-17 15:26:55 +00:00
from middleware . threads import BaseThread , invoke_in_main_thread
from utils import ui as util_ui
from wrapper . toxav_enums import *
2022-09-27 13:51:50 +00:00
global LOG
import logging
2022-11-17 15:26:55 +00:00
2022-09-27 13:51:50 +00:00
LOG = logging . getLogger ( ' app. ' + __name__ )
2022-10-11 16:36:09 +00:00
# callbacks can be called in any thread so were being careful
def LOG_ERROR ( l ) : print ( ' EROR< ' + l )
def LOG_WARN ( l ) : print ( ' WARN< ' + l )
def LOG_INFO ( l ) :
bIsVerbose = hasattr ( __builtins__ , ' app ' ) and app . oArgs . loglevel < = 20 - 1
if bIsVerbose : print ( ' INFO< ' + l )
def LOG_DEBUG ( l ) :
bIsVerbose = hasattr ( __builtins__ , ' app ' ) and app . oArgs . loglevel < = 10 - 1
if bIsVerbose : print ( ' DBUG< ' + l )
def LOG_TRACE ( l ) :
bIsVerbose = hasattr ( __builtins__ , ' app ' ) and app . oArgs . loglevel < 10 - 1
pass # print('TRACE+ '+l)
2022-09-27 13:51:50 +00:00
TIMER_TIMEOUT = 30.0
bSTREAM_CALLBACK = False
2022-09-27 16:02:36 +00:00
iFPS = 25
2022-09-27 12:38:39 +00:00
class AV ( common . tox_save . ToxAvSave ) :
def __init__ ( self , toxav , settings ) :
super ( ) . __init__ ( toxav )
2022-10-11 16:36:09 +00:00
self . _toxav = toxav
2022-09-27 12:38:39 +00:00
self . _settings = settings
self . _running = True
2022-09-27 13:51:50 +00:00
s = settings
if ' video ' not in s :
LOG . warn ( " AV.__init__ ' video ' not in s " )
LOG . debug ( f " AV.__init__ { s !r} " )
elif ' device ' not in s [ ' video ' ] :
LOG . warn ( " AV.__init__ ' device ' not in s.video " )
LOG . debug ( f " AV.__init__ { s [ ' video ' ] !r} " )
2022-09-27 12:38:39 +00:00
self . _calls = { } # dict: key - friend number, value - Call instance
self . _audio = None
self . _audio_stream = None
self . _audio_thread = None
self . _audio_running = False
self . _out_stream = None
self . _audio_channels = 1
self . _audio_duration = 60
2022-09-27 13:51:50 +00:00
self . _audio_rate_pa = 48000
self . _audio_rate_tox = 48000
self . _audio_rate_pa = 48000
self . _audio_krate_tox_audio = self . _audio_rate_tox / / 1000
self . _audio_krate_tox_video = 5000
self . _audio_sample_count_pa = self . _audio_rate_pa * self . _audio_channels * self . _audio_duration / / 1000
self . _audio_sample_count_tox = self . _audio_rate_tox * self . _audio_channels * self . _audio_duration / / 1000
2022-09-27 12:38:39 +00:00
self . _video = None
self . _video_thread = None
2022-09-27 16:02:36 +00:00
self . _video_running = None
2022-09-27 12:38:39 +00:00
2022-09-27 13:51:50 +00:00
self . _video_width = 320
self . _video_height = 240
2022-10-11 16:36:09 +00:00
# was iOutput = self._settings._args.audio['output']
iInput = self . _settings [ ' audio ' ] [ ' input ' ]
self . lPaSampleratesI = ts . lSdSamplerates ( iInput )
iOutput = self . _settings [ ' audio ' ] [ ' output ' ]
2022-09-27 13:51:50 +00:00
self . lPaSampleratesO = ts . lSdSamplerates ( iOutput )
global oPYA
oPYA = self . _audio = pyaudio . PyAudio ( )
2022-09-27 12:38:39 +00:00
def stop ( self ) :
self . _running = False
self . stop_audio_thread ( )
self . stop_video_thread ( )
def __contains__ ( self , friend_number ) :
return friend_number in self . _calls
# -----------------------------------------------------------------------------------------------------------------
# Calls
# -----------------------------------------------------------------------------------------------------------------
def __call__ ( self , friend_number , audio , video ) :
""" Call friend with specified number """
2022-09-27 13:51:50 +00:00
if friend_number in self . _calls :
LOG . warn ( f " __call__ already has { friend_number } " )
return
if self . _audio_krate_tox_audio not in ts . lToxSampleratesK :
LOG . warn ( f " __call__ { self . _audio_krate_tox_audio } not in { ts . lToxSampleratesK } " )
try :
self . _toxav . call ( friend_number ,
self . _audio_krate_tox_audio if audio else 0 ,
self . _audio_krate_tox_video if video else 0 )
except ArgumentError as e :
LOG . warn ( f " _toxav.call already has { friend_number } " )
return
2022-09-27 12:38:39 +00:00
self . _calls [ friend_number ] = Call ( audio , video )
2022-09-27 13:51:50 +00:00
threading . Timer ( TIMER_TIMEOUT ,
lambda : self . finish_not_started_call ( friend_number ) ) . start ( )
2022-09-27 12:38:39 +00:00
def accept_call ( self , friend_number , audio_enabled , video_enabled ) :
2022-09-27 13:51:50 +00:00
# obsolete
return call_accept_call ( self , friend_number , audio_enabled , video_enabled )
2022-10-08 02:46:23 +00:00
2022-09-27 13:51:50 +00:00
def call_accept_call ( self , friend_number , audio_enabled , video_enabled ) :
LOG . debug ( f " call_accept_call from { friend_number } { self . _running } " +
f " { audio_enabled } { video_enabled } " )
# import pdb; pdb.set_trace() - gets into q Qt exec_ problem
# ts.trepan_handler()
if self . _audio_krate_tox_audio not in ts . lToxSampleratesK :
LOG . warn ( f " __call__ { self . _audio_krate_tox_audio } not in { ts . lToxSampleratesK } " )
2022-09-27 12:38:39 +00:00
if self . _running :
self . _calls [ friend_number ] = Call ( audio_enabled , video_enabled )
2022-09-27 13:51:50 +00:00
# audio_bit_rate: Audio bit rate in Kb/sec. Set this to 0 to disable audio sending.
# video_bit_rate: Video bit rate in Kb/sec. Set this to 0 to disable video sending.
try :
self . _toxav . answer ( friend_number ,
self . _audio_krate_tox_audio if audio_enabled else 0 ,
self . _audio_krate_tox_video if video_enabled else 0 )
except ArgumentError as e :
LOG . debug ( f " AV accept_call error from { friend_number } { self . _running } " +
f " { e } " )
raise
2022-09-27 12:38:39 +00:00
if audio_enabled :
2022-09-27 13:51:50 +00:00
# may raise
2022-09-27 12:38:39 +00:00
self . start_audio_thread ( )
if video_enabled :
2022-09-27 13:51:50 +00:00
# may raise
2022-09-27 12:38:39 +00:00
self . start_video_thread ( )
def finish_call ( self , friend_number , by_friend = False ) :
2022-09-27 13:51:50 +00:00
LOG . debug ( f " finish_call { friend_number } " )
2022-09-27 12:38:39 +00:00
if not by_friend :
self . _toxav . call_control ( friend_number , TOXAV_CALL_CONTROL [ ' CANCEL ' ] )
if friend_number in self . _calls :
del self . _calls [ friend_number ]
2022-09-27 13:51:50 +00:00
try :
# AttributeError: 'int' object has no attribute 'out_audio'
if not len ( list ( filter ( lambda c : c . out_audio , self . _calls ) ) ) :
self . stop_audio_thread ( )
if not len ( list ( filter ( lambda c : c . out_video , self . _calls ) ) ) :
self . stop_video_thread ( )
except Exception as e :
LOG . error ( f " finish_call FixMe: { e } " )
# dunno
2022-09-27 12:38:39 +00:00
self . stop_audio_thread ( )
self . stop_video_thread ( )
def finish_not_started_call ( self , friend_number ) :
if friend_number in self :
call = self . _calls [ friend_number ]
if not call . is_active :
self . finish_call ( friend_number )
def toxav_call_state_cb ( self , friend_number , state ) :
"""
New call state
"""
2022-09-27 13:51:50 +00:00
LOG . debug ( f " toxav_call_state_cb { friend_number } " )
2022-09-27 12:38:39 +00:00
call = self . _calls [ friend_number ]
call . is_active = True
call . in_audio = state | TOXAV_FRIEND_CALL_STATE [ ' SENDING_A ' ] > 0
call . in_video = state | TOXAV_FRIEND_CALL_STATE [ ' SENDING_V ' ] > 0
if state | TOXAV_FRIEND_CALL_STATE [ ' ACCEPTING_A ' ] and call . out_audio :
self . start_audio_thread ( )
if state | TOXAV_FRIEND_CALL_STATE [ ' ACCEPTING_V ' ] and call . out_video :
self . start_video_thread ( )
def is_video_call ( self , number ) :
return number in self and self . _calls [ number ] . in_video
# -----------------------------------------------------------------------------------------------------------------
# Threads
# -----------------------------------------------------------------------------------------------------------------
def start_audio_thread ( self ) :
"""
Start audio sending
2022-10-11 16:36:09 +00:00
from a callback
2022-09-27 12:38:39 +00:00
"""
2022-09-27 13:51:50 +00:00
global oPYA
2022-10-11 16:36:09 +00:00
# was iInput = self._settings._args.audio['input']
iInput = self . _settings [ ' audio ' ] [ ' input ' ]
2022-09-27 12:38:39 +00:00
if self . _audio_thread is not None :
2022-10-11 16:36:09 +00:00
LOG_WARN ( f " start_audio_thread device= { iInput } " )
2022-09-27 12:38:39 +00:00
return
2022-10-11 16:36:09 +00:00
LOG_DEBUG ( f " start_audio_thread device= { iInput } " )
2022-09-27 13:51:50 +00:00
lPaSamplerates = ts . lSdSamplerates ( iInput )
if not ( len ( lPaSamplerates ) ) :
e = f " No supported sample rates for device: audio[input]= { iInput !r} "
2022-10-11 16:36:09 +00:00
LOG_ERROR ( f " start_audio_thread { e } " )
#?? dunno - cancel call?
return
2022-09-27 13:51:50 +00:00
if not self . _audio_rate_pa in lPaSamplerates :
2022-10-11 16:36:09 +00:00
LOG_WARN ( f " { self . _audio_rate_pa } not in { lPaSamplerates !r} " )
2022-09-27 13:51:50 +00:00
if False :
self . _audio_rate_pa = oPYA . get_device_info_by_index ( iInput ) [ ' defaultSampleRate ' ]
else :
2022-10-11 16:36:09 +00:00
LOG_WARN ( f " Setting audio_rate to: { lPaSamplerates [ 0 ] } " )
2022-09-27 13:51:50 +00:00
self . _audio_rate_pa = lPaSamplerates [ 0 ]
try :
2022-10-11 16:36:09 +00:00
LOG_DEBUG ( f " start_audio_thread framerate: { self . _audio_rate_pa } " \
2022-09-27 13:51:50 +00:00
+ f " device: { iInput } "
+ f " supported: { lPaSamplerates !r} " )
if self . _audio_rate_pa not in lPaSamplerates :
2022-10-11 16:36:09 +00:00
LOG_WARN ( f " PAudio sampling rate was { self . _audio_rate_pa } changed to { lPaSamplerates [ 0 ] } " )
2022-09-27 13:51:50 +00:00
self . _audio_rate_pa = lPaSamplerates [ 0 ]
if bSTREAM_CALLBACK :
self . _audio_stream = oPYA . open ( format = pyaudio . paInt16 ,
rate = self . _audio_rate_pa ,
channels = self . _audio_channels ,
input = True ,
input_device_index = iInput ,
frames_per_buffer = self . _audio_sample_count_pa * 10 ,
stream_callback = self . send_audio_data )
self . _audio_running = True
self . _audio_stream . start_stream ( )
while self . _audio_stream . is_active ( ) :
sleep ( 0.1 )
self . _audio_stream . stop_stream ( )
self . _audio_stream . close ( )
else :
self . _audio_stream = oPYA . open ( format = pyaudio . paInt16 ,
rate = self . _audio_rate_pa ,
channels = self . _audio_channels ,
input = True ,
input_device_index = iInput ,
frames_per_buffer = self . _audio_sample_count_pa * 10 )
self . _audio_running = True
self . _audio_thread = BaseThread ( target = self . send_audio ,
name = ' _audio_thread ' )
self . _audio_thread . start ( )
except Exception as e :
LOG . error ( f " Starting self._audio.open { e } " )
LOG . debug ( repr ( dict ( format = pyaudio . paInt16 ,
rate = self . _audio_rate_pa ,
channels = self . _audio_channels ,
input = True ,
input_device_index = iInput ,
frames_per_buffer = self . _audio_sample_count_pa * 10 ) ) )
2022-10-11 16:36:09 +00:00
# catcher in place in calls_manager? not if from a callback
# calls_manager._call.toxav_call_state_cb(friend_number, mask)
# raise RuntimeError(e)
return
2022-09-27 13:51:50 +00:00
else :
2022-10-11 16:36:09 +00:00
LOG_DEBUG ( f " start_audio_thread { self . _audio_stream !r} " )
2022-09-27 12:38:39 +00:00
def stop_audio_thread ( self ) :
if self . _audio_thread is None :
return
self . _audio_running = False
self . _audio_thread = None
self . _audio_stream = None
self . _audio = None
if self . _out_stream is not None :
self . _out_stream . stop_stream ( )
self . _out_stream . close ( )
self . _out_stream = None
def start_video_thread ( self ) :
if self . _video_thread is not None :
return
2022-09-27 13:51:50 +00:00
s = self . _settings
if ' video ' not in s :
LOG . warn ( " AV.__init__ ' video ' not in s " )
LOG . debug ( f " start_video_thread { s !r} " )
raise RuntimeError ( " start_video_thread not ' video ' in s) " )
elif ' device ' not in s [ ' video ' ] :
LOG . error ( " start_video_thread not ' device ' in s[ ' video ' ] " )
LOG . debug ( f " start_video_thread { s [ ' video ' ] !r} " )
raise RuntimeError ( " start_video_thread not ' device ' ins s[ ' video ' ] " )
self . _video_width = s [ ' video ' ] [ ' width ' ]
self . _video_height = s [ ' video ' ] [ ' height ' ]
2022-11-05 01:16:25 +00:00
# dunno
2022-09-27 16:02:36 +00:00
if True or s [ ' video ' ] [ ' device ' ] == - 1 :
2022-09-27 13:51:50 +00:00
self . _video = screen_sharing . DesktopGrabber ( s [ ' video ' ] [ ' x ' ] ,
s [ ' video ' ] [ ' y ' ] ,
s [ ' video ' ] [ ' width ' ] ,
s [ ' video ' ] [ ' height ' ] )
2022-09-27 12:38:39 +00:00
else :
2022-09-27 13:51:50 +00:00
with ts . ignoreStdout ( ) :
import cv2
2022-09-27 16:02:36 +00:00
if s [ ' video ' ] [ ' device ' ] == 0 :
# webcam
self . _video = cv2 . VideoCapture ( s [ ' video ' ] [ ' device ' ] , cv2 . DSHOW )
else :
2022-09-27 13:51:50 +00:00
self . _video = cv2 . VideoCapture ( s [ ' video ' ] [ ' device ' ] )
2022-09-27 16:02:36 +00:00
self . _video . set ( cv2 . CAP_PROP_FPS , iFPS )
self . _video . set ( cv2 . CAP_PROP_FRAME_WIDTH , self . _video_width )
self . _video . set ( cv2 . CAP_PROP_FRAME_HEIGHT , self . _video_height )
# self._video.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
if self . _video is None :
LOG . error ( " start_video_thread " \
+ f " device: { s [ ' video ' ] [ ' device ' ] } " \
+ f " supported: { s [ ' video ' ] [ ' width ' ] } { s [ ' video ' ] [ ' height ' ] } " )
return
LOG . info ( " start_video_thread " \
+ f " device: { s [ ' video ' ] [ ' device ' ] } " \
+ f " supported: { s [ ' video ' ] [ ' width ' ] } { s [ ' video ' ] [ ' height ' ] } " )
2022-10-08 02:46:23 +00:00
2022-09-27 13:51:50 +00:00
self . _video_running = True
self . _video_thread = BaseThread ( target = self . send_video ,
name = ' _video_thread ' )
2022-09-27 12:38:39 +00:00
self . _video_thread . start ( )
def stop_video_thread ( self ) :
if self . _video_thread is None :
return
self . _video_running = False
2022-09-27 13:51:50 +00:00
i = 0
while i < ts . iTHREAD_JOINS :
self . _video_thread . join ( ts . iTHREAD_TIMEOUT )
try :
if not self . _video_thread . is_alive ( ) : break
except :
# AttributeError: 'NoneType' object has no attribute 'join'
break
i = i + 1
else :
LOG . warn ( " self._video_thread.is_alive BLOCKED " )
2022-09-27 12:38:39 +00:00
self . _video_thread = None
self . _video = None
# -----------------------------------------------------------------------------------------------------------------
# Incoming chunks
# -----------------------------------------------------------------------------------------------------------------
def audio_chunk ( self , samples , channels_count , rate ) :
"""
Incoming chunk
"""
if self . _out_stream is None :
2022-10-11 16:36:09 +00:00
# was iOutput = self._settings._args.audio['output']
iOutput = self . _settings [ ' audio ' ] [ ' output ' ]
2022-09-27 13:51:50 +00:00
if not rate in self . lPaSampleratesO :
LOG . warn ( f " { rate } not in { self . lPaSampleratesO !r} " )
if False :
rate = oPYA . get_device_info_by_index ( iOutput ) [ ' defaultSampleRate ' ]
LOG . warn ( f " Setting audio_rate to: { self . lPaSampleratesO [ 0 ] } " )
rate = self . lPaSampleratesO [ 0 ]
try :
with ts . ignoreStderr ( ) :
self . _out_stream = oPYA . open ( format = pyaudio . paInt16 ,
channels = channels_count ,
rate = rate ,
output_device_index = iOutput ,
output = True )
except Exception as e :
2022-09-27 16:02:36 +00:00
LOG . error ( f " Error playing audio_chunk creating self._out_stream { e } " )
invoke_in_main_thread ( util_ui . message_box ,
2022-09-27 13:51:50 +00:00
str ( e ) ,
util_ui . tr ( " Error Chunking audio " ) )
2022-09-27 16:02:36 +00:00
# dunno
self . stop ( )
return
2022-09-27 13:51:50 +00:00
2022-10-11 16:36:09 +00:00
iOutput = self . _settings [ ' audio ' ] [ ' output ' ]
LOG . debug ( f " audio_chunk output_device_index= { iOutput } rate= { rate } channels= { channels_count } " )
2022-09-27 12:38:39 +00:00
self . _out_stream . write ( samples )
# -----------------------------------------------------------------------------------------------------------------
# AV sending
# -----------------------------------------------------------------------------------------------------------------
2022-09-27 13:51:50 +00:00
def send_audio_data ( self , data , count , * largs , * * kwargs ) :
pcm = data
# :param sampling_rate: Audio sampling rate used in this frame.
if self . _toxav is None :
raise RuntimeError ( " _toxav not initialized " )
if self . _audio_rate_tox not in ts . lToxSamplerates :
LOG . warn ( f " ToxAudio sampling rate was { self . _audio_rate_tox } changed to { ts . lToxSamplerates [ 0 ] } " )
self . _audio_rate_tox = ts . lToxSamplerates [ 0 ]
for friend_num in self . _calls :
if self . _calls [ friend_num ] . out_audio :
try :
# app.av.calls ERROR Error send_audio: One of the frame parameters was invalid. E.g. the resolution may be too small or too large, or the audio sampling rate may be unsupported
2022-11-05 01:16:25 +00:00
# app.av.calls ERROR Error send_audio audio_send_frame: This client is currently not in a call with the friend.
2022-09-27 13:51:50 +00:00
self . _toxav . audio_send_frame ( friend_num ,
pcm ,
count ,
self . _audio_channels ,
self . _audio_rate_tox )
except Exception as e :
LOG . error ( f " Error send_audio audio_send_frame: { e } " )
LOG . debug ( f " send_audio self._audio_rate_tox= { self . _audio_rate_tox } self._audio_channels= { self . _audio_channels } " )
2022-11-05 01:16:25 +00:00
# invoke_in_main_thread(util_ui.message_box,
# str(e),
# util_ui.tr("Error send_audio audio_send_frame"))
2022-09-27 13:51:50 +00:00
pass
2022-09-27 12:38:39 +00:00
def send_audio ( self ) :
"""
This method sends audio to friends
"""
2022-09-27 13:51:50 +00:00
i = 0
count = self . _audio_sample_count_tox
LOG . debug ( f " send_audio stream= { self . _audio_stream } " )
2022-09-27 12:38:39 +00:00
while self . _audio_running :
try :
2022-09-27 13:51:50 +00:00
pcm = self . _audio_stream . read ( count , exception_on_overflow = False )
if not pcm :
sleep ( 0.1 )
else :
self . send_audio_data ( pcm , count )
2022-09-27 12:38:39 +00:00
except :
2022-11-05 01:16:25 +00:00
LOG_DEBUG ( f " error send_audio { i } " )
else :
LOG_TRACE ( f " send_audio { i } " )
2022-09-27 13:51:50 +00:00
i + = 1
sleep ( 0.01 )
2022-09-27 12:38:39 +00:00
def send_video ( self ) :
"""
This method sends video to friends
"""
2022-09-27 16:02:36 +00:00
LOG . debug ( f " send_video thread= { threading . current_thread ( ) . name } "
2022-09-27 13:51:50 +00:00
+ f " self._video_running= { self . _video_running } "
+ f " device: { self . _settings [ ' video ' ] [ ' device ' ] } " )
2022-09-27 12:38:39 +00:00
while self . _video_running :
try :
result , frame = self . _video . read ( )
2022-09-27 16:02:36 +00:00
if not result :
LOG . warn ( f " send_video video_send_frame _video.read result= { result } " )
break
if frame is None :
LOG . warn ( f " send_video video_send_frame _video.read result= { result } frame= { frame } " )
continue
2022-09-27 13:51:50 +00:00
else :
2022-11-05 01:16:25 +00:00
LOG_TRACE ( f " send_video video_send_frame _video.read result= { result } " )
2022-09-27 12:38:39 +00:00
height , width , channels = frame . shape
2022-09-27 16:02:36 +00:00
friends = [ ]
2022-09-27 12:38:39 +00:00
for friend_num in self . _calls :
if self . _calls [ friend_num ] . out_video :
2022-09-27 16:02:36 +00:00
friends . append ( friend_num )
if len ( friends ) == 0 :
LOG . warn ( f " send_video video_send_frame no friends " )
else :
2022-11-05 01:16:25 +00:00
LOG_TRACE ( f " send_video video_send_frame { friends } " )
2022-09-27 16:02:36 +00:00
friend_num = friends [ 0 ]
try :
y , u , v = self . convert_bgr_to_yuv ( frame )
self . _toxav . video_send_frame ( friend_num , width , height , y , u , v )
except Exception as e :
LOG . debug ( f " send_video video_send_frame ERROR { e } " )
pass
2022-09-27 13:51:50 +00:00
2022-09-27 16:02:36 +00:00
except Exception as e :
LOG . error ( f " send_video video_send_frame { e } " )
2022-09-27 12:38:39 +00:00
pass
2022-09-27 16:02:36 +00:00
sleep ( 1.0 / iFPS )
2022-09-27 12:38:39 +00:00
def convert_bgr_to_yuv ( self , frame ) :
"""
: param frame : input bgr frame
: return y , u , v : y , u , v values of frame
How this function works :
OpenCV creates YUV420 frame from BGR
This frame has following structure and size :
width , height - dim of input frame
width , height * 1.5 - dim of output frame
width
- - - - - - - - - - - - - - - - - - - - - - - - -
| |
| 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
"""
2022-09-27 13:51:50 +00:00
with ts . ignoreStdout ( ) :
import cv2
2022-09-27 12:38:39 +00:00
frame = cv2 . cvtColor ( frame , cv2 . COLOR_BGR2YUV_I420 )
y = frame [ : self . _video_height , : ]
y = list ( itertools . chain . from_iterable ( y ) )
2022-09-27 13:51:50 +00:00
import numpy as np
2022-09-27 12:38:39 +00:00
u = np . zeros ( ( self . _video_height / / 2 , self . _video_width / / 2 ) , dtype = np . int )
u [ : : 2 , : ] = frame [ self . _video_height : self . _video_height * 5 / / 4 , : self . _video_width / / 2 ]
u [ 1 : : 2 , : ] = frame [ self . _video_height : self . _video_height * 5 / / 4 , self . _video_width / / 2 : ]
u = list ( itertools . chain . from_iterable ( u ) )
v = np . zeros ( ( self . _video_height / / 2 , self . _video_width / / 2 ) , dtype = np . int )
v [ : : 2 , : ] = frame [ self . _video_height * 5 / / 4 : , : self . _video_width / / 2 ]
v [ 1 : : 2 , : ] = frame [ self . _video_height * 5 / / 4 : , self . _video_width / / 2 : ]
v = list ( itertools . chain . from_iterable ( v ) )
return bytes ( y ) , bytes ( u ) , bytes ( v )