summaryrefslogtreecommitdiff
path: root/scripts/py/vsi/vsi_video.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/py/vsi/vsi_video.py')
-rw-r--r--scripts/py/vsi/vsi_video.py461
1 files changed, 461 insertions, 0 deletions
diff --git a/scripts/py/vsi/vsi_video.py b/scripts/py/vsi/vsi_video.py
new file mode 100644
index 0000000..88f44fb
--- /dev/null
+++ b/scripts/py/vsi/vsi_video.py
@@ -0,0 +1,461 @@
+#!/usr/bin/env python3
+# SPDX-FileCopyrightText: Copyright 2024 Arm Limited and/or its affiliates <open-source-office@arm.com>
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import time
+import atexit
+import logging
+import subprocess
+from multiprocessing.connection import Client, Connection
+from os import path, getcwd
+from os import name as os_name
+
+
+class VideoClient:
+ def __init__(self):
+ # Server commands
+ self.SET_FILENAME = 1
+ self.STREAM_CONFIGURE = 2
+ self.STREAM_ENABLE = 3
+ self.STREAM_DISABLE = 4
+ self.FRAME_READ = 5
+ self.FRAME_WRITE = 6
+ self.CLOSE_SERVER = 7
+ # Color space
+ self.GRAYSCALE8 = 1
+ self.RGB888 = 2
+ self.BGR565 = 3
+ self.YUV420 = 4
+ self.NV12 = 5
+ self.NV21 = 6
+ # Variables
+ self.conn = None
+
+ def connectToServer(self, address, authkey):
+ for _ in range(50):
+ try:
+ self.conn = Client(address, authkey=authkey.encode('utf-8'))
+ if isinstance(self.conn, Connection):
+ break
+ else:
+ self.conn = None
+ except Exception:
+ self.conn = None
+ time.sleep(0.01)
+
+ def setFilename(self, filename, mode):
+ self.conn.send([self.SET_FILENAME, getcwd(), filename, mode])
+ filename_valid = self.conn.recv()
+
+ return filename_valid
+
+ def configureStream(self, frame_width, frame_height, color_format, frame_rate):
+ self.conn.send([self.STREAM_CONFIGURE, frame_width, frame_height, color_format, frame_rate])
+ configuration_valid = self.conn.recv()
+
+ return configuration_valid
+
+ def enableStream(self, mode):
+ self.conn.send([self.STREAM_ENABLE, mode])
+ stream_active = self.conn.recv()
+
+ return stream_active
+
+ def disableStream(self):
+ self.conn.send([self.STREAM_DISABLE])
+ stream_active = self.conn.recv()
+
+ return stream_active
+
+ def readFrame(self):
+ self.conn.send([self.FRAME_READ])
+ data = self.conn.recv_bytes()
+ eos = self.conn.recv()
+
+ return data, eos
+
+ def writeFrame(self, data):
+ self.conn.send([self.FRAME_WRITE])
+ self.conn.send_bytes(data)
+
+ def closeServer(self):
+ try:
+ if isinstance(self.conn, Connection):
+ self.conn.send([self.CLOSE_SERVER])
+ self.conn.close()
+ except Exception as e:
+ logging.error(f'Exception occurred on cleanup: {e}')
+
+
+# User registers
+REG_IDX_MAX = 12 # Maximum user register index used in VSI
+MODE = 0 # Regs[0] // Mode: 0=Input, 1=Output
+CONTROL = 0 # Regs[1] // Control: enable, flush
+STATUS = 0 # Regs[2] // Status: active, buf_empty, buf_full, overflow, underflow, eos
+FILENAME_LEN = 0 # Regs[3] // Filename length
+FILENAME_CHAR = 0 # Regs[4] // Filename character
+FILENAME_VALID = 0 # Regs[5] // Filename valid flag
+FRAME_WIDTH = 300 # Regs[6] // Requested frame width
+FRAME_HEIGHT = 300 # Regs[7] // Requested frame height
+COLOR_FORMAT = 0 # Regs[8] // Color format
+FRAME_RATE = 0 # Regs[9] // Frame rate
+FRAME_INDEX = 0 # Regs[10] // Frame index
+FRAME_COUNT = 0 # Regs[11] // Frame count
+FRAME_COUNT_MAX = 0 # Regs[12] // Frame count maximum
+
+# MODE register definitions
+MODE_IO_Msk = 1<<0
+MODE_Input = 0<<0
+MODE_Output = 1<<0
+
+# CONTROL register definitions
+CONTROL_ENABLE_Msk = 1<<0
+CONTROL_CONTINUOS_Msk = 1<<1
+CONTROL_BUF_FLUSH_Msk = 1<<2
+
+# STATUS register definitions
+STATUS_ACTIVE_Msk = 1<<0
+STATUS_BUF_EMPTY_Msk = 1<<1
+STATUS_BUF_FULL_Msk = 1<<2
+STATUS_OVERFLOW_Msk = 1<<3
+STATUS_UNDERFLOW_Msk = 1<<4
+STATUS_EOS_Msk = 1<<5
+
+# IRQ Status register definitions
+IRQ_Status_FRAME_Msk = 1<<0
+IRQ_Status_OVERFLOW_Msk = 1<<1
+IRQ_Status_UNDERFLOW_Msk = 1<<2
+IRQ_Status_EOS_Msk = 1<<3
+
+# Variables
+Video = VideoClient()
+Filename = ""
+FilenameIdx = 0
+
+
+# Close VSI Video Server on exit
+def cleanup():
+ Video.closeServer()
+
+
+# Client connection to VSI Video Server
+def init(address, authkey):
+ global FILENAME_VALID
+
+ base_dir = path.dirname(__file__)
+ server_path = path.join(base_dir, 'vsi_video_server.py')
+
+ logging.info("Start video server")
+ if path.isfile(server_path):
+ # Start Video Server
+ if os_name == 'nt':
+ py_cmd = 'python'
+ else:
+ py_cmd = 'python3'
+ cmd = f"{py_cmd} {server_path} " \
+ f"--ip {address[0]} " \
+ f"--port {address[1]} " \
+ f"--authkey {authkey}"
+ subprocess.Popen(cmd, shell=True)
+ # Connect to Video Server
+ Video.connectToServer(address, authkey)
+ if Video.conn == None:
+ logging.error("Server not connected")
+
+ else:
+ logging.error(f"Server script not found: {server_path}")
+
+ # Register clean-up function
+ atexit.register(cleanup)
+
+
+## Flush Stream buffer
+def flushBuffer():
+ global STATUS, FRAME_INDEX, FRAME_COUNT
+
+ STATUS |= STATUS_BUF_EMPTY_Msk
+ STATUS &= ~STATUS_BUF_FULL_Msk
+
+ FRAME_INDEX = 0
+ FRAME_COUNT = 0
+
+
+## VSI IRQ Status register
+# @param IRQ_Status IRQ status register to update
+# @param value status bits to clear
+# @return IRQ_Status return updated register
+def wrIRQ(IRQ_Status, value):
+ IRQ_Status_Clear = IRQ_Status & ~value
+ IRQ_Status &= ~IRQ_Status_Clear
+
+ return IRQ_Status
+
+
+## Timer Event
+# @param IRQ_Status IRQ status register to update
+# @return IRQ_Status return updated register
+def timerEvent(IRQ_Status):
+
+ IRQ_Status |= IRQ_Status_FRAME_Msk
+
+ if (STATUS & STATUS_OVERFLOW_Msk) != 0:
+ IRQ_Status |= IRQ_Status_OVERFLOW_Msk
+
+ if (STATUS & STATUS_UNDERFLOW_Msk) != 0:
+ IRQ_Status |= IRQ_Status_UNDERFLOW_Msk
+
+ if (STATUS & STATUS_EOS_Msk) != 0:
+ IRQ_Status |= IRQ_Status_EOS_Msk
+
+ if (CONTROL & CONTROL_CONTINUOS_Msk) == 0:
+ wrCONTROL(CONTROL & ~(CONTROL_ENABLE_Msk | CONTROL_CONTINUOS_Msk))
+
+ return IRQ_Status
+
+
+## Read data from peripheral for DMA P2M transfer (VSI DMA)
+# @param size size of data to read (in bytes, multiple of 4)
+# @return data data read (bytearray)
+def rdDataDMA(size):
+ global STATUS, FRAME_COUNT
+
+ if (STATUS & STATUS_ACTIVE_Msk) != 0:
+
+ if Video.conn != None:
+ data, eos = Video.readFrame()
+ if eos:
+ STATUS |= STATUS_EOS_Msk
+ if FRAME_COUNT < FRAME_COUNT_MAX:
+ FRAME_COUNT += 1
+ else:
+ STATUS |= STATUS_OVERFLOW_Msk
+ if FRAME_COUNT == FRAME_COUNT_MAX:
+ STATUS |= STATUS_BUF_FULL_Msk
+ STATUS &= ~STATUS_BUF_EMPTY_Msk
+ else:
+ data = bytearray()
+
+ else:
+ data = bytearray()
+
+ return data
+
+
+## Write data to peripheral for DMA M2P transfer (VSI DMA)
+# @param data data to write (bytearray)
+# @param size size of data to write (in bytes, multiple of 4)
+def wrDataDMA(data, size):
+ global STATUS, FRAME_COUNT
+
+ if (STATUS & STATUS_ACTIVE_Msk) != 0:
+
+ if Video.conn != None:
+ Video.writeFrame(data)
+ if FRAME_COUNT > 0:
+ FRAME_COUNT -= 1
+ else:
+ STATUS |= STATUS_UNDERFLOW_Msk
+ if FRAME_COUNT == 0:
+ STATUS |= STATUS_BUF_EMPTY_Msk
+ STATUS &= ~STATUS_BUF_FULL_Msk
+
+
+## Write CONTROL register (user register)
+# @param value value to write (32-bit)
+def wrCONTROL(value):
+ global CONTROL, STATUS
+
+ if ((value ^ CONTROL) & CONTROL_ENABLE_Msk) != 0:
+ STATUS &= ~STATUS_ACTIVE_Msk
+ if (value & CONTROL_ENABLE_Msk) != 0:
+ logging.info("Start video stream")
+ if Video.conn != None:
+ logging.info("Configure video stream")
+ configuration_valid = Video.configureStream(FRAME_WIDTH, FRAME_HEIGHT, COLOR_FORMAT, FRAME_RATE)
+ if configuration_valid:
+ logging.info("Enable video stream")
+ server_active = Video.enableStream(MODE)
+ if server_active:
+ STATUS |= STATUS_ACTIVE_Msk
+ STATUS &= ~(STATUS_OVERFLOW_Msk | STATUS_UNDERFLOW_Msk | STATUS_EOS_Msk)
+ else:
+ logging.error("Enable video stream failed")
+ else:
+ logging.error("Configure video stream failed")
+ else:
+ logging.error("Server not connected")
+ else:
+ logging.info("Stop video stream")
+ if Video.conn != None:
+ logging.info("Disable video stream")
+ Video.disableStream()
+ else:
+ logging.error("Server not connected")
+
+ if (value & CONTROL_BUF_FLUSH_Msk) != 0:
+ value &= ~CONTROL_BUF_FLUSH_Msk
+ flushBuffer()
+
+ CONTROL = value
+
+
+## Read STATUS register (user register)
+# @return status current STATUS User register (32-bit)
+def rdSTATUS():
+ global STATUS
+
+ status = STATUS
+ STATUS &= ~(STATUS_OVERFLOW_Msk | STATUS_UNDERFLOW_Msk | STATUS_EOS_Msk)
+
+ return status
+
+
+## Write FILENAME_LEN register (user register)
+# @param value value to write (32-bit)
+def wrFILENAME_LEN(value):
+ global STATUS, FILENAME_LEN, FILENAME_VALID, Filename, FilenameIdx
+
+ logging.info("Set new source name length and reset filename and valid flag")
+ FilenameIdx = 0
+ Filename = ""
+ FILENAME_VALID = 0
+ FILENAME_LEN = value
+
+
+## Write FILENAME_CHAR register (user register)
+# @param value value to write (32-bit)
+def wrFILENAME_CHAR(value):
+ global FILENAME_VALID, Filename, FilenameIdx
+
+ if FilenameIdx < FILENAME_LEN:
+ logging.info(f"Append {value} to filename")
+ Filename += f"{value}"
+ FilenameIdx += 1
+ logging.debug(f"Received {FilenameIdx} of {FILENAME_LEN} characters")
+
+ if FilenameIdx == FILENAME_LEN:
+ logging.info("Check if file exists on Server side and set VALID flag")
+ logging.debug(f"Filename: {Filename}")
+
+ if Video.conn != None:
+ FILENAME_VALID = Video.setFilename(Filename, MODE)
+ else:
+ logging.error("Server not connected")
+
+ logging.debug(f"Filename VALID: {FILENAME_VALID}")
+
+
+## Write FRAME_INDEX register (user register)
+# @param value value to write (32-bit)
+# @return value value written (32-bit)
+def wrFRAME_INDEX(value):
+ global STATUS, FRAME_INDEX, FRAME_COUNT
+
+ FRAME_INDEX += 1
+ if FRAME_INDEX == FRAME_COUNT_MAX:
+ FRAME_INDEX = 0
+
+ if (MODE & MODE_IO_Msk) == MODE_Input:
+ # Input
+ if FRAME_COUNT > 0:
+ FRAME_COUNT -= 1
+ if FRAME_COUNT == 0:
+ STATUS |= STATUS_BUF_EMPTY_Msk
+ STATUS &= ~STATUS_BUF_FULL_Msk
+ else:
+ # Output
+ if FRAME_COUNT < FRAME_COUNT_MAX:
+ FRAME_COUNT += 1
+ if FRAME_COUNT == FRAME_COUNT_MAX:
+ STATUS |= STATUS_BUF_FULL_Msk
+ STATUS &= ~STATUS_BUF_EMPTY_Msk
+
+ return FRAME_INDEX
+
+
+## Read user registers (the VSI User Registers)
+# @param index user register index (zero based)
+# @return value value read (32-bit)
+def rdRegs(index):
+ value = 0
+
+ if index == 0:
+ value = MODE
+ elif index == 1:
+ value = CONTROL
+ elif index == 2:
+ value = rdSTATUS()
+ elif index == 3:
+ value = FILENAME_LEN
+ elif index == 4:
+ value = FILENAME_CHAR
+ elif index == 5:
+ value = FILENAME_VALID
+ elif index == 6:
+ value = FRAME_WIDTH
+ elif index == 7:
+ value = FRAME_HEIGHT
+ elif index == 8:
+ value = COLOR_FORMAT
+ elif index == 9:
+ value = FRAME_RATE
+ elif index == 10:
+ value = FRAME_INDEX
+ elif index == 11:
+ value = FRAME_COUNT
+ elif index == 12:
+ value = FRAME_COUNT_MAX
+
+ return value
+
+
+## Write user registers (the VSI User Registers)
+# @param index user register index (zero based)
+# @param value value to write (32-bit)
+# @return value value written (32-bit)
+def wrRegs(index, value):
+ global MODE, FRAME_WIDTH, FRAME_HEIGHT, COLOR_FORMAT, FRAME_RATE, FRAME_COUNT_MAX
+
+ if index == 0:
+ MODE = value
+ elif index == 1:
+ wrCONTROL(value)
+ elif index == 2:
+ value = STATUS
+ elif index == 3:
+ wrFILENAME_LEN(value)
+ elif index == 4:
+ wrFILENAME_CHAR(chr(value))
+ elif index == 5:
+ value = FILENAME_VALID
+ elif index == 6:
+ if value != 0:
+ FRAME_WIDTH = value
+ elif index == 7:
+ if value != 0:
+ FRAME_HEIGHT = value
+ elif index == 8:
+ COLOR_FORMAT = value
+ elif index == 9:
+ FRAME_RATE = value
+ elif index == 10:
+ value = wrFRAME_INDEX(value)
+ elif index == 11:
+ value = FRAME_COUNT
+ elif index == 12:
+ FRAME_COUNT_MAX = value
+ flushBuffer()
+
+ return value