Detect: Basic hwaccel detection based on OS and GPU, Linux only ATM

This commit is contained in:
Ross Patterson 2019-08-07 05:24:09 -07:00
parent 891ca521fd
commit f17766aec3
6 changed files with 276 additions and 2 deletions

19
Makefile Normal file
View File

@ -0,0 +1,19 @@
## Automate common development tasks
.PHONY: default
defalt: ffmpeg/detect.json
.tox/py35/bin/python:
tox -e py35
touch "$(@)"
.tox/py35/lib/python3.5/site-packages/pandas: .tox/py35/bin/python
.tox/py35/bin/pip install requests lxml pandas
touch "$(@)"
.PHONY: ffmpeg/detect.json
ffmpeg/detect.json: .tox/py35/lib/python3.5/site-packages/pandas
.tox/py35/bin/python examples/get_detect_data.py >"$(@)"

59
examples/get_detect_data.py Executable file
View File

@ -0,0 +1,59 @@
#!/usr/bin/env python
"""
Retrieve and process all the external data for hardware detection.
"""
import sys
import json
import requests
import pandas
HWACCELINTRO_URL = 'https://trac.ffmpeg.org/wiki/HWAccelIntro'
API_TO_HWACCEL = {
'AMF': 'amf',
'NVENC/NVDEC/CUVID': 'cuvid',
'Direct3D 11': 'd3d11va',
'Direct3D 9 (DXVA2)': 'dxva2',
'libmfx': 'libmfx',
'MediaCodec': 'mediacodec',
'Media Foundation': 'mediafoundation',
'MMAL': 'mmal',
'OpenCL': 'opencl',
'OpenMAX': 'omx',
'V4L2 M2M': 'v4l2m2m',
'VAAPI': 'vaapi',
'VDPAU': 'vdpau',
'VideoToolbox': 'videotoolbox',
}
PLATFORM_TO_PY = {
'Apple': 'Darwin',
}
def main():
data = {}
data['hwaccels'] = hwaccels = {}
response = requests.get(HWACCELINTRO_URL)
api_avail_table, impl_table = pandas.read_html(response.content)
gpu_vendor_cols = api_avail_table.loc[1][1:]
platform_cols = api_avail_table.loc[0][1:]
api_rows = api_avail_table[0][2:]
hwaccels['api_avail'] = platforms = {}
for gpu_vendor_idx, gpu_vendor in enumerate(gpu_vendor_cols):
platform = platform_cols[gpu_vendor_idx + 1]
platform = PLATFORM_TO_PY.get(platform, platform)
gpu_vendors = platforms.setdefault(platform, {})
avail_hwaccels = gpu_vendors.setdefault(gpu_vendor, [])
for api_idx, api in enumerate(api_rows):
if api_avail_table[gpu_vendor_idx + 1][api_idx + 2] != 'N':
avail_hwaccels.append(API_TO_HWACCEL[api])
json.dump(data, sys.stdout, indent=2)
if __name__ == '__main__':
main()

View File

@ -1,6 +1,7 @@
from __future__ import unicode_literals
from . import nodes
from . import _build
from . import _detect
from . import _ffmpeg
from . import _filters
from . import _probe
@ -8,6 +9,7 @@ from . import _run
from . import _view
from .nodes import *
from ._build import *
from ._detect import *
from ._ffmpeg import *
from ._filters import *
from ._probe import *
@ -17,6 +19,7 @@ from ._view import *
__all__ = (
nodes.__all__
+ _build.__all__
+ _detect.__all__
+ _ffmpeg.__all__
+ _probe.__all__
+ _run.__all__

121
ffmpeg/_detect.py Normal file
View File

@ -0,0 +1,121 @@
"""Detect optimal arguments for various options.
This module includes a number of constants used to attempt to detect the
options which will provide the best performance for a given OS/GPU/etc..
For most of these constants, it only matters that the best performing option
available for a given OS/platform/hardware rank first for that
OS/platform/hardware, not which OS/platform/hardware performs better. For
example, it doesn't matter if `vdpau` is lower than `cuvid` or vice versa,
because one is only available for Linux and the other for Windows. Similarly,
it doesn't matter how `amf` is ranked with respect to `cuvid` because one is
only available on NVidia GPUs and the other AMD. It *does* matter how
`cuvid`/`amf` are ranked with respect to `dxva2` because those could both be
available on the same OS and GPU.
Additions and suggestions for these constants are very much welcome,
especially if they come with benchmarks and/or good explanations from those
who understand this domain well. Contributions of more complicated or
involved detection logic may also be welcome, though the case will have to be
made more rigorously.
"""
import sys
import platform
import os
import json
import logging
import argparse
import subprocess
import ffmpeg
logger = logging.getLogger(__name__)
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
'--ffmpeg', default='ffmpeg',
help='The path to the ffmpeg execuatble')
# List `hwaccel` options by order of expected performance when available.
HWACCELS_BY_PERFORMANCE = [
'cuvid', 'amf', 'vdpau',
'qsv', 'd3d11va', 'dxva2', 'vaapi', 'drm']
# Loaded from JSON
DATA = None
def detect_gpu():
"""
Detect the GPU vendor, generation and model if possible.
"""
plat_sys = platform.system()
if plat_sys == 'Linux':
display_output = subprocess.check_output(
['lshw', '-class', 'display', '-json'])
return json.loads(display_output.decode().strip().strip(','))
def detect_hwaccels(hwaccels=None, cmd='ffmpeg'):
"""
Extract details about the ffmpeg build.
"""
# Filter against what's available in the ffmpeg build
build_hwaccels = ffmpeg.get_hwaccels(cmd=cmd)
if hwaccels is None:
# Consider all the available hwaccels
hwaccels = build_hwaccels
else:
# Support passing in a restricted set of hwaccels
hwaccels = [
hwaccel for hwaccel in hwaccels if hwaccel in build_hwaccels]
# Filter against which APIs are available on this OS+GPU
data = _get_data()
plat_sys = platform.system()
gpu = detect_gpu()
api_avail = data['hwaccels']['api_avail'][plat_sys][
gpu['vendor'].replace(' Corporation', '')]
hwaccels = [hwaccel for hwaccel in hwaccels if hwaccel in api_avail]
hwaccels.sort(key=lambda hwaccel: (
# Sort unranked hwaccels last, but in the order given by ffmpeg
hwaccel not in HWACCELS_BY_PERFORMANCE,
(
# Sort ranked hwaccels per the constant
hwaccel in HWACCELS_BY_PERFORMANCE and
HWACCELS_BY_PERFORMANCE.index(hwaccel))))
return hwaccels
__all__ = [
'detect_gpu',
'detect_hwaccels',
]
def _get_data():
"""
Don't load the data JSON unless needed, cache in a global.
"""
global DATA
if DATA is None:
with open(os.path.join(
os.path.dirname(__file__), 'detect.json')) as data_opened:
DATA = json.load(data_opened)
return DATA
def main(args=None):
"""
Dump all ffmpeg build data to json.
"""
args = parser.parse_args(args)
data = dict(
gpu=detect_gpu(),
hwaccels=detect_hwaccels(args.ffmpeg))
json.dump(data, sys.stdout, indent=2)
if __name__ == '__main__':
main()

71
ffmpeg/detect.json Normal file
View File

@ -0,0 +1,71 @@
{
"hwaccels": {
"api_avail": {
"Linux": {
"AMD": [
"opencl",
"omx",
"vaapi",
"vdpau"
],
"NVIDIA": [
"cuvid",
"opencl",
"vaapi",
"vdpau"
],
"Intel": [
"libmfx",
"opencl",
"vaapi"
]
},
"Darwin": {
"iOS": [
"videotoolbox"
],
"macOS": [
"opencl",
"videotoolbox"
]
},
"Android": {
"NaN": [
"mediacodec",
"opencl",
"omx",
"v4l2m2m"
]
},
"Other": {
"Raspberry Pi": [
"mmal",
"omx"
]
},
"Windows": {
"AMD": [
"amf",
"d3d11va",
"dxva2",
"mediafoundation",
"opencl"
],
"NVIDIA": [
"cuvid",
"d3d11va",
"dxva2",
"mediafoundation",
"opencl"
],
"Intel": [
"d3d11va",
"dxva2",
"libmfx",
"mediafoundation",
"opencl"
]
}
}
}
}

View File

@ -95,8 +95,9 @@ setup(
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
],
entry_points = {
entry_points={
'console_scripts': [
'ffmpeg-build-json=ffmpeg._build:main'],
'ffmpeg-build-json=ffmpeg._build:main',
'ffmpeg-detect=ffmpeg._detect:main'],
},
)