Skip to content
This repository has been archived by the owner on Jun 10, 2024. It is now read-only.

how to avoid race condition when using PySurfaceConverter in multiple processes? #554

Open
niujiabenbeng opened this issue Dec 1, 2023 · 1 comment

Comments

@niujiabenbeng
Copy link

Race conditions occur when using PySurfaceConverter in multiple processes. #506 suggests cloning the output surface, which reduces the problem a lot but does not solve it.

As the following code shows, we convert the same surface twice, but the results are not equal when using multiple processing.

#! /usr/bin/env python
# coding: utf-8

# pylint: disable=all

import multiprocessing
import numpy as np
import PyNvCodec as nvc
import PytorchNvCodec as pnvc


class NvColorConverter:
    "Color converter using PySurfaceConverter."

    def __init__(self, width, height, gpuid=0):
        # yapf: disable
        self.width, self.height = width, height
        self.context = nvc.ColorspaceConversionContext(
            nvc.ColorSpace.BT_601, nvc.ColorRange.MPEG)
        self.to_yuv = nvc.PySurfaceConverter(
            width, height, nvc.PixelFormat.NV12,
            nvc.PixelFormat.YUV420, gpuid)
        self.to_rgb = nvc.PySurfaceConverter(
            width, height, nvc.PixelFormat.YUV420,
            nvc.PixelFormat.RGB, gpuid)
        self.to_planar = nvc.PySurfaceConverter(
            width, height, nvc.PixelFormat.RGB,
            nvc.PixelFormat.RGB_PLANAR, gpuid)
        self.downloader = nvc.PySurfaceDownloader(
            width, height, nvc.PixelFormat.RGB_PLANAR, gpuid)
        # yapf: enable

    def convert_color(self, surface):
        surface = self.to_yuv.Execute(surface, self.context)
        surface = self.to_rgb.Execute(surface, self.context)
        surface = self.to_planar.Execute(surface, self.context)
        # We clone the surface as suggested.
        surface = surface.Clone()
        frame = np.ndarray(shape=(0, ), dtype=np.uint8)
        self.downloader.DownloadSingleSurface(surface, frame)
        return frame


# arg is a placeholder
def test_decode_video(arg):
    path, gpuid = "./test.mp4", 0
    dec = nvc.PyNvDecoder(path, gpuid)
    converter = NvColorConverter(dec.Width(), dec.Height(), gpuid)
    for i in range(dec.Numframes()):
        surface = dec.DecodeSingleSurface()
        if surface.Empty(): break
        # We use the same converter to convert the same surface twice,
        # When processes = 1, both arrays have the same value,
        # When processes > 1, two arrays are not equal.
        array1 = converter.convert_color(surface)
        array2 = converter.convert_color(surface)
        if not np.array_equal(array1, array2):
            print("frame not match")


def main():
    with multiprocessing.Pool(processes=8) as pool:
        pool.map(test_decode_video, [None] * 16)


if __name__ == "__main__":
    main()
@RomanArzumanyan
Copy link
Contributor

Hi @niujiabenbeng

Apologies for big delay in response.
Please consider checking out https://github.com/RomanArzumanyan/VALI repository.
It's a VPF spin-off which is actively developed and maintained. It has compatible API and package naming.

Just clone your issue there. Thanks!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants