diff --git a/tools/visual-pipeline-and-platform-evaluation-tool/docs/user-guide/_assets/vippet.json b/tools/visual-pipeline-and-platform-evaluation-tool/docs/user-guide/_assets/vippet.json index 66abb98a50..a04789c2c3 100644 --- a/tools/visual-pipeline-and-platform-evaluation-tool/docs/user-guide/_assets/vippet.json +++ b/tools/visual-pipeline-and-platform-evaluation-tool/docs/user-guide/_assets/vippet.json @@ -1305,13 +1305,13 @@ "tests" ], "summary": "Run Performance Test", - "description": "Start an asynchronous performance test job.\n\nOperation:\n * Validate the performance test request.\n * Create a PerformanceJob with RUNNING state.\n * Spawn a background thread that runs the pipelines using\n a GStreamer-based runner.\n * Return the job identifier so the caller can poll status endpoints.\n\nRequest body:\n body: PerformanceTestSpec\n * pipeline_performance_specs \u2013 list of pipelines and number of\n streams per pipeline.\n * video_output \u2013 configuration for optional encoded video output\n (enabled flag and encoder_device).\n\nReturns:\n 202 Accepted:\n TestJobResponse with job_id of the created performance job.\n 400 Bad Request:\n MessageResponse if the request is invalid at manager level, for\n example:\n * all stream counts are zero,\n * pipeline ids do not exist (if validated up front in future).\n 500 Internal Server Error:\n MessageResponse if an unexpected error occurs when creating the\n job or starting the background thread.\n\nSuccess conditions:\n * At least one stream is requested across all pipelines.\n * TestsManager.test_performance() successfully enqueues the job.\n\nFailure conditions (high level):\n * Validation or configuration error inside TestsManager \u2192 400.\n * Any unhandled exception in job creation \u2192 500.\n\nRequest example:\n .. code-block:: json\n\n {\n \"pipeline_performance_specs\": [\n {\"id\": \"pipeline-a3f5d9e1\", \"streams\": 8},\n {\"id\": \"pipeline-b7c2e114\", \"streams\": 4}\n ],\n \"video_output\": {\n \"enabled\": false,\n \"encoder_device\": {\"device_name\": \"GPU\", \"gpu_id\": 0}\n }\n }\n\nSuccessful response example (202):\n .. code-block:: json\n\n {\n \"job_id\": \"job123\"\n }\n\nError response example (400, invalid request):\n .. code-block:: json\n\n {\n \"message\": \"At least one stream must be specified to run the pipeline.\"\n }", + "description": "Start an asynchronous performance test job.\n\nOperation:\n * Validate the performance test request.\n * Create a PerformanceJob with RUNNING state.\n * Spawn a background thread that runs the pipelines using\n a GStreamer-based runner.\n * Return the job identifier so the caller can poll status endpoints.\n\nRequest body:\n body: PerformanceTestSpec\n * pipeline_performance_specs \u2013 list of pipelines and number of\n streams per pipeline.\n * video_output \u2013 configuration for optional encoded video output\n (enabled flag).\n\nReturns:\n 202 Accepted:\n TestJobResponse with job_id of the created performance job.\n 400 Bad Request:\n MessageResponse if the request is invalid at manager level, for\n example:\n * all stream counts are zero,\n * pipeline ids do not exist (if validated up front in future).\n 500 Internal Server Error:\n MessageResponse if an unexpected error occurs when creating the\n job or starting the background thread.\n\nSuccess conditions:\n * At least one stream is requested across all pipelines.\n * TestsManager.test_performance() successfully enqueues the job.\n\nFailure conditions (high level):\n * Validation or configuration error inside TestsManager \u2192 400.\n * Any unhandled exception in job creation \u2192 500.\n\nRequest example:\n .. code-block:: json\n\n {\n \"pipeline_performance_specs\": [\n {\"id\": \"pipeline-a3f5d9e1\", \"streams\": 8},\n {\"id\": \"pipeline-b7c2e114\", \"streams\": 4}\n ],\n \"video_output\": {\n \"enabled\": false\n }\n }\n\nSuccessful response example (202):\n .. code-block:: json\n\n {\n \"job_id\": \"job123\"\n }\n\nError response example (400, invalid request):\n .. code-block:: json\n\n {\n \"message\": \"At least one stream must be specified to run the pipeline.\"\n }", "operationId": "run_performance_test", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PerformanceTestSpec-Input" + "$ref": "#/components/schemas/PerformanceTestSpec" } } }, @@ -1367,13 +1367,13 @@ "tests" ], "summary": "Run Density Test", - "description": "Start an asynchronous density test job.\n\nOperation:\n * Validate the density test request.\n * Use requested fps_floor and per\u2011pipeline stream_rate ratios.\n * Create a DensityJob with RUNNING state.\n * Spawn a background thread that runs a Benchmark to determine the\n maximum number of streams that still meets fps_floor.\n * Return the job identifier so the caller can poll status endpoints.\n\nRequest body:\n body: DensityTestSpec\n * fps_floor \u2013 minimum acceptable FPS per stream.\n * pipeline_density_specs \u2013 list of pipelines with stream_rate\n percentages that must sum to 100.\n * video_output \u2013 configuration for optional encoded video output.\n\nReturns:\n 202 Accepted:\n TestJobResponse with job_id of the created density job.\n 400 Bad Request:\n MessageResponse when:\n * pipeline_density_specs.stream_rate values do not sum to 100,\n * other validation errors raised by Benchmark or TestsManager.\n 500 Internal Server Error:\n MessageResponse for unexpected errors when creating or starting\n the job.\n\nSuccess conditions:\n * stream_rate ratios sum to 100%.\n * DensityTestSpec is valid and Benchmark.run() can be started in a\n background thread.\n\nFailure conditions:\n * Validation errors in Benchmark._calculate_streams_per_pipeline() or\n TestsManager.test_density() \u2192 400.\n * Any other unhandled exception \u2192 500.\n\nRequest example:\n .. code-block:: json\n\n {\n \"fps_floor\": 30,\n \"pipeline_density_specs\": [\n {\"id\": \"pipeline-a3f5d9e1\", \"stream_rate\": 50},\n {\"id\": \"pipeline-b7c2e114\", \"stream_rate\": 50}\n ],\n \"video_output\": {\n \"enabled\": false,\n \"encoder_device\": {\"device_name\": \"GPU\", \"gpu_id\": 0}\n }\n }\n\nSuccessful response example (202):\n .. code-block:: json\n\n {\n \"job_id\": \"job456\"\n }\n\nError response example (400, bad ratios):\n .. code-block:: json\n\n {\n \"message\": \"Pipeline stream_rate ratios must sum to 100%, got 110%\"\n }", + "description": "Start an asynchronous density test job.\n\nOperation:\n * Validate the density test request.\n * Use requested fps_floor and per\u2011pipeline stream_rate ratios.\n * Create a DensityJob with RUNNING state.\n * Spawn a background thread that runs a Benchmark to determine the\n maximum number of streams that still meets fps_floor.\n * Return the job identifier so the caller can poll status endpoints.\n\nRequest body:\n body: DensityTestSpec\n * fps_floor \u2013 minimum acceptable FPS per stream.\n * pipeline_density_specs \u2013 list of pipelines with stream_rate\n percentages that must sum to 100.\n * video_output \u2013 configuration for optional encoded video output.\n\nReturns:\n 202 Accepted:\n TestJobResponse with job_id of the created density job.\n 400 Bad Request:\n MessageResponse when:\n * pipeline_density_specs.stream_rate values do not sum to 100,\n * other validation errors raised by Benchmark or TestsManager.\n 500 Internal Server Error:\n MessageResponse for unexpected errors when creating or starting\n the job.\n\nSuccess conditions:\n * stream_rate ratios sum to 100%.\n * DensityTestSpec is valid and Benchmark.run() can be started in a\n background thread.\n\nFailure conditions:\n * Validation errors in Benchmark._calculate_streams_per_pipeline() or\n TestsManager.test_density() \u2192 400.\n * Any other unhandled exception \u2192 500.\n\nRequest example:\n .. code-block:: json\n\n {\n \"fps_floor\": 30,\n \"pipeline_density_specs\": [\n {\"id\": \"pipeline-a3f5d9e1\", \"stream_rate\": 50},\n {\"id\": \"pipeline-b7c2e114\", \"stream_rate\": 50}\n ],\n \"video_output\": {\n \"enabled\": false\n }\n }\n\nSuccessful response example (202):\n .. code-block:: json\n\n {\n \"job_id\": \"job456\"\n }\n\nError response example (400, bad ratios):\n .. code-block:: json\n\n {\n \"message\": \"Pipeline stream_rate ratios must sum to 100%, got 110%\"\n }", "operationId": "run_density_test", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DensityTestSpec-Input" + "$ref": "#/components/schemas/DensityTestSpec" } } }, @@ -1568,7 +1568,7 @@ "title": "Id" }, "request": { - "$ref": "#/components/schemas/DensityTestSpec-Output" + "$ref": "#/components/schemas/DensityTestSpec" } }, "type": "object", @@ -1579,7 +1579,7 @@ "title": "DensityJobSummary", "description": "Short summary for a density test job.\n\nAttributes:\n id: Job identifier.\n request: Original DensityTestSpec used to start the job." }, - "DensityTestSpec-Input": { + "DensityTestSpec": { "properties": { "fps_floor": { "type": "integer", @@ -1614,79 +1614,11 @@ "$ref": "#/components/schemas/VideoOutputConfig", "description": "Video output configuration.", "default": { - "enabled": false, - "encoder_device": { - "device_name": "GPU", - "gpu_id": 0 - } - }, - "examples": [ - { - "enabled": false, - "encoder_device": { - "device_name": "GPU", - "gpu_id": 0 - } - } - ] - } - }, - "type": "object", - "required": [ - "fps_floor", - "pipeline_density_specs" - ], - "title": "DensityTestSpec", - "description": "Request body for starting a density test.\n\nAttributes:\n fps_floor: Minimum acceptable FPS per stream.\n pipeline_density_specs: List of pipelines with relative stream_rate ratios.\n video_output: Optional configuration for storing encoded video outputs." - }, - "DensityTestSpec-Output": { - "properties": { - "fps_floor": { - "type": "integer", - "minimum": 0.0, - "title": "Fps Floor", - "description": "Minimum acceptable FPS per stream.", - "examples": [ - 30 - ] - }, - "pipeline_density_specs": { - "items": { - "$ref": "#/components/schemas/PipelineDensitySpec" - }, - "type": "array", - "title": "Pipeline Density Specs", - "description": "List of pipelines with relative stream_rate percentages that must sum to 100.", - "examples": [ - [ - { - "id": "pipeline-1", - "stream_rate": 50 - }, - { - "id": "pipeline-2", - "stream_rate": 50 - } - ] - ] - }, - "video_output": { - "$ref": "#/components/schemas/VideoOutputConfig", - "description": "Video output configuration.", - "default": { - "enabled": false, - "encoder_device": { - "device_name": "GPU", - "gpu_id": 0 - } + "enabled": false }, "examples": [ { - "enabled": false, - "encoder_device": { - "device_name": "GPU", - "gpu_id": 0 - } + "enabled": false } ] } @@ -1781,40 +1713,6 @@ "title": "Edge", "description": "Directed connection between two nodes in a generic pipeline graph.\n\nAttributes:\n id: Edge identifier, unique within a single graph.\n source: ID of the source node.\n target: ID of the target node." }, - "EncoderDeviceConfig": { - "properties": { - "device_name": { - "type": "string", - "title": "Device Name", - "description": "Name of the encoder device (e.g., 'GPU', 'CPU', 'NPU')", - "default": "GPU", - "examples": [ - "GPU", - "CPU", - "NPU" - ] - }, - "gpu_id": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Gpu Id", - "description": "GPU device index (only applicable when device_name indicates a GPU)", - "examples": [ - 0, - 1 - ] - } - }, - "type": "object", - "title": "EncoderDeviceConfig", - "description": "Encoder device configuration used in video output settings.\n\nAttributes:\n device_name: Name of the encoder device (for example ``\"GPU\"``).\n gpu_id: Optional GPU index when applicable.\n\nExample:\n .. code-block:: json\n\n {\n \"device_name\": \"GPU\",\n \"gpu_id\": 0\n }" - }, "HTTPValidationError": { "properties": { "detail": { @@ -2190,7 +2088,7 @@ "title": "Id" }, "request": { - "$ref": "#/components/schemas/PerformanceTestSpec-Output" + "$ref": "#/components/schemas/PerformanceTestSpec" } }, "type": "object", @@ -2201,57 +2099,7 @@ "title": "PerformanceJobSummary", "description": "Short summary for a performance test job.\n\nAttributes:\n id: Job identifier.\n request: Original PerformanceTestSpec used to start the job." }, - "PerformanceTestSpec-Input": { - "properties": { - "pipeline_performance_specs": { - "items": { - "$ref": "#/components/schemas/PipelinePerformanceSpec" - }, - "type": "array", - "title": "Pipeline Performance Specs", - "description": "List of pipelines with number of streams for each.", - "examples": [ - [ - { - "id": "pipeline-1", - "streams": 8 - }, - { - "id": "pipeline-2", - "streams": 8 - } - ] - ] - }, - "video_output": { - "$ref": "#/components/schemas/VideoOutputConfig", - "description": "Video output configuration.", - "default": { - "enabled": false, - "encoder_device": { - "device_name": "GPU", - "gpu_id": 0 - } - }, - "examples": [ - { - "enabled": false, - "encoder_device": { - "device_name": "GPU", - "gpu_id": 0 - } - } - ] - } - }, - "type": "object", - "required": [ - "pipeline_performance_specs" - ], - "title": "PerformanceTestSpec", - "description": "Request body for starting a performance test.\n\nAttributes:\n pipeline_performance_specs: List of pipelines and their stream counts.\n video_output: Optional configuration for storing encoded video outputs." - }, - "PerformanceTestSpec-Output": { + "PerformanceTestSpec": { "properties": { "pipeline_performance_specs": { "items": { @@ -2277,19 +2125,11 @@ "$ref": "#/components/schemas/VideoOutputConfig", "description": "Video output configuration.", "default": { - "enabled": false, - "encoder_device": { - "device_name": "GPU", - "gpu_id": 0 - } + "enabled": false }, "examples": [ { - "enabled": false, - "encoder_device": { - "device_name": "GPU", - "gpu_id": 0 - } + "enabled": false } ] } @@ -2955,25 +2795,11 @@ "title": "Enabled", "description": "Flag to enable or disable video output generation.", "default": false - }, - "encoder_device": { - "$ref": "#/components/schemas/EncoderDeviceConfig", - "description": "Encoder device configuration (only applicable when video output is enabled).", - "default": { - "device_name": "GPU", - "gpu_id": 0 - }, - "examples": [ - { - "device_name": "GPU", - "gpu_id": 0 - } - ] } }, "type": "object", "title": "VideoOutputConfig", - "description": "Generic configuration of optional encoded video output.\n\nAttributes:\n enabled: Flag to enable or disable video output generation.\n encoder_device: EncoderDeviceConfig used when video output is enabled.\n\nExample:\n .. code-block:: json\n\n {\n \"enabled\": false,\n \"encoder_device\": {\n \"device_name\": \"GPU\",\n \"gpu_id\": 0\n }\n }" + "description": "Generic configuration of optional encoded video output.\n\nAttributes:\n enabled: Flag to enable or disable video output generation.\n\nExample:\n .. code-block:: json\n\n {\n \"enabled\": false\n }" } } } diff --git a/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/api/api.generated.ts b/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/api/api.generated.ts index 7d87cead7a..0e731e2d14 100644 --- a/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/api/api.generated.ts +++ b/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/api/api.generated.ts @@ -224,7 +224,7 @@ const injectedRtkApi = api query: (queryArg) => ({ url: `/tests/performance`, method: "POST", - body: queryArg.performanceTestSpecInput, + body: queryArg.performanceTestSpec, }), invalidatesTags: ["tests"], }), @@ -235,7 +235,7 @@ const injectedRtkApi = api query: (queryArg) => ({ url: `/tests/density`, method: "POST", - body: queryArg.densityTestSpecInput, + body: queryArg.densityTestSpec, }), invalidatesTags: ["tests"], }), @@ -365,12 +365,12 @@ export type OptimizePipelineApiArg = { export type RunPerformanceTestApiResponse = /** status 202 Performance test job created */ TestJobResponse; export type RunPerformanceTestApiArg = { - performanceTestSpecInput: PerformanceTestSpec2; + performanceTestSpec: PerformanceTestSpec; }; export type RunDensityTestApiResponse = /** status 202 Density test job created */ TestJobResponse; export type RunDensityTestApiArg = { - densityTestSpecInput: DensityTestSpec2; + densityTestSpec: DensityTestSpec; }; export type GetVideosApiResponse = /** status 200 Successful Response */ Video[]; @@ -439,17 +439,9 @@ export type PerformanceJobStatus = { } | null; error_message: string | null; }; -export type EncoderDeviceConfig = { - /** Name of the encoder device (e.g., 'GPU', 'CPU', 'NPU') */ - device_name?: string; - /** GPU device index (only applicable when device_name indicates a GPU) */ - gpu_id?: number | null; -}; export type VideoOutputConfig = { /** Flag to enable or disable video output generation. */ enabled?: boolean; - /** Encoder device configuration (only applicable when video output is enabled). */ - encoder_device?: EncoderDeviceConfig; }; export type PerformanceTestSpec = { /** List of pipelines with number of streams for each. */ @@ -607,20 +599,6 @@ export type TestJobResponse = { /** Identifier of the created test job. */ job_id: string; }; -export type PerformanceTestSpec2 = { - /** List of pipelines with number of streams for each. */ - pipeline_performance_specs: PipelinePerformanceSpec[]; - /** Video output configuration. */ - video_output?: VideoOutputConfig; -}; -export type DensityTestSpec2 = { - /** Minimum acceptable FPS per stream. */ - fps_floor: number; - /** List of pipelines with relative stream_rate percentages that must sum to 100. */ - pipeline_density_specs: PipelineDensitySpec[]; - /** Video output configuration. */ - video_output?: VideoOutputConfig; -}; export type Video = { filename: string; width: number; diff --git a/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/features/pipeline-tests/SaveOutputWarning.tsx b/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/features/pipeline-tests/SaveOutputWarning.tsx index 66458eb640..a94830d3e2 100644 --- a/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/features/pipeline-tests/SaveOutputWarning.tsx +++ b/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/features/pipeline-tests/SaveOutputWarning.tsx @@ -1,22 +1,8 @@ const SaveOutputWarning = () => { return (
- Note 1: The current implementation does not automatically infer the - best encoding device from the existing pipeline. Select the same device - that is already used by other blocks in your pipeline. To learn more, - refer to our documentation:{" "} - - link - - . -
- Note 2: Selecting this option will negatively impact the - performance results. + Note: Selecting this option will negatively impact the performance + results.
); }; diff --git a/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/pages/DensityTests.tsx b/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/pages/DensityTests.tsx index fdb8c3ad01..8acc84b421 100644 --- a/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/pages/DensityTests.tsx +++ b/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/pages/DensityTests.tsx @@ -9,7 +9,6 @@ import { PipelineStreamsSummary } from "@/features/pipeline-tests/PipelineStream import { PipelineName } from "@/features/pipelines/PipelineName.tsx"; import { useAppSelector } from "@/store/hooks"; import { selectPipelines } from "@/store/reducers/pipelines"; -import { selectDevices } from "@/store/reducers/devices"; import { Checkbox } from "@/components/ui/checkbox"; import { Tooltip, @@ -18,7 +17,6 @@ import { } from "@/components/ui/tooltip"; import { Plus, X } from "lucide-react"; import { ParticipationSlider } from "@/features/pipeline-tests/ParticipationSlider.tsx"; -import DeviceSelect from "@/components/shared/DeviceSelect"; import SaveOutputWarning from "@/features/pipeline-tests/SaveOutputWarning.tsx"; interface PipelineSelection { @@ -30,7 +28,6 @@ interface PipelineSelection { const DensityTests = () => { const pipelines = useAppSelector(selectPipelines); - const devices = useAppSelector(selectDevices); const [runDensityTest, { isLoading: isRunning }] = useRunDensityTestMutation(); const [pipelineSelections, setPipelineSelections] = useState< @@ -46,7 +43,6 @@ const DensityTests = () => { } | null>(null); const [videoOutputEnabled, setVideoOutputEnabled] = useState(false); const [errorMessage, setErrorMessage] = useState(null); - const [encoderDevice, setEncoderDevice] = useState("CPU"); const { data: jobStatus } = useGetDensityJobStatusQuery( { jobId: jobId! }, @@ -154,24 +150,10 @@ const DensityTests = () => { setTestResult(null); setErrorMessage(null); try { - const selectedDevice = devices.find( - (d) => d.device_name === encoderDevice, - ); - const result = await runDensityTest({ - densityTestSpecInput: { + densityTestSpec: { video_output: { enabled: videoOutputEnabled, - encoder_device: - videoOutputEnabled && selectedDevice - ? { - device_name: selectedDevice.device_name, - gpu_id: - selectedDevice.device_family === "GPU" - ? (selectedDevice.gpu_id ?? 0) - : undefined, - } - : undefined, }, fps_floor: fpsFloor, pipeline_density_specs: pipelineSelections.map((selection) => ({ @@ -319,16 +301,6 @@ const DensityTests = () => { - {videoOutputEnabled && ( -
- Select device for encoding: - -
- )} {videoOutputEnabled && } diff --git a/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/pages/PerformanceTests.tsx b/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/pages/PerformanceTests.tsx index 18decba423..dca520d2c5 100644 --- a/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/pages/PerformanceTests.tsx +++ b/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/pages/PerformanceTests.tsx @@ -7,7 +7,6 @@ import { TestProgressIndicator } from "@/features/pipeline-tests/TestProgressInd import { PipelineName } from "@/features/pipelines/PipelineName.tsx"; import { useAppSelector } from "@/store/hooks"; import { selectPipelines } from "@/store/reducers/pipelines"; -import { selectDevices } from "@/store/reducers/devices"; import { Checkbox } from "@/components/ui/checkbox"; import { Tooltip, @@ -16,7 +15,6 @@ import { } from "@/components/ui/tooltip"; import { Plus, X } from "lucide-react"; import { StreamsSlider } from "@/features/pipeline-tests/StreamsSlider.tsx"; -import DeviceSelect from "@/components/shared/DeviceSelect"; import SaveOutputWarning from "@/features/pipeline-tests/SaveOutputWarning.tsx"; interface PipelineSelection { @@ -28,7 +26,6 @@ interface PipelineSelection { const PerformanceTests = () => { const pipelines = useAppSelector(selectPipelines); - const devices = useAppSelector(selectDevices); const [runPerformanceTest, { isLoading: isRunning }] = useRunPerformanceTestMutation(); const [pipelineSelections, setPipelineSelections] = useState< @@ -44,7 +41,6 @@ const PerformanceTests = () => { } | null>(null); const [videoOutputEnabled, setVideoOutputEnabled] = useState(false); const [errorMessage, setErrorMessage] = useState(null); - const [encoderDevice, setEncoderDevice] = useState("CPU"); const { data: jobStatus } = useGetPerformanceJobStatusQuery( { jobId: jobId! }, @@ -149,24 +145,10 @@ const PerformanceTests = () => { setTestResult(null); setErrorMessage(null); try { - const selectedDevice = devices.find( - (d) => d.device_name === encoderDevice, - ); - const result = await runPerformanceTest({ - performanceTestSpecInput: { + performanceTestSpec: { video_output: { enabled: videoOutputEnabled, - encoder_device: - videoOutputEnabled && selectedDevice - ? { - device_name: selectedDevice.device_name, - gpu_id: - selectedDevice.device_family === "GPU" - ? (selectedDevice.gpu_id ?? 0) - : undefined, - } - : undefined, }, pipeline_performance_specs: pipelineSelections.map((selection) => ({ id: selection.pipelineId, @@ -297,16 +279,6 @@ const PerformanceTests = () => { - {videoOutputEnabled && ( -
- Select device for encoding: - -
- )} {videoOutputEnabled && } diff --git a/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/pages/Pipelines.tsx b/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/pages/Pipelines.tsx index be4d2e2f53..510bb8f72c 100644 --- a/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/pages/Pipelines.tsx +++ b/tools/visual-pipeline-and-platform-evaluation-tool/ui/src/pages/Pipelines.tsx @@ -24,7 +24,6 @@ import StopPerformanceTestButton from "@/features/pipeline-editor/StopPerformanc import ExportPipelineButton from "@/features/pipeline-editor/ExportPipelineButton.tsx"; import DeletePipelineButton from "@/features/pipeline-editor/DeletePipelineButton.tsx"; import ImportPipelineButton from "@/features/pipeline-editor/ImportPipelineButton.tsx"; -import DeviceSelect from "@/components/shared/DeviceSelect"; import { Zap } from "lucide-react"; import { isApiError } from "@/lib/apiUtils"; import { @@ -33,8 +32,6 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { Checkbox } from "@/components/ui/checkbox"; -import { useAppSelector } from "@/store/hooks"; -import { selectDevices } from "@/store/reducers/devices"; type UrlParams = { id: string; @@ -42,7 +39,6 @@ type UrlParams = { const Pipelines = () => { const { id } = useParams(); - const devices = useAppSelector(selectDevices); const [performanceTestJobId, setPerformanceTestJobId] = useState< string | null >(null); @@ -56,7 +52,6 @@ const Pipelines = () => { const [editorKey, setEditorKey] = useState(0); const [shouldFitView, setShouldFitView] = useState(false); const [videoOutputEnabled, setVideoOutputEnabled] = useState(true); - const [encoderDevice, setEncoderDevice] = useState("CPU"); const [completedVideoPath, setCompletedVideoPath] = useState( null, ); @@ -396,24 +391,10 @@ const Pipelines = () => { }, }).unwrap(); - const selectedDevice = devices.find( - (d) => d.device_name === encoderDevice, - ); - const response = await runPerformanceTest({ - performanceTestSpecInput: { + performanceTestSpec: { video_output: { enabled: videoOutputEnabled, - encoder_device: - videoOutputEnabled && selectedDevice - ? { - device_name: selectedDevice.device_name, - gpu_id: - selectedDevice.device_family === "GPU" - ? (selectedDevice.gpu_id ?? 0) - : undefined, - } - : undefined, }, pipeline_performance_specs: [ { @@ -617,31 +598,7 @@ const Pipelines = () => {

- {videoOutputEnabled && ( - - )} - {videoOutputEnabled && ( -
- Note: The current implementation does not automatically - infer the best encoding device from the existing pipeline. Select - the same device that is already used by other blocks in your - pipeline. To learn more, refer to our documentation:{" "} - - link - - . -
- )} ); diff --git a/tools/visual-pipeline-and-platform-evaluation-tool/vippet/api/api_schemas.py b/tools/visual-pipeline-and-platform-evaluation-tool/vippet/api/api_schemas.py index f23d282844..aef115927a 100644 --- a/tools/visual-pipeline-and-platform-evaluation-tool/vippet/api/api_schemas.py +++ b/tools/visual-pipeline-and-platform-evaluation-tool/vippet/api/api_schemas.py @@ -528,63 +528,24 @@ class PipelineRequestOptimize(BaseModel): parameters: Optional[Dict[str, Any]] -class EncoderDeviceConfig(BaseModel): - """ - Encoder device configuration used in video output settings. - - Attributes: - device_name: Name of the encoder device (for example ``"GPU"``). - gpu_id: Optional GPU index when applicable. - - Example: - .. code-block:: json - - { - "device_name": "GPU", - "gpu_id": 0 - } - """ - - device_name: str = Field( - default="GPU", - description="Name of the encoder device (e.g., 'GPU', 'CPU', 'NPU')", - examples=["GPU", "CPU", "NPU"], - ) - gpu_id: Optional[int] = Field( - default=None, - description="GPU device index (only applicable when device_name indicates a GPU)", - examples=[0, 1], - ) - - class VideoOutputConfig(BaseModel): """ Generic configuration of optional encoded video output. Attributes: enabled: Flag to enable or disable video output generation. - encoder_device: EncoderDeviceConfig used when video output is enabled. Example: .. code-block:: json { - "enabled": false, - "encoder_device": { - "device_name": "GPU", - "gpu_id": 0 - } + "enabled": false } """ enabled: bool = Field( default=False, description="Flag to enable or disable video output generation." ) - encoder_device: EncoderDeviceConfig = Field( - default=EncoderDeviceConfig(device_name="GPU", gpu_id=0), - description="Encoder device configuration (only applicable when video output is enabled).", - examples=[{"device_name": "GPU", "gpu_id": 0}], - ) class PerformanceTestSpec(BaseModel): @@ -609,12 +570,9 @@ class PerformanceTestSpec(BaseModel): video_output: VideoOutputConfig = Field( default=VideoOutputConfig( enabled=False, - encoder_device=EncoderDeviceConfig(device_name="GPU", gpu_id=0), ), description="Video output configuration.", - examples=[ - {"enabled": False, "encoder_device": {"device_name": "GPU", "gpu_id": 0}} - ], + examples=[{"enabled": False}], ) @@ -646,12 +604,9 @@ class DensityTestSpec(BaseModel): video_output: VideoOutputConfig = Field( default=VideoOutputConfig( enabled=False, - encoder_device=EncoderDeviceConfig(device_name="GPU", gpu_id=0), ), description="Video output configuration.", - examples=[ - {"enabled": False, "encoder_device": {"device_name": "GPU", "gpu_id": 0}} - ], + examples=[{"enabled": False}], ) diff --git a/tools/visual-pipeline-and-platform-evaluation-tool/vippet/api/routes/tests.py b/tools/visual-pipeline-and-platform-evaluation-tool/vippet/api/routes/tests.py index beebf0b2ea..b616e1741b 100644 --- a/tools/visual-pipeline-and-platform-evaluation-tool/vippet/api/routes/tests.py +++ b/tools/visual-pipeline-and-platform-evaluation-tool/vippet/api/routes/tests.py @@ -47,7 +47,7 @@ def run_performance_test(body: schemas.PerformanceTestSpec): * pipeline_performance_specs – list of pipelines and number of streams per pipeline. * video_output – configuration for optional encoded video output - (enabled flag and encoder_device). + (enabled flag). Returns: 202 Accepted: @@ -78,8 +78,7 @@ def run_performance_test(body: schemas.PerformanceTestSpec): {"id": "pipeline-b7c2e114", "streams": 4} ], "video_output": { - "enabled": false, - "encoder_device": {"device_name": "GPU", "gpu_id": 0} + "enabled": false } } @@ -186,8 +185,7 @@ def run_density_test(body: schemas.DensityTestSpec): {"id": "pipeline-b7c2e114", "stream_rate": 50} ], "video_output": { - "enabled": false, - "encoder_device": {"device_name": "GPU", "gpu_id": 0} + "enabled": false } } diff --git a/tools/visual-pipeline-and-platform-evaluation-tool/vippet/graph.py b/tools/visual-pipeline-and-platform-evaluation-tool/vippet/graph.py index d599551843..fbb6c775bb 100644 --- a/tools/visual-pipeline-and-platform-evaluation-tool/vippet/graph.py +++ b/tools/visual-pipeline-and-platform-evaluation-tool/vippet/graph.py @@ -10,6 +10,7 @@ from utils import generate_unique_filename from videos import get_videos_manager, OUTPUT_VIDEO_DIR from models import get_supported_models_manager +from video_encoder import ENCODER_DEVICE_CPU, ENCODER_DEVICE_GPU from resources import ( get_labels_manager, get_scripts_manager, @@ -392,6 +393,29 @@ def get_input_video_filenames(self) -> list[str]: return input_filenames + def get_recommended_encoder_device(self) -> str: + """ + Iterate backwards through nodes to find the last video/x-raw node + and return the recommended encoder device based on memory type. + + Note: NPU variants are not considered because NPUs do not provide dedicated + memory accessible for GStreamer pipeline buffering; they operate exclusively + on system or shared memory. + + Returns: + str: ENCODER_DEVICE_GPU if video/x-raw(memory:VAMemory) is detected, + ENCODER_DEVICE_CPU for standard video/x-raw or when no video/x-raw + node exists in the pipeline. + """ + for node in reversed(self.nodes): + if not node.type.startswith("video/x-raw"): + continue + if "memory:VAMemory" in node.type: + return ENCODER_DEVICE_GPU + return ENCODER_DEVICE_CPU + + return ENCODER_DEVICE_CPU + def to_simple_view(self) -> "Graph": """ Generate a simplified view of the pipeline graph by filtering out technical elements. diff --git a/tools/visual-pipeline-and-platform-evaluation-tool/vippet/managers/pipeline_manager.py b/tools/visual-pipeline-and-platform-evaluation-tool/vippet/managers/pipeline_manager.py index 7294da5b19..9a60d76ee9 100644 --- a/tools/visual-pipeline-and-platform-evaluation-tool/vippet/managers/pipeline_manager.py +++ b/tools/visual-pipeline-and-platform-evaluation-tool/vippet/managers/pipeline_manager.py @@ -299,11 +299,14 @@ def build_pipeline_command( # Handle final video output if enabled if video_config.enabled and stream_index == 0: + # Get recommended encoder device from the graph + encoder_device = graph.get_recommended_encoder_device() + # Replace fakesink with actual video output element unique_pipeline_str, generated_paths = ( self.video_encoder.replace_fakesink_with_video_output( pipeline.id, unique_pipeline_str, - video_config.encoder_device, + encoder_device, input_video_filenames, ) ) diff --git a/tools/visual-pipeline-and-platform-evaluation-tool/vippet/tests/api_tests/tests_test.py b/tools/visual-pipeline-and-platform-evaluation-tool/vippet/tests/api_tests/tests_test.py index 55b40fd7b9..8c4b3e3db4 100644 --- a/tools/visual-pipeline-and-platform-evaluation-tool/vippet/tests/api_tests/tests_test.py +++ b/tools/visual-pipeline-and-platform-evaluation-tool/vippet/tests/api_tests/tests_test.py @@ -200,8 +200,6 @@ def test_run_performance_test_with_gpu_encoder(self, mock_test_manager): # Verify video output configuration self.assertTrue(call_args.video_output.enabled) - self.assertEqual(call_args.video_output.encoder_device.device_name, "GPU") - self.assertEqual(call_args.video_output.encoder_device.gpu_id, 0) # ------------------------------------------------------------------ # /tests/density diff --git a/tools/visual-pipeline-and-platform-evaluation-tool/vippet/tests/graph_test.py b/tools/visual-pipeline-and-platform-evaluation-tool/vippet/tests/graph_test.py index 8fb141db14..82cf6d90f1 100644 --- a/tools/visual-pipeline-and-platform-evaluation-tool/vippet/tests/graph_test.py +++ b/tools/visual-pipeline-and-platform-evaluation-tool/vippet/tests/graph_test.py @@ -3,6 +3,7 @@ from dataclasses import dataclass from unittest.mock import MagicMock, patch from graph import Graph, Node, Edge +from video_encoder import ENCODER_DEVICE_GPU, ENCODER_DEVICE_CPU mock_models_manager = MagicMock() mock_videos_manager = MagicMock() @@ -2171,6 +2172,134 @@ def test_empty_graph_raises_error(self): self.assertIn("Empty graph", str(cm.exception)) +class TestGetRecommendedEncoderDevice(unittest.TestCase): + """Test cases for Graph.get_recommended_encoder_device method.""" + + def test_gpu_encoder_for_va_memory_caps(self): + """Test that GPU encoder is recommended when video/x-raw(memory:VAMemory) is found.""" + graph = Graph( + nodes=[ + Node(id="0", type="filesrc", data={"location": "test.mp4"}), + Node(id="1", type="decodebin3", data={}), + Node( + id="2", + type="video/x-raw(memory:VAMemory)", + data={"__node_kind": "caps"}, + ), + Node(id="3", type="fakesink", data={}), + ], + edges=[ + Edge(id="0", source="0", target="1"), + Edge(id="1", source="1", target="2"), + Edge(id="2", source="2", target="3"), + ], + ) + + self.assertEqual(graph.get_recommended_encoder_device(), ENCODER_DEVICE_GPU) + + def test_cpu_encoder_for_standard_video_raw(self): + """Test that CPU encoder is recommended for standard video/x-raw caps.""" + graph = Graph( + nodes=[ + Node(id="0", type="filesrc", data={"location": "test.mp4"}), + Node(id="1", type="decodebin3", data={}), + Node( + id="2", + type="video/x-raw", + data={"__node_kind": "caps", "width": "640", "height": "480"}, + ), + Node(id="3", type="fakesink", data={}), + ], + edges=[ + Edge(id="0", source="0", target="1"), + Edge(id="1", source="1", target="2"), + Edge(id="2", source="2", target="3"), + ], + ) + + self.assertEqual(graph.get_recommended_encoder_device(), ENCODER_DEVICE_CPU) + + def test_cpu_encoder_when_no_video_raw_caps(self): + """Test that CPU encoder is recommended when no video/x-raw caps exist.""" + graph = Graph( + nodes=[ + Node(id="0", type="filesrc", data={"location": "test.mp4"}), + Node(id="1", type="decodebin3", data={}), + Node(id="2", type="queue", data={}), + Node(id="3", type="fakesink", data={}), + ], + edges=[ + Edge(id="0", source="0", target="1"), + Edge(id="1", source="1", target="2"), + Edge(id="2", source="2", target="3"), + ], + ) + + self.assertEqual(graph.get_recommended_encoder_device(), ENCODER_DEVICE_CPU) + + def test_uses_last_video_raw_caps_when_multiple_exist(self): + """Test that the method uses the last video/x-raw caps in the pipeline.""" + graph = Graph( + nodes=[ + Node(id="0", type="filesrc", data={"location": "test.mp4"}), + Node( + id="1", + type="video/x-raw", + data={"__node_kind": "caps", "width": "640"}, + ), + Node(id="2", type="queue", data={}), + Node( + id="3", + type="video/x-raw(memory:VAMemory)", + data={"__node_kind": "caps"}, + ), + Node(id="4", type="fakesink", data={}), + ], + edges=[ + Edge(id="0", source="0", target="1"), + Edge(id="1", source="1", target="2"), + Edge(id="2", source="2", target="3"), + Edge(id="3", source="3", target="4"), + ], + ) + + # Should return GPU because the last video/x-raw has VAMemory + self.assertEqual(graph.get_recommended_encoder_device(), ENCODER_DEVICE_GPU) + + def test_iterates_backwards_through_nodes(self): + """Test that the method iterates backwards (uses last occurrence, not first).""" + graph = Graph( + nodes=[ + Node(id="0", type="filesrc", data={"location": "test.mp4"}), + Node( + id="1", + type="video/x-raw(memory:VAMemory)", + data={"__node_kind": "caps"}, + ), + Node(id="2", type="queue", data={}), + Node( + id="3", type="video/x-raw", data={"__node_kind": "caps"} + ), # Last one, no VAMemory + Node(id="4", type="fakesink", data={}), + ], + edges=[ + Edge(id="0", source="0", target="1"), + Edge(id="1", source="1", target="2"), + Edge(id="2", source="2", target="3"), + Edge(id="3", source="3", target="4"), + ], + ) + + # Should return CPU because iterating backwards finds node 3 first (no VAMemory) + self.assertEqual(graph.get_recommended_encoder_device(), ENCODER_DEVICE_CPU) + + def test_empty_graph(self): + """Test that CPU encoder is recommended for an empty graph.""" + graph = Graph(nodes=[], edges=[]) + + self.assertEqual(graph.get_recommended_encoder_device(), ENCODER_DEVICE_CPU) + + class TestToSimpleView(unittest.TestCase): """ Test the to_simple_view method which generates simplified graphs diff --git a/tools/visual-pipeline-and-platform-evaluation-tool/vippet/tests/managers_tests/pipeline_manager_test.py b/tools/visual-pipeline-and-platform-evaluation-tool/vippet/tests/managers_tests/pipeline_manager_test.py index dd2371b96e..40d13e64a3 100644 --- a/tools/visual-pipeline-and-platform-evaluation-tool/vippet/tests/managers_tests/pipeline_manager_test.py +++ b/tools/visual-pipeline-and-platform-evaluation-tool/vippet/tests/managers_tests/pipeline_manager_test.py @@ -5,7 +5,6 @@ from api.api_schemas import ( Node, Edge, - EncoderDeviceConfig, PipelineType, PipelineSource, PipelineGraph, @@ -458,7 +457,6 @@ def test_build_pipeline_command_with_video_output_enabled(self): pipeline_performance_specs = [PipelinePerformanceSpec(id=added.id, streams=1)] video_config = VideoOutputConfig( enabled=True, - encoder_device=EncoderDeviceConfig(device_name="CPU", gpu_id=None), ) command, output_paths = manager.build_pipeline_command( @@ -497,7 +495,6 @@ def test_build_pipeline_command_with_gpu_encoder(self): pipeline_performance_specs = [PipelinePerformanceSpec(id=added.id, streams=2)] video_config = VideoOutputConfig( enabled=True, - encoder_device=EncoderDeviceConfig(device_name="GPU", gpu_id=0), ) command, output_paths = manager.build_pipeline_command( @@ -544,7 +541,6 @@ def test_build_pipeline_command_video_output_multiple_pipelines(self): ] video_config = VideoOutputConfig( enabled=True, - encoder_device=EncoderDeviceConfig(device_name="CPU", gpu_id=None), ) command, output_paths = manager.build_pipeline_command( diff --git a/tools/visual-pipeline-and-platform-evaluation-tool/vippet/tests/video_encoder_test.py b/tools/visual-pipeline-and-platform-evaluation-tool/vippet/tests/video_encoder_test.py index b1b108b286..e81fe9348d 100644 --- a/tools/visual-pipeline-and-platform-evaluation-tool/vippet/tests/video_encoder_test.py +++ b/tools/visual-pipeline-and-platform-evaluation-tool/vippet/tests/video_encoder_test.py @@ -3,10 +3,9 @@ from video_encoder import ( VideoEncoder, - GPU_0, - OTHER, + ENCODER_DEVICE_CPU, + ENCODER_DEVICE_GPU, ) -from api.api_schemas import EncoderDeviceConfig class TestVideoEncoderClass(unittest.TestCase): @@ -27,56 +26,19 @@ def test_initialization(self, mock_gst_inspector): self.assertIn("h264", encoder.encoder_configs) mock_gst_inspector.assert_called_once() - def test_select_gpu_single_gpu(self): - """Test GPU device without index.""" - gpu_id, vaapi_suffix = self.encoder.select_gpu("GPU") - self.assertEqual(gpu_id, 0) - self.assertIsNone(vaapi_suffix) - - def test_select_gpu_first_with_index(self): - """Test GPU.0 device.""" - gpu_id, vaapi_suffix = self.encoder.select_gpu("GPU.0") - self.assertEqual(gpu_id, 0) - self.assertIsNone(vaapi_suffix) - - def test_select_gpu_second_gpu(self): - """Test GPU.1 device with vaapi suffix.""" - gpu_id, vaapi_suffix = self.encoder.select_gpu("GPU.1") - self.assertEqual(gpu_id, 1) - self.assertEqual(vaapi_suffix, "129") - - def test_select_gpu_cpu_device(self): - """Test non-GPU device (CPU).""" - gpu_id, vaapi_suffix = self.encoder.select_gpu("CPU") - self.assertEqual(gpu_id, -1) - self.assertIsNone(vaapi_suffix) - def test_select_element_gpu_0(self): """Test selecting encoder for GPU 0.""" self.encoder.gst_inspector.elements = [("elem1", "vah264enc")] - encoder_device = EncoderDeviceConfig(device_name="GPU", gpu_id=0) + encoder_device = ENCODER_DEVICE_GPU encoder_dict = { - GPU_0: [("vah264enc", "vah264enc")], - OTHER: [("x264enc", "x264enc")], + ENCODER_DEVICE_GPU: [("vah264enc", "vah264enc")], + ENCODER_DEVICE_CPU: [("x264enc", "x264enc")], } - result = self.encoder.select_element(encoder_dict, encoder_device, None) + result = self.encoder.select_element(encoder_dict, encoder_device) self.assertEqual(result, "vah264enc") - def test_select_element_fallback_to_cpu(self): - """Test fallback to CPU encoder when GPU encoder not available.""" - self.encoder.gst_inspector.elements = [("elem1", "x264enc")] - - encoder_device = EncoderDeviceConfig(device_name="GPU", gpu_id=0) - encoder_dict = { - GPU_0: [("vah264enc", "vah264enc")], - OTHER: [("x264enc", "x264enc bitrate=16000")], - } - - result = self.encoder.select_element(encoder_dict, encoder_device, None) - self.assertEqual(result, "x264enc bitrate=16000") - @patch("video_encoder.videos_manager") def test_detect_codec_from_input(self, mock_videos_manager): """Test codec detection from input videos.""" @@ -123,7 +85,7 @@ def test_replace_fakesink_with_video_output(self, mock_videos_manager): mock_videos_manager.get_video = Mock(return_value=mock_video) pipeline_str = "videotestsrc ! fakesink" - encoder_device = EncoderDeviceConfig(device_name="GPU", gpu_id=0) + encoder_device = ENCODER_DEVICE_GPU pipeline_id = "test-pipeline-123" result, output_paths = self.encoder.replace_fakesink_with_video_output( @@ -148,7 +110,7 @@ def test_replace_fakesink_with_h265_codec(self, mock_videos_manager): mock_videos_manager.get_video = Mock(return_value=mock_video) pipeline_str = "videotestsrc ! fakesink" - encoder_device = EncoderDeviceConfig(device_name="GPU", gpu_id=0) + encoder_device = ENCODER_DEVICE_GPU pipeline_id = "test-pipeline-456" result, output_paths = self.encoder.replace_fakesink_with_video_output( @@ -170,7 +132,7 @@ def test_replace_multiple_fakesinks(self, mock_videos_manager): pipeline_str = ( "videotestsrc ! tee name=t t. ! queue ! fakesink t. ! queue ! fakesink" ) - encoder_device = EncoderDeviceConfig(device_name="GPU", gpu_id=0) + encoder_device = ENCODER_DEVICE_GPU pipeline_id = "test-pipeline-789" result, output_paths = self.encoder.replace_fakesink_with_video_output( @@ -196,7 +158,7 @@ def test_replace_multiple_fakesinks(self, mock_videos_manager): @patch("video_encoder.videos_manager") def test_replace_fakesink_unsupported_codec(self, mock_videos_manager): """Test that unsupported codec raises ValueError.""" - encoder_device = EncoderDeviceConfig(device_name="GPU", gpu_id=0) + encoder_device = ENCODER_DEVICE_GPU mock_video = Mock() mock_video.codec = "av1" # Unsupported codec @@ -221,7 +183,7 @@ def test_replace_fakesink_no_encoder_found(self, mock_videos_manager): mock_video.codec = "h264" mock_videos_manager.get_video = Mock(return_value=mock_video) - encoder_device = EncoderDeviceConfig(device_name="GPU", gpu_id=0) + encoder_device = ENCODER_DEVICE_GPU with self.assertRaises(ValueError) as context: self.encoder.replace_fakesink_with_video_output( diff --git a/tools/visual-pipeline-and-platform-evaluation-tool/vippet/video_encoder.py b/tools/visual-pipeline-and-platform-evaluation-tool/vippet/video_encoder.py index 7381a511b1..45749ac367 100644 --- a/tools/visual-pipeline-and-platform-evaluation-tool/vippet/video_encoder.py +++ b/tools/visual-pipeline-and-platform-evaluation-tool/vippet/video_encoder.py @@ -4,14 +4,12 @@ from typing import Dict, List, Optional, Tuple from explore import GstInspector -from api.api_schemas import EncoderDeviceConfig from utils import generate_unique_filename from videos import get_videos_manager, OUTPUT_VIDEO_DIR -# Keys for device selection -GPU_0 = "GPU_0" -GPU_N = "GPU_N" -OTHER = "OTHER" +# Constants for encoder device types +ENCODER_DEVICE_CPU = "CPU" +ENCODER_DEVICE_GPU = "GPU" # Default codec for encoding DEFAULT_CODEC = "h264" @@ -60,130 +58,69 @@ def __init__(self): # Define encoder configurations for different codecs self.encoder_configs = { "h264": { - GPU_0: [ + ENCODER_DEVICE_GPU: [ ("vah264lpenc", "vah264lpenc"), ("vah264enc", "vah264enc"), ], - GPU_N: [ - ( - f"varenderD{VAAPI_SUFFIX_PLACEHOLDER}h264lpenc", - f"varenderD{VAAPI_SUFFIX_PLACEHOLDER}h264lpenc", - ), - ( - f"varenderD{VAAPI_SUFFIX_PLACEHOLDER}h264enc", - f"varenderD{VAAPI_SUFFIX_PLACEHOLDER}h264enc", - ), - ], - OTHER: [ + ENCODER_DEVICE_CPU: [ ("x264enc", "x264enc bitrate=16000 speed-preset=superfast"), ], }, "h265": { - GPU_0: [ + ENCODER_DEVICE_GPU: [ ("vah265lpenc", "vah265lpenc"), ("vah265enc", "vah265enc"), ], - GPU_N: [ - ( - f"varenderD{VAAPI_SUFFIX_PLACEHOLDER}h265lpenc", - f"varenderD{VAAPI_SUFFIX_PLACEHOLDER}h265lpenc", - ), - ( - f"varenderD{VAAPI_SUFFIX_PLACEHOLDER}h265enc", - f"varenderD{VAAPI_SUFFIX_PLACEHOLDER}h265enc", - ), - ], - OTHER: [ + ENCODER_DEVICE_CPU: [ ("x265enc", "x265enc bitrate=16000 speed-preset=superfast"), ], }, } - def select_gpu(self, device: str | None) -> Tuple[int, Optional[str]]: - """ - Parse device name and determine GPU ID and VAAPI suffix. - - Args: - device: Device name (e.g., "GPU", "GPU.0", "GPU.1", "CPU") - - Returns: - Tuple of (gpu_id, vaapi_suffix) - - gpu_id: 0 for first GPU, >0 for other GPUs, -1 for non-GPU - - vaapi_suffix: VAAPI device suffix for multi-GPU systems - """ - gpu_id = -1 - vaapi_suffix = None - - # Determine gpu_id and vaapi_suffix - # If there is only one GPU, device name is just GPU - # If there is more than one GPU, device names are like GPU.0, GPU.1, ... - if device == "GPU": - gpu_id = 0 - elif device is not None and device.startswith("GPU."): - try: - gpu_index = int(device.split(".")[1]) - if gpu_index == 0: - gpu_id = 0 - elif gpu_index > 0: - vaapi_suffix = str(128 + gpu_index) - gpu_id = gpu_index - except (IndexError, ValueError): - self.logger.warning(f"Failed to parse GPU index from device: {device}") - gpu_id = -1 - else: - gpu_id = -1 - - return gpu_id, vaapi_suffix - def select_element( self, field_dict: Dict[str, List[Tuple[str, str]]], - encoder_device: EncoderDeviceConfig, - vaapi_suffix: Optional[str], + encoder_device: str, ) -> Optional[str]: """ Select an appropriate encoder element from available GStreamer elements. Args: field_dict: Dictionary mapping device types to lists of (search, result) tuples - encoder_device: Encoder device configuration - vaapi_suffix: VAAPI device suffix for multi-GPU systems + encoder_device: Target encoder device. Must be one of the module constants: + - ENCODER_DEVICE_CPU ("CPU"): Use CPU-based encoder + - ENCODER_DEVICE_GPU ("GPU"): Use GPU-based encoder (VAAPI) Returns: Selected encoder element string with properties, or None if not found + + Raises: + ValueError: If encoder_device is not a valid constant value """ - key = OTHER - if encoder_device.device_name.startswith("GPU"): - if encoder_device.gpu_id == 0: - key = GPU_0 - elif encoder_device.gpu_id is not None and encoder_device.gpu_id > 0: - key = GPU_N - - pairs = field_dict.get(key, []) - # Add OTHER pairs as fallback if key is not OTHER - if key != OTHER: - pairs = pairs + field_dict.get(OTHER, []) + # Validate encoder_device + valid_devices = {ENCODER_DEVICE_CPU, ENCODER_DEVICE_GPU} + if encoder_device not in valid_devices: + raise ValueError( + f"Invalid encoder_device: {encoder_device}. " + f"Must be one of: {', '.join(valid_devices)}" + ) + + pairs = field_dict.get(encoder_device, []) if not pairs: - self.logger.warning(f"No encoder pairs found for key: {key}") + self.logger.warning( + f"No encoder pairs found for encoder_device: {encoder_device}" + ) return None for search, result in pairs: - if search == "": # to support optional parameters - return result - - if VAAPI_SUFFIX_PLACEHOLDER in search or VAAPI_SUFFIX_PLACEHOLDER in result: - suffix = vaapi_suffix if vaapi_suffix is not None else "" - search = search.replace(VAAPI_SUFFIX_PLACEHOLDER, suffix) - result = result.replace(VAAPI_SUFFIX_PLACEHOLDER, suffix) - for element in self.gst_inspector.elements: if element[1] == search: self.logger.debug(f"Selected encoder element: {result}") return result self.logger.warning( - f"No matching encoder element found for device: {encoder_device.device_name}" + f"No matching encoder element found for encoder_device: {encoder_device}" ) return None @@ -230,7 +167,7 @@ def replace_fakesink_with_video_output( self, pipeline_id: str, pipeline_str: str, - encoder_device: EncoderDeviceConfig, + encoder_device: str, input_video_filenames: list[str], ) -> Tuple[str, List[str]]: """ @@ -239,33 +176,38 @@ def replace_fakesink_with_video_output( Args: pipeline_id: Pipeline ID used to generate unique output filenames pipeline_str: GStreamer pipeline string containing fakesink(s) - encoder_device: Encoder device configuration + encoder_device: Target encoder device. Must be one of the module constants: + - ENCODER_DEVICE_CPU ("CPU"): Use CPU-based encoder + - ENCODER_DEVICE_GPU ("GPU"): Use GPU-based encoder (VAAPI) input_video_filenames: List of input video filenames to detect codec Returns: Tuple of (modified pipeline string, list of output paths) Raises: - ValueError: If codec is not supported or no suitable encoder is found + ValueError: If codec is not supported, encoder_device is invalid, + or no suitable encoder is found """ + # Detect codec from input video files (h264, h265, etc.) codec = self._detect_codec_from_input(input_video_filenames) self._validate_codec(codec) + # Get encoder configuration for the detected codec (GPU/CPU variants) encoder_config = self.encoder_configs[codec] - gpu_id, vaapi_suffix = self.select_gpu(encoder_device.device_name) + # Select the best available encoder element based on device type and + # installed GStreamer plugins (e.g., vah264enc for GPU, x264enc for CPU) encoder_element = self.select_element( encoder_config, encoder_device, - vaapi_suffix, ) if encoder_element is None: self.logger.error( - f"Failed to select encoder element for device: {encoder_device.device_name}" + f"Failed to select encoder element for codec: {codec} and encoder_device: {encoder_device}" ) raise ValueError( - f"No suitable encoder found for device: {encoder_device.device_name}" + f"No suitable encoder found for codec: {codec} and encoder_device: {encoder_device}" ) # Count fakesink instances