-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Performance and Color Issues with PopH264 Decoder in Unity - Windows+Hololens2 #83
Comments
Have you tried decoding the same stream on windows or mac or any other platform? Is the performance similar? Any chance you have a dump of the stream? |
No not yet with your decoder. I only used ffmpeg in other envs/systems for decoding besides Unity with the custom implementation. Here is a dump of ~13 secs save in a .bytes file. |
Oh as for colour; the decoder is probably outputting 2 or 3 planes (the output is almost always yuv) you're just using the luma/greyscale plane. Im aware the new simple demo doesnt do conversion to rgb yet |
Thank you very much for the information! Understood. Is everything so far good with the dump from your site? Perhaps did we something wrong during the implementation besides the greyscale colourspace which is bad in terms of latency and artifacts? Thank you very, very much in advance and of course for the already listed tips! |
It doesn't look too bad at a glance, but you're only checking for frames when you push data in....(and you're throwing away small buffers? why?) The library is more asynchronous than that. Push data whenever you have it, and check for new frames I won't have time to look at this for a while probably, but you could follow through this conversation #80 to get an idea of how to test what happens with your data; it's quite easy to test your data with the integration tests in the c++ app (which might also work on hololens, but an easy first step is to just try on desktop 0452ffb ) |
You're still not looking for new frames in quite the right way (I don't know how often you're getting data in, so not sure if this is the source of any problems... This is what I wrote before
You're still only checking for frames when you push, instead of checking for new frames all the time |
You don't really need any of this... you're doing...
Just do (although this still isn't handling 3 planes, but in this case on this device it's okay)
|
Indeed, thank you very much for pointing that out again. |
Does it run in the test app? #83 (comment) Unless you run from the debugger/visual studio and are seeing a lot of error/debug messages in the output. (If you are, attach them here!) For Hardware... it depends on MF providing a hardware decoder, what device are you decoding with? |
Unity-built-app |
Okay, that's the software one, what GPU/H264 decoders do you have? (some nvidia cards don't provide a hardware decoder to mediafoundation) |
I ran the build .sln from unity directly in VS2022 to have an attached debugger while runtime on the HL2. The only message I get in regard in PopH264 is this one:
Nothing else which could pinpoint the problem why it is not showing/executing anything. On the Win11 machine I have a NVIDIA GeForce RTX 4080 Laptop GPU. The encoding process is being executed with this script: #============================================================
# import packages
#============================================================
from grpc_prebuilt_files import streamerbackend_pb2_grpc as sbb_pb2_grpc, streamerbackend_pb2 as sbb_pb2
from grpc_prebuilt_files import stream_pb2_grpc, stream_pb2
from utils.detect_stream_sources import getAllCams
from utils.timestamp import get_current_time_with_milliseconds
from logging.handlers import RotatingFileHandler
import subprocess as sp
import threading
import logging
import signal
import grpc
import uuid
import time
import sys
import os
#============================================================
# class
#============================================================
#============================================================
# propertys
#============================================================
# logging
log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s')
logFile = '/home/apoqlar/logs/gRPC_stream.log'
my_handler = RotatingFileHandler(logFile, mode='a', maxBytes=5*1024*1024,
backupCount=2, encoding=None, delay=0)
my_handler.setFormatter(log_formatter)
my_handler.setLevel(logging.INFO)
app_log = logging.getLogger('root')
app_log.setLevel(logging.INFO)
app_log.addHandler(my_handler)
# sys.stdout.write = app_log.info
# sys.stderr.write = app_log.error
# end config logging
CHANGE_DEVICE = False
SECOND_SIZE = 0
TIC_REFERENCE = 0
STREAMING_STATUS = None
GUID = str(uuid.uuid4()) # generate uuid for angular app
IMG_W = 1280
IMG_H = 720
time.sleep(2) # let jetson to boot
# automatic detection if USB video source is connected
cams = getAllCams()
print(f"allCams: {cams}")
# get USB device link
# video_source = '/home/apoqlar/Downloads/output_long.mp4'
""""""
video_source = ''
if (len(cams["links"]) > 1):
for i, cam in enumerate(cams['name']):
if 'usb' in (cams['bus'][i]):
video_source = cams['links'][i]
print(f"USB cam: {cam, i} and adress: {video_source}")
CHANGE_DEVICE = True
else:
for i, cam in enumerate(cams['name']):
if 'PCI' in (cams['bus'][i]):
video_source = cams['links'][i]
print(f"PCI cam: {cam, i} and adress: {video_source}")
CHANGE_DEVICE = False
""""""
# video_source = '/dev/video1'
FFMPEG_BIN = "/home/apoqlar/apoqlar/libs/ffmpeg_6_0/ffmpeg"
# FFMPEG_BIN = "ffmpeg"
ffmpeg_cmd = [ FFMPEG_BIN,
'-i', video_source,
'-r', '30',
'-pix_fmt', 'yuv420p',
'-c:v', 'h264_nvmpi',
# '-c:v', 'hevc_nvmpi',
'-force_key_frames',
'expr:gte(t,n_forced*0.5)',
'-preset', 'ultrafast',
'-threads', '16',
'-vf', 'scale=1920:1080', # 1280x720
# '-loglevel', 'error',
'-an','-sn', # disable audio processing
'-f', 'image2pipe', '-']
pipe = sp.Popen(ffmpeg_cmd, stdout = sp.PIPE, bufsize=10)
time.sleep(2) # let ffmpeg to warm up
#============================================================
# functions
#============================================================
def detect_devices():
"""detecting video devices on host system"""
global video_source, CHANGE_DEVICE, ffmpeg_cmd, pipe
cams = getAllCams()
print(f"detected cams: {cams}")
# if (len(cams) > 1) and (CHANGE_DEVICE is False):
if (len(cams["links"]) > 1) and (CHANGE_DEVICE is False):
pipe.kill()
os.kill(os.getpid(),signal.SIGKILL)
video_source = ''
for i, cam in enumerate(cams['name']):
if 'usb' in (cams['bus'][i]):
video_source = cams['links'][i]
print(f"USB cam: {cam, i} and adress: {video_source}")
CHANGE_DEVICE = True
# video_source = '/dev/video1'
pipe.stdout.flush()
ffmpeg_cmd = ffmpeg_cmd
pipe = sp.Popen(ffmpeg_cmd, stdout = sp.PIPE, bufsize=10)
time.sleep(2) # let ffmpeg to warm up
CHANGE_DEVICE = True
# elif (len(cams) <= 1) and (CHANGE_DEVICE is True):
elif (len(cams["links"]) <= 1) and (CHANGE_DEVICE is True):
pipe.kill()
os.kill(os.getpid(),signal.SIGKILL)
# TODO: remove the for loop due to len <= 1
video_source = ''
for i, cam in enumerate(cams['name']):
if 'PCI' in (cams['bus'][i]):
video_source = cams['links'][i]
print(f"PCI cam: {cam, i} and adress: {video_source}")
CHANGE_DEVICE = False
# video_source = '/dev/video0'
pipe.stdout.flush()
ffmpeg_cmd = ffmpeg_cmd
pipe = sp.Popen(ffmpeg_cmd, stdout = sp.PIPE, bufsize=10)
time.sleep(2) # let ffmpeg to warm up
CHANGE_DEVICE = False
def stream():
global SECOND_SIZE, TIC_REFERENCE, STREAMING_STATUS, GUID, REC
while True:
tic = time.perf_counter()
h265_buffer = pipe.stdout.read(20000) # 1280 * 720 * 3
message_metadata = []
meta_object = stream_pb2.VideoData.Metadata(type = '.JPEG', width = 1280, height = 720, fps = 30.0)
message_metadata.append(meta_object)
# data = stream_pb2.VideoData(buffer = h265_buffer, id = GUID, meta = iter(message_metadata))
# timestamp integration; TODO: add extra field in stream.proto
t_stamp = get_current_time_with_milliseconds()
data = stream_pb2.VideoData(buffer = h265_buffer, id = t_stamp, meta = iter(message_metadata))
# print("Sending data")
yield data
# calculate B/s
old_tic = int(tic)
try:
SECOND_SIZE = SECOND_SIZE + sys.getsizeof(h265_buffer)
except Exception as e:
print(f"error: {e}")
app_log.error(f"error: {e}")
# SECOND_SIZE = SECOND_SIZE + sys.getsizeof(buf.tobytes())
if old_tic > TIC_REFERENCE:
print(f"size of one second: {SECOND_SIZE} bytes")
app_log.info(f"size of one second: {SECOND_SIZE} bytes")
TIC_REFERENCE = old_tic
SECOND_SIZE = 0
""""""
if (TIC_REFERENCE % 10) == 0: # 10 seconds to wait/check
app_log.info(f"looking for other video ressources")
t_detect = threading.Thread(target=detect_devices)
t_detect.daemon = True
t_detect.start()
""""""
def stream_info():
# global STREAMING_STATUS
data = sbb_pb2.StreamingStatusInfo(streaming = "yes", nclients = "2")
# STREAMING_STATUS = data.order
return data
def run():
global STREAMING_STATUS
channel = grpc.insecure_channel('127.0.0.1:50052')
backend_channel = grpc.insecure_channel('127.0.0.1:11952')
stub = stream_pb2_grpc.StreamerServiceStub(channel)
backend_stub = sbb_pb2_grpc.StreamerBackendServerStub(backend_channel)
print('SenderClient start')
app_log.info(f"SenderClient start")
try:
responses = stub.LiveStreamVideo(stream())
print("1")
for res in responses:
backend_responses = backend_stub.GetStreamingStatus(stream_info()) # to check every time the status and not once
STREAMING_STATUS = backend_responses.order
# print(res)
if res is None:
run()
continue
except grpc.RpcError as e:
print("ERROR: ")
print(e.details())
app_log.error(f"error: {e.details()}")
run() # if python backend server is down
#============================================================
# Awake
#============================================================
#============================================================
# main
#============================================================
if __name__ == '__main__':
run()
#============================================================
# after the App exit
#============================================================
# nothing to do here May I provide you more information? |
Try setting a profile & Level for h264 encoding to see if it makes any difference |
Unfortunately it did not changed anything. |
Okay, well, when i get some free time, I'll take your stream dump and run it through the test app on a windows machine and see what it does. as per #83 (comment) I don't have a hololens2 to hand any more (It went missing when I lent it to another company), so I cannot try it on hardware, but running the UWP test app on windows does behave differently to the proper win32 version |
If I can help you with anything, please let me know. |
Yes, as I said a few times, just modify the test app! |
Understood. Already in the making and will let you know. |
Environment
Unity Version: 2021.3.8f1
Platform: Win 11, HoloLens 2
PopH264 Version: prebuilt unitypackage 1.9
Description
We have integrated the PopH264 decoder into our Unity project to decode and display H264 buffers received from a gRPC server. The setup involves decoding video frames that are 20kB per buffer, which may contain one or more frames.
We've encountered several issues:
We have confirmed that the incoming H264 data is correctly encoded, as it has been successfully decoded using an alternative custom decoder based on DirectX. This suggests the issue lies within the PopH264 decoder or our implementation of the usage.
In Unity, we've added a GameObject with a MeshRenderer that uses a custom unlit shader to display the decoded texture. The H264Decoder script attached to this GameObject is responsible for decoding the frames and updating the texture.
the decoder integration which is attached to the plane gameobject:
`
using UnityEngine;
using System.Collections.Generic;
public class H264Decoder : MonoBehaviour
{
// Decoder instance
private PopH264.Decoder decoder;
}
`
the receiving of the buffers from gRPC:
`
private async UniTask ChangeFrameLoop(int frameNumber)
{
if (IsCancellationRequested)
return;
}
`
the shader:
`
Shader"Grayscale"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
Subshader
{
Pass
{
Cull off
CGPROGRAM
#pragma vertex vertex_shader
#pragma fragment pixel_shader
#pragma target 2.0
sampler2D _MainTex;
struct custom_type
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
};
custom_type vertex_shader(float4 vertex : POSITION, float2 uv : TEXCOORD0)
{
custom_type vs;
vs.vertex = UnityObjectToClipPos(vertex);
vs.uv = uv;
return vs;
}
float4 pixel_shader(custom_type ps) : COLOR
{
float3 color = tex2D(_MainTex, ps.uv.xy).xyz;
float grayscale = dot(color, float3(0.2126, 0.7152, 0.0722));
return float4(grayscale, grayscale, grayscale, 1.0);
}
ENDCG
}
}
}
`
Questions
Steps to Reproduce
Additional Context
We've tried various settings for the decoder parameters, such as disabling buffering, double-decoding keyframes, and verbose debugging.
The Unity shader is set up to convert from greyscale to RGB, assuming the issue might be related to color space conversion. The planes[0] array is already returning grayscale without specifiying the color space in the decoder setup.
Attached is the H264Decoder script and the cleaned-up snippet from our gRPC frame handling logic.
We would appreciate any insights or guidance you can provide to help resolve these issues.
The text was updated successfully, but these errors were encountered: